堆排序是对简单选择排序改进,减少了查找最小记录的次数。
实现堆排序需要解决两个问题:
(1 )如何由一个无序序列构建成一个堆?
(2 )如何在输出堆顶元素之后,调整剩余元素成为一个新的堆?
先讨论第二个问题,假设输出堆顶元素之后,以堆中最后一个元素替代之,此时根结点的左右子树均为堆,则仅需自上至下进行调整即可。这个自堆顶至叶子的调整过程称为“筛选”。
而从一个无序序列构建堆的过程就是一个反复“筛选”的过程。若将此序列看成是一个完成二叉树,则最后一个非终端结点是第(n>>1)个元素,由此“筛选”只需从第(n>>1)个元素开始。
对应于程序,就有两部分,部分就是调整堆,即“筛选”,另一部分就是输出堆顶元素,并用未排序序列中的最后一个元素替代之。
//调整堆为最大堆
void heapAdjust(ElemType array[],int s,int length){
int i;
ElemType median;
for(i = 2 * s; i <= length;i *= 2){
//保证数组不越界i,且右孩子结点关键字较小,则i指向key较小的记录
if(i < length && (array[i-1] < array[i] ? 1: 0))
i++;
if(array[s-1] > array[i-1])
break;
median = array[s-1];
array[s-1] = array[i-1];
array[i-1] = median;
s = i;
}
}
这里调整为最大堆是为了最后输出是按从小到大顺序输出,如果需要从大到小排序,可以更改程序里的两个if语句,使其中的的“>”变成“<”,“<”变成“>”即可
void heap_sort(ElemType array[],int length){
int i;
ElemType median;
//对初始记录序列构建最小堆
for(i = length/2; i > 0;i--)
heapAdjust(array,i,length);
for(i = length;i > 1;i--){
//将堆顶的最小元素和当前未经排序子序列[1....i]中最后一个记录相互交换
median = array[0];
array[0] = array[i-1];
array[i-1] = median;
//重新构建最大堆
heapAdjust(array,1,i-1);
}
}
注意: 堆排序方法对记录数较少的文件并不值的提倡,但对于n较大的文件还是很有頝的,因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上,对于深度为k的堆,筛选算法中进行的关键字比较次数至多为2(k-1)次,则在建含n个元素,深度为h的堆时,总共进行的关键字比较次数不超过4n。
堆排序时间复杂度为O(nlogn)。