一、预备知识-堆
堆排序是利用堆这种数据结构而设计的一种排序算法,堆排序是一种选择排序,它的最坏,最好,平均时间复杂度均为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