堆排序是一种高级排序算法,时间复杂度为O(nlogn),空间复杂度仅为O(1),对于海量数据使用堆排序是相当高效的。
相比其他排序算法,堆排序确实比较难,不仅体现在思想上,更体现在代码逻辑上。在《数据结构与算法分析(Java语言描述)》所给的堆排序伪代码中,有一个很巧妙的地方,值得我们反复推敲。当然在看代码前,大家先自行搞懂堆排序思想。
堆排序第一个步骤是构造二叉堆(按二叉树理解也没问题)。二叉堆是由数组实现的,假设我们要对一个数组进行升序排列,就得构造大根堆;否则,就得构造小根堆。我们当然是进行升序排列,所以要构造大根堆。
//arr : 数组
//n : 数组前n个数的长度。为什么不是数组长度?因为后面的数是已经排好序的,
// 所以不必再进入二叉堆。这里不理解不要紧,后面堆排序中大家就能明白了。
//i : 从左往右,从下往上第一个非叶结点的数组下标。即 i = n / 2 - 1
//另外一个重要的点,下面这个函数只是让以某个非叶结点为根的子堆局部构成大根堆,
//而整个数组构成大根堆是循环遍历的结果。
private static void adjustHeap(int[] arr, int n, int i){
int child; //子节点
int temp; //存储结点i的值
//下面这个for循环,temp初始化为arr[i]后便永恒不变了(巧妙1);
//每轮循环的判断条件是看当前结点是否有左子节点;
//子节点成为当前结点(巧妙2),继续向下构造大根堆(因此构造大根堆是一个自顶向下的过程)
for (temp = arr[i]; i * 2 + 1 < n; i = child){
child = i * 2 + 1;
if (child + 1 < n && arr[child] < arr[child + 1])
++child; //存在右子节点且节点值比左子节点大(自然会交换最大的)
if (temp < arr[child]) //当前节点值小于子节点值,要交换
arr[i] = arr[child];//(巧妙3,看下面解释)
else break;
}
arr[i] = temp; //temp最终位置,是最后一次赋值来的,而不是一次次交换的结果
}
上面说了,当前节点值小于子节点值时,逻辑上是肯定要交换的,不然就不是大根堆。但是大家也会疑惑,后面代码压根就不是交换,只是把子节点值赋给了当前结点。
我前面说了,堆排序适合于排序海量数据,在最差情况下,如果每两个数之间都要进行一次O(3)的交换,肯定会带来很大的开销。看到这里,大家又疑惑了,这不是跟我前面说的自相矛盾吗?
大家结合一下代码中的巧妙1,巧妙2,巧妙3。有没有这样一种体会:代码里没有实现交换,但是逻辑上的确交换了。每次构造中,temp是不变的,若有继续向下构造大根堆的机会,依然是用temp来比较,却是早就交换了哈哈!
构造二叉堆是堆排序的核心与重难点,大家一定要反复理解透彻。相比之下,接下来的堆排序不过是循环使用adjustHeap罢了。
public static void heapsort(int[] arr){
for (int i = arr.length / 2 - 1; i >= 0; i--) //整体形成大根堆
adjustHeap(arr, arr.length, i);
for (int i = arr.length - 1; i >= 0; i--){
//每次交换大根堆的第一个数和最后一个数
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
//交换后的最后一个数已经放在排序后的最终位置
//对剩余数重新构造大根堆
//(只需对根节点构造大根堆,因为根节点以下的子堆早已是局部大根堆了)
adjustHeap(arr, i, 0);
}
}
最后我测试以下的堆排序的性能:
public static void main(String[] args) {
int[] arr = new int[9999999];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int)(Math.random()*arr.length);
}
long start = System.currentTimeMillis();
heapsort(arr);
long end = System.currentTimeMillis();
System.out.println("堆排序耗时:" + (end - start) + "ms");
}
我前面比较过希尔排序的耗时,结合这次的实践证明,堆排序比希尔增量和Hibbard增量下的希尔排序快了1/3,略慢于Sedgewick增量下的希尔排序
《希尔排序在三种著名增量下的时间性能比较》