一、预备知识-堆
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为O(nlogn),它也是不稳定排序。
堆是具有以下性质的完全二叉树:
- 每个结点的值都大于或等于其左右孩子结点的值,称为大根堆;
- 或者每个结点的值都小于或等于其左右孩子结点的值,称为小根堆。如下图:
通过图可以比较直观的看出大根堆和小根堆的特点,需要注意的是:这种结构是对父节点-左/右孩子节点之间做的约束,而对左-右孩子节点之间并没有什么要求。
另外,因为堆的结构是完全二叉树,所以可以用数组来存储,并通过节点下标的规律快速进行索引。
下面是上图大根堆与小根堆对应的数组:
二、堆排序基本思想图解(大根堆为例)
假设现在待排序数据存在array[count]中,其初始状态如下:
对应的完全二叉树为:
堆排序的过程如下:
(1)初始化堆;
因为堆是对父节点-左/右孩子节点之间的约束,所以从最后一个非叶子节点开始调整。
注意每次交换后,都要对下一层的子堆进行递归调整,因为交换后有可能破坏已调整子堆的结构。
(2)进行调整后,堆顶元素(array[0])为最大值,将最大值与堆尾部元素(array[count-1])交换,并将count值减去1,则此时得到新的无序数组array[count],此时的堆被破坏;
对应到数组元素为:
(黄色标记为已排序部分)
(3)调整堆:与建堆过程类似,堆顶元素被一个比较小的值代替,所以从堆顶元素开始调整,在堆顶、堆顶的左孩子、堆顶的右孩子中找出最大的与堆顶进行交换,被交换的元素再次与他下一层的左右孩子进行比较(递归)调整。
(4)重复(2)。
下面把整个过程画完:
此时,大概的一个手工过程就懂了,注意的是:初始化堆是基础,时从下向上调整。交换后调整堆时因为有了建堆的基础,每次调整的都是二叉树的一支子树,是从上往下。
三、代码实现
- package Alog;
-
- public class HeapSort {
-
- public static void main(String[] args) {
- int[] array = new int[]{12, 5, 9 , 36, 8, 21, 7};
- System.out.println("初始状态:");
- showArray(array);
-
- initHeap(array); //这个应该也是堆排序的一部分,此处只是为了显示下结果
- System.out.println("建堆之后:");
- showArray(array);
-
- heapSort(array);
- System.out.println("排序之后:");
- showArray(array);
- }
-
-
- public static void heapSort(int[] array){
- initHeap(array); //建堆
-
- int count = array.length;
- while(count > 1) {
- int tmp = array[count - 1];
- array[count - 1] = array[0];
- array[0] = tmp;
-
- count--; //未排序部分又少一个
- adjustHeap(array, count, 0);//调整过程自上而下,参数root=0
- }
- }
-
- public static void initHeap(int[] array){
- //建堆,从最后一个非叶子节点开始,而最后一个非叶子节点的下标为array.length/2-1
- for(int root = array.length/2 - 1; root >= 0; root--){
- adjustHeap(array, array.length, root);
- }
-
- }
-
-
- public static void adjustHeap(int[] array, int count, int root){
- int maxChildIndex;
-
- while(root <= count/2-1) { //待调整子堆的根节点必须是非叶子节点
- //需要在root、letfchild、rightchild中找到最大的值,
- //因为最后一个非叶子节点有可能没有右孩子,所以要做出判断。
- if(root == count/2 - 1 && count % 2 == 0){
- //节点数量为偶数时,最后一个非叶子节点只有左孩子
- maxChildIndex = 2 * root + 1;
- }else{
- int leftChildIndex = 2 * root + 1;
- int rightChildIndex = 2 * root + 2;
-
- maxChildIndex = array[leftChildIndex] > array[rightChildIndex] ?
- leftChildIndex : rightChildIndex;
- }
-
- if(array[root] < array[maxChildIndex]){
- int tmp = array[root];
- array[root] = array[maxChildIndex];
- array[maxChildIndex] = tmp;
-
- //*****************这里很重要,继续调整因交换结构改变的子堆
- root = maxChildIndex;
- }else{
- return;
- }
- }
- }
-
- public static void showArray(int[] array){
- for(int i = 0; i < array.length; i++){
- System.out.print(array[i] + ((i == array.length - 1) ? "\n" : " "));
- }
- }
- }
输出结果:
初始状态:
12 5 9 36 8 21 7
建堆之后:
36 12 21 5 8 9 7
排序之后:
5 7 8 9 12 21 36