目录
1.堆排序概述
简单选择排序是:假设排序序列为
L[1...n]
,第i趟排序从
L[i...n]
中选择关键字最小(大)元素与
L[i]
交换,每一趟排序可以确定一个元素到最终的位置上,这样经过
n−1
趟排序就可以使得整个排序序列有序。可惜的是,这样操作并没有把每一趟的比较结果保存下来,在后一趟的比较中,有许多比较在前一趟的比较中已经做过了,因而比较的次数较多。
如果每趟排序可以在找到最小(大)元素的同时,根据比较结果对其它记录做出调整(逆序交换),那么简单选择排序的效率就会大大提高。于是Floyd和Williams在1964年发明了Heap Sort(堆排序)方法,对简单选择排序进行改进,所以堆排序也是选择排序算法中的一种。
堆排序是一种基于完全二叉树的树形选择排序 方法。在排序的过程中将待排序列看成是一颗完全二叉树的顺序存储结构,树上的每一个结点对应数组中的一个元素,可以利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序序列中构建“二叉堆”,简称“建堆”操作。从而在二叉堆的根部找到关键字最大(小)的元素。这种叫“堆”的数据结构可以保存每趟排序过程中的中间比较结果。堆按性质可分为大顶堆和小顶堆。如果想让序列按升序(降序)排序,就需要将待排序的n个元素构造成大顶堆(小顶堆)。此时堆顶即为最大值(最小值),将它和堆数组的尾元素交换,堆数组长度减1。然后将剩余的n-1个待排元素重新建堆,从而得到n个元素中的次大元素,将它和堆数组的尾元素交换,堆数组长度减1。反复迭代,最终得到一个有序的序列。
堆排序具有空间原址性(in space),即任何时候都只需要常数个额外的元素空间存储临时数据。
2.二叉堆
(二叉)堆是具有下列性质的完全二叉树:
每个结点的值 L[i] 都大于或等于其左孩子 L[left(i)] 和右孩子 L[right(i)] 结点的值,称为大顶堆(最大堆);
每个结点的值 L[i] 都小于或等于其左孩子 L[left(i)] 和右孩子 L[right(i)] 结点的值,称为小顶堆(最小堆);
堆 | 堆的性质 |
---|---|
最大堆 | L[PARENT(i)]>=L[i] |
最小堆 | L[PARENT(i)]<=L[i] |
从二叉堆性质中,可以看到根节点一定是所有结点的最小(大)值。较小(大)的结点和根结点的距离较近。
3.二叉堆的存储
二叉堆在内存中是用数组存储的。
如果堆的根结点为
L[0]
,
i
结点的父结点下标就为
如果堆的根结点为
L[1]
,
i
结点的父结点下标就为
4.维护堆的性质
堆排序算法中,最关键的就是构造初始堆。需要编写一个维护大顶堆性质的函数Max_Heapify。当输入一个数组L和一个下标i,然后调用Max_Heapify时,比较 L[i] 、 L[left(i)] 和 L[right(i)] 三者的大小;如果 L[i] 小于其孩子结点,就违背了大顶堆的性质,让 L[i] 和较大的孩子结点交换,实现在大顶堆中“降级”,从而使 L[i] 结点遵循大顶堆的性质。但交换后,以 L[i] 为根的子树又有可能违反最大堆的性质,因此对该子树递归的调用Max_Heapify函数。如调用Max_Heapify(L,2)后的递归过程。
4.1递归实现maxHeapify函数
void maxHeapify(int arr[],int i,int heapsize) {//递归实现
int left=2*i+1;
int right=2*i+2;
int largest;
if (left<=heapsize && arr[left]>arr[i])
largest=left;
else
largest=i;
if (right<=heapsize && arr[right]>arr[largest])
largest=right;
if(largest!=i) {
swap(&arr[largest],&arr[i]);
maxHeapify(arr,largest,heapsize);
}
}
4.2非递归实现maxHeapify函数
void maxHeapify(int arr[],int i,int heapsize) {//非递归实现
int left=2*i+1;
int right=2*i+2;
int largest;
while(left<=heapsize) {
if (arr[left]>arr[i])
largest=left;
else
largest=i;
if (right<=heapsize && arr[right]>arr[largest])
largest=right;
if(largest!=i) {
swap(&arr[largest],&arr[i]);
i=largest; //从上向下维护堆性质;所以要更新根节点、左右节点的索引;
left=2*i+1;
right=2*i+2;
} else {
break;
}
}
}
5.建堆操作
对一个长度为length的数组 arr[0...(length−1)] 而言,结点索引为 (length−1) 的父亲结点的索引为 i=⌊((length−1)–1)/2⌋ 。子数组 arr[(i+1)...(length−1)] 中的元素都是叶结点。每个叶结点都可看成是只包含一个元素的堆。我们从索引为i的结点开始,从右向左、从下向上地调用maxHeapify,把数组 arr[0...(length−1)] 转换成一个大顶堆。
5.1从下向上、从右向左建堆(C实现)
void buildMaxHeap(int arr[],int heapsize) {//实现1
for(int i=(heapsize-1)/2; i>=0; i--) {//从下向上,从右向左维护堆性质
maxHeapify(arr,i,heapsize);
}
}
5.2从上向下、从左向右建堆(C实现)
当数组仅有 arr[0] 时,不要执行操作,就是一个天然的大顶堆;然后把 arr[1...(length−1)] 的元素逐个插入到堆的尾部,每插入一个元素,都要对已有的大顶堆进行维护。不再展开详解。仅给出代码实现。
void buildMaxHeap(int arr[],int heapsize) { //实现2
if(heapsize==0)
return;
for(int i=1;i<=heapsize;i++){
int index=i;
int parent=floor((index-1)/2); //floor函数定义在<math.h>中;
while(parent>=0 && arr[parent]<arr[index]){
swap(&arr[parent],&arr[index]); //arr[0]元素父节点arr[-1],循环中断;
index=parent;
parent=floor((index-1)/2); //不加floor函数,可以把arr[0]的父节点看成是自身;
}
}
}
6.堆排序
将待排序的length个元素构造成大顶堆。此时堆顶 arr[0] 即为最大值,将它和堆数组的尾元素 arr[length−1] 交换。然后将 arr[length−1] 从堆中去掉,剩余待排元素重新建堆,从而得到次最大元素,将它和堆数组的尾元素交换。反复迭代,直到堆中元素的数目只剩一个,则必然是最小的元素。所以从 arr[length−1] 遍历到 arr[1] 即可。最终得到一个有序的序列。
//从arr[length-1]到arr[1]依次调用buildMaxHeap,最终实现排序
void heapSort(int arr[],int len) {
buildMaxHeap(arr,len-1);
for(int i=len-1; i>0; i--) {
swap(&arr[0],&arr[i]);
//buildMaxHeap(arr,i-1);此处无需从新建堆,从新维护堆性质即可
maxHeapify(arr,0,i-1);
}
}
7.堆排序C代码实现
#include <stdio.h>
#include <stdlib.h>
int arrtest[100];
void swap(int *a,int *b) {
int temp=*a;
*a=*b;
*b=temp;
}
void maxHeapify(int arr[],int i,int heapsize) { //维护堆的性质,可以用非递归实现
int left=2*i+1;
int right=2*i+2;
int largest;
if (left<=heapsize && arr[left]>arr[i])
largest=left;
else
largest=i;
if (right<=heapsize && arr[right]>arr[largest])
largest=right;
if(largest!=i) {
swap(&arr[largest],&arr[i]);
maxHeapify(arr,largest,heapsize);
}
}
void buildMaxHeap(int arr[],int heapsize) { //建堆
for(int i=(heapsize-1)/2; i>=0; i--) {
maxHeapify(arr,i,heapsize);
}
}
void heapSort(int arr[],int len) { //堆排序
buildMaxHeap(arr,len-1);
for(int i=len-1; i>0; i--) {
swap(&arr[0],&arr[i]);
//buildMaxHeap(arr,i-1);//此处无需从新建堆,从新维护堆性质即可
maxHeapify(arr,0,i-1);
}
}
void PrintArray(int arr[],int length) { //打印数组,用于查看排序效果
printf("[");
for(int i=0; i<length; i++) {
if(i==length-1)
printf("%d]\n",arr[i]);
else
printf("%d ,",arr[i]);
}
}
int main() {
int num=0;
printf("请输入数组元素个数:");
scanf("%d",&num);
for(int i=0; i<num; i++) { //读入待排序数据
scanf("%d",&arrtest[i]);
}
printf("排序前数据为:");
PrintArray(arrtest,num);
heapSort(arrtest,num);
printf("排序后数据为:");
PrintArray(arrtest,num);
return 0;
}
8.参考资料
- 《2013数据结构联考复习指导》 王道论坛 组编
- 《算法导论 (原书第3版)》 机械工业出版社
- 《大话数据结构》 清华大学出版社 程杰 著