前提知识:
若以一维数组存储一个堆,则堆对应一棵完全二叉树,且所有非叶结点的值均不大于(或不小于)其子女的值,根结点(堆顶元素)的值是最小(或最大)的。
小顶堆:{12,36,24,85,47,30,53,91}
大顶堆:{96, 83,27,38,11,09}
如果i>1,则双亲是结点[i/2]。也就是说下标i与2i和2i+1是双亲子女关系。 当排序对象为数组时,下标从0开始,所以下标 i 与下标 2i+1和2i+2是双亲子女关系。
最后一个有孩子节点的节点是(length-1)/2
升序排序:用大顶堆
降序排序:用小顶堆
基本思想:
先将数组建成一个大顶堆;然后再将大顶堆的根放到数组的最后一个位置;然后调整此时的堆成大顶堆,重复,直至到根。
操作过程:
从算法描述来看,堆排序需要两个过程,一是建立堆,二是堆顶与堆的最后一个元素交换位置。所以堆排序有两个函数组成。一是建堆的渗透函数,二是反复调用渗透函数实现排序的函数。
public class heapSort {
/*调整大顶堆
* @parm 数组
* @parm 要调整的位置
* @parm 数组的长度
*/
public void heapAdjust(int a[],int s,int length){
int temp = a[s];
int child = 2*s+1;//左孩子的位置;右孩子位置是2*s+2
while(child < length){
if(child+1 < length && a[child]<a[child+1]){
++child;//找到左右孩子中,较大的那一位
}
if(a[s]<a[child]){
//比较a[s]和较大的孩子,a[s]>a[child],则不交换;否则,交换
a[s]=a[child];// 重新设置s ,即待调整的下一个结点的位置
s=child;
child = 2*s+1;
}else{
break;
}
a[s]=temp;//最后将调整的值,赋值给最后a[s]调整到的位置
}
}
//生成堆
public void createHeap(int a[],int length){
//最后一个有孩子的节点位置是(length-1)/2
for(int i=(length-1)/2;i>=0;--i){
heapAdjust(a,i,length);
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int a[]={3,5,2,9,7,1,4};
int n=a.length;
heapSort hs = new heapSort();
hs.createHeap(a,n);
for(int i=n-1;i>=0;--i){
//先把最后一个元素和大顶堆的根交换
int temp=a[i];
a[i]=a[0];
a[0]=temp;
//再调整堆
hs.heapAdjust(a, 0, i);
}
for(int i=0;i<n;i++){
System.out.println(a[i]);
}
}
}
分析:
设树深度为k,。从根到叶的筛选,元素比较次数至多2(k-1)次,交换记录至多k 次。所以,在建好堆后,排序过程中的筛选次数不超过下式:
而建堆时的比较次数不超过4n 次,因此堆排序最坏情况下,时间复杂度也为:O(nlogn )。
感觉堆排序比较难理解,关键是要有数据结构堆的知识掌握