堆排序是威洛母斯堆树形选择排序进一步改进的方法,以弥补树形选择排序占用空间多的遗憾。
堆排序只需要一个记录大小的辅助空间,将数组中存储的数据看成一课完全二叉树,利用二叉树中双亲结点和孩子结点的内在关系选择关键字最大或最小的记录。堆排序的操作对象是向量数组。
具体操作:
1.把待排序的记录关键字存放在arr[1..n]中,将arr抽象成一棵完全二叉树,每个结点作为一个数组的单位。
2.记第一个结点arr[1]为完全二叉树的根,其余的arr[2..n]按从左到右的顺序排列
3.任意结点arr[i]的左孩子为a[2*i],右孩子为a[2*i+1]
大根堆与小根堆:
若任意结点满足arr[i]>=arr[i*2]并且arr[i]>=[i*2+1],则称该树为大根堆得的完全二叉树。否则为小根堆完全二叉树。
本篇章使用的例子为大根堆
一.重建堆
若堆中某结点改变时,该如何重建堆?
【算法思想】
1.首先将待调整的数据记录后移除数组,此时该结点相当于空节点
2.从该结点的左右子树中选取一个较大的结点于待调整结点比较,若待调整结点大于左右子树较大的一个,则将较大的子结点向上移动补充待调整结点。
3.此时,被上移的子节点相当于空节点,在该节点的左右子树中选出一个较大的记录,如果满足过程2的条件,则也将其向上移动。
4.重复123操作直到待调整结点的子树为空或待调整结点的数据大于其左右子树的数据。
5.将最后上移的结点的值赋为待调整的结点。
该算法又叫筛选法
【几个问题】
1.若一开始待调整的结点的左右子树均小于待调整结点的值?
此时不需要移动
2.若待调整的结点是最后一个,根据算法描述的思想,该结点没有子树,那怎么使该节点位于正确的位置,如图所示
如果为想移动98,他应该出在根节点的位置,但是根据程序思想,他总是会向下找结点,那怎么办呢?
答案就是结合建初堆
c代码
/*重建堆*/
/*a为数组元素 b为待调整的结点在数组的下标,c为数组的长度*/
void sift(int a[],int b,int c){
//暂存根的值
int t=a[b];
//临时变量i
int i=b;
//找到结点的左孩子,如果数组从1开始则左孩子为2*i,若数组从0开始则微微2*i+1
int j=2*i+1;
//做标记,如果满足待调整值大于左右子树,则将finshed的值改为1
int finshed=0;
while(j<c&&!finshed){
//挑选出较大的结点
if(j<c&&j+1<c&&a[j+1]>a[j]){
j=j+1;
}
//筛选 如果结点大于该当前结点 ,说明该节点在当前位置合适,筛选完毕 否则交换值
if(t>=a[j]) finshed=1;
else{
//该结点的值为子节点较大的一个
a[i]=a[j];
//将操作单位改为子节点较大的那个
i=j;
//再找到他的左孩子
j=2*i+1;
}
}
//将原结点放入合适位置
a[i]=t;
}
【算法分析】
在每次循环是比较左右子树一次,然后再在左右子树中选大的一个,所以对于深度为h的树,比较次数是2(h-1)
2.建初堆
如何由一个任意序列建初堆?
【算法思想】
1.将一个任意的序列看成是一棵完全二叉树,反复利用调整算法(筛选)重建堆,自底向上逐层把子树调整层成堆,直到根节点。
2.因为在完全二叉树中,最后一个非叶子结点位于第[i/2]的位置,逐步向上倒退,直到根节点。
/*建初堆*/
/*a为待调整数组,n为数组大小*/
void crt_heap(int a[],int n){
//从n/2开始调整
for(int i=n/2;i>=0;i--){
//反复重建堆
sift(a,i,n);
}
}
【算法分析】
建立n个元素的,深度为h的堆时,在第i层,有2的i次方-1个元素需要筛选,每个元素的调整次数为2(h-1),所以
总耗时为∑2的i-1次方(下界为1,上界为h-1)*2(h-1)=∑2的i次方*(h-1)
展开之后可以知道最高次幂为1次方,所以该算法是线性的。
时间复杂度为O(n).
3.堆排序
如何将堆排序,满足从1到i的结点顺序排列?
【算法思想】
1.将待排序的数组初建堆
2.调整剩余的序列,利用筛选法将n-i个元素重建堆
3.重复步骤2,进行n-1次排序(根节点不需要排序所有减一),新筛选的堆会越来越小,直到序列成为有序堆。
/*堆排序*/
/*a为待调整数组,n为数组大小*/
void HeapSort(int a[],int n){
//建初堆,可以减少比较次数,也可以不写
// crt_heap(a,n);
for(int i=n-1;i>=1;i--){
//将根节点的值与当前值交换
int t=a[0];
a[0]=a[i];
a[i]=t;
// 重建堆 使根节点的记录处在合适的位置上
sift(a,0,i-1);
}
}
【算法分析】
n个结点的完全二叉树的深度为(log2的n次)+1,n个结点排序使调用重排序的次数为n-1次(每次的时间复杂度为log2的i次方),总共次数为2(log2的n-1次方+log2的n-2次方+..+log2的2次方)<2nlog2的n次方
所以堆排序即使在最差的情况下,时间复杂度也只是n*log2n次方。是一种高效的排序方式