一、基本思路
堆排序的规则在于构造最小堆或最大堆,其核心是对堆的调整(adjust),以最大堆为例,我们需要将待排序数组看成一个完全二叉树,它有以下特点,每个节点都大于它的任何子节点,符合这个条件的堆,就是最大堆,如
其对应的数组为 { 9,8,7,6,5,4,3,2 }
当我们拿到一个数组时,我们的最开始需要利用待排序区间构建一个最大堆,然后将堆顶的数,也就是当前待排序区间最大的数移到待排序区间最后一个(与最后一个交换),并把待排序区间缩减1,然后逐步调整根节点
如何构建初始最大堆(其中需要多步调整)是我们整个排序最核心的部分,其大致的原理为
1.从最后一个非叶子节点开始调整,如上图值为6的节点,以它作为子树,对它进行调整
调整方法为:将子树根节点保存为一个temp值,逐层往下探索,每次遇到比该temp值大的子节点,将两个子节点最大的值赋给它们的父节点,并往最大子节点的那个分支继续探索,如果遇到的该层的子节点均比该temp值小,那么我们就找到了temp值应该在的位置啦,把temp放进去,这样一来,该子树也就调整为局部最大堆了
2.调整倒数第二个、倒数第三个非叶子节点......
3.当我们调整完最后的非叶子节点,也就是堆顶的节点时,最大堆也就构建完成了
4.此时,将堆顶的元素放入已排序区(与最后一个元素交换),待排序区间缩减1
二、例子
我们以{ 0,1,2,3,6,5,4,7,9,8 }为例
第一步:构建初始最大堆
1.调整值为 6 的这个节点代表的子树,temp值为6,我们可以看到,它的子节点中最大的为8,比temp大,把8赋给父节点,如下
下面的这个8形同虚设,你可以将它理解为只是一个即将被填充的坑而已
此时,往下寻找temp值应该在的地方,由于已经到底了,所以我们将temp填进这个坑,如下
经过一次调整后的树为
2.调整值为3的这个节点代表的子树,temp值为3,往下找,找到最大子节点9,并且还大于temp,9赋给父节点,由于已经到最底,将temp填进这个坑,得到的树为
3.调整值为2的这个节点代表的子树,temp值为2,与上面类似,得到的树为
4.调整值为1的这个节点代表的子树,此时temp值为1,可以预想到,1这么小肯定会被放到最底下啦,我们来看看吧
往下第一层,取最大的子节点9,由于9比temp大, 将9赋给父节点,留下一个坑,如
再往下看,取最大的子节点7,由于7比temp大, 将7赋给父节点那个坑,如
再把temp值填进这个坑,得到如下
5.调整值为0的节点,按照前面的调整方法,我们可以得到初始的最大堆为
第二步:逐步调整根节点
将堆顶的数,也就是当前待排序区间最大的数移到待排序区间最后一个(与最后一个交换),并把待排序区间缩减1,(如下图),然后调整根节点,也就是值为0的那个节点
重复第二步,直到待排序区间为0,就大功告成啦
三、代码实现
public class HeapSort {//以最大堆为例
public static void main(String[] args) {
int [] a=new int[]{3,5,7,9,8,6,2,1,4,0};
HeapSort heaptSort=new HeapSort();
heaptSort.heapSort(a);
for(int item:a){
System.out.print(item+" ");
}
}
private void heapSort(int[] a){
for(int i=a.length/2-1;i>=0;i--){//先构建初始最大堆
adjustHeap(a, i, a.length);
}
for(int j=a.length-1;j>0;j--){//交换+调整根节点
int t=a[j];
a[j]=a[0];
a[0]=t;
adjustHeap(a, 0, j);
}
}
private void adjustHeap(int[] a,int i,int length){//i为非叶子节点的位置,length为堆的长度
int temp=a[i]; //此次要调整的子树的根节点的值
for(int k=2*i+1;k<length;k=2*k+1){ //一层一层观察子节点能否
if(k+1<length&&a[k]<a[k+1]){
k++; //如果左节点比右节点小,k指向右节点,这保证了k一直指向最大的子节点
}
if(a[k]>temp){
a[i]=a[k]; //如果最大子节点比根节点还大,则把最大子节点的值赋给父节点,不作交换
i=k;
}
else
break;
}
a[i]=temp;//上面循环过后,i已经指向了一个其子节点都小于temp的节点,将temp填进该位置
}
}
四、复杂度
最佳、平均、最差情况均为O(nlogn)