一、堆排序法的概念
堆排序(Heap Sort)也是一种选择排序算法,堆排序是利用堆的特性进行排序的过程。
二、算法描述
堆是一个完全二叉树,树中每个节点对应于原始数据的一个记录,并且每个节点应满足以下条件:非叶节点的数据大于或等于其左、右孩子节点的数据(若是按从大到小的顺序排序,则要求非叶节点的数据小于或等于其左、右孩子节点的数据)。
提示:在堆中,对节点的左孩子和右孩子的大小没有要求,只规定父节点和子节点数据之间必须满足的条件。
当要按从小到大的顺序输出数据时,要求根节点为最大值。
下面以按从小到大的顺序排序为例,介绍堆排序的相关内容。
由堆的定义可看出,其根节点为最大值,堆排序就是利用这一特点进行排序的。堆排序过程包括以下两个阶段:
(1)将无序的数据构成堆(即用无序数据生成满足堆定义的完全二叉树)。
(2)利用堆排序(即将上一步生成的堆输出,得到排序后的有序数据)。
1.构成堆
构成堆就是把无序的数据按堆的定义进行交换调整,使父节点的数据大于子节点的数据。构成堆的具体步骤如下。
(1)将无序数据放入完全二叉树的各节点。
(2)由二叉树的下层向上层逐层进行父子节点的数据比较,使用一种称为“筛”的运算进行节点数据的调整,直到使节点最后满足堆的条件为止。
筛运算针对非叶节点进行,以非叶节点A i 的筛过程为例,当对A进行筛运算时,比它编号i大的非叶节点都已进行过筛运算,即已形成了以各非叶节点为根的堆,其中包括以A i 的左、右孩子为根的堆(若A 2i 和A 2i+1 为叶节点,则认为叶节点是堆)。所以,对A i 进行筛运算是在其左、右子树均为堆的基础上实现的。具体过程为:
(1)确定A i 的两个子树的最大值,放在A j 中。
(2)将A i 的数据与A的数据进行比较,如果A为根的子树已构成堆,筛运j i ≥A j ,表示以A i 算完成。
(3)若A i <A j ,则将A i 与A j 互换位置,互换位置后可能会破坏以A i (此时A i 的值为原来的A j )为根的堆,接着再以A j 为根重复前面的步骤,直到父节点数据大于子节点,或子节点为空时为止。这样,以A j 为根的子树就被调整为一个堆。
提示:在以上过程中,在对A j 进行的筛运算中,若其值较小,则会被逐层下移。这样,较小的数据就像被筛子筛下去,而较大的数据保留在筛子上面,所以把构成堆的过程形象地称为筛运算。
下面以一组待排序的数据演示构成堆的过程,假设有8个需要排序的数据序列如下:
69,65,90,37,92,6,28,54
下图1,给出构成堆的全过程,a图为无序数据构成的完全二叉树,具体过程如下:
图1构成堆过程
(1)首先对最后一个非叶节点(节点4)进行筛运算,使其与子节点进行比较,因该子节点只有左子树(节点8),而37<54,需对这两个节点位置进行互换,得到b图。此时,节点4及其子节点构成堆。
(2)接着对二叉树中倒数第2个非叶节点(节点3)进行筛运算,因节点3的两个子节点中节点7的值较大,因此使节点3与节点7进行比较,90>28,不需要进行互换,得到图c。此时,节点3及其子节点构成堆。
(3)对二叉树中倒数第3个非叶节点(节点2)进行筛运算,因节点2的两个子节点中节点5的值较大,因此使节点2与节点5进行比较,65<92,将节点2与节点5的数据进行互换,得到图d。此时,节点2及其子节点构成堆。
(4)对二叉树中倒数第4个非叶节点(节点1)进行筛运算,因节点1的两个子节点中节点2的值较大,因此使节点1与节点2进行比较,69<92,将节点1与节点2的数据进行互换,得到图e。此时,因节点2的值已改变,可能破坏了节点2与其子节点构成的堆,需要重新对节点2及其子节点进行运算。在本例中,节点2比两个子节点的值都大,符合堆的要求。此时,节点1及其子节点构成堆。至此,就将完全二叉树构成了一个满足要求的堆。
2.利用堆排序
使用上面的步骤构成堆以后,接下来的工作就是通过堆输出有序的数据。
从图1_e生成的堆可看出,节点1的值是整个二叉树中最大的,按顺序输出时,需将其放在数组的最后。根据这个规律,将堆按顺序输出数据的过程如下:
(1)取堆的根节点(最大值),将其放到数组的最后。由于数组元素的最后一个元素保存着节点8的值37,因此,将节点8的值与节点1互换。
(2)节点8的值37换到根节点后,重新执行前面介绍的构成堆的方法,此时,应将最后一个节点排除在外。
(3)重复上面的过程,逐步从根节点取出最大值,放入数组的后面,再对剩下的节点重新构造成堆,直到只剩下一个节点,即可得到有序的数据。
根据以上描述过程,将图1_e所示的堆进行排序,如下图2所示。具体过程如下:
(1)从a图的根节点选择,将其与最后一个节点8交换,得到b图。
(2)根据b图重新构成堆,得到c图。
(3)重复上面的步骤,将根节点与最后一个节点(节点7)交换,得到d图,再将剩下的节点重新构成堆。就这样不断重复,最后可得到排序后的数据。
图2堆排序
三、算法实现
1、堆排序法
/**
* 构成堆
* */
void HeapAdjust(int a[], int s, int n)
{
int j, t;
while (2*s+1 < n) { //第s个节点有子树
j = s*2 +1;
if ((j+1) <n) {
if (a[j] < a[j+1]) //如果左子树小于右子树,则需要比较右子树
j++;
}
if (a[s] < a[j]) { //比较s节点和子树的数据大小,如果小于子树,则交换
t = a[s];
a[s] = a[j];
a[j] = t;
s = j; //堆被破坏,重新调整
} else {
break;
}
}
}
/**
* 堆排序
* */
void HeapSort(int a[], int n)
{
int t, i;
for (i=n/2-1; i>=0; i--) //将a[0]至a[n-1]构成堆
HeapAdjust(a, i, n);
for (i=n-1; i>0; i--) { //取根节点与末节点进行交换
t = a[0]; //与第i个记录进行交换
a[0] = a[i];
a[i] = t;
HeapAdjust(a, 0, i); //将a[0]至a[i]重新调整为堆
}
}
2、堆排序法测试
#include <stdio.h>
#include <stdlib.h>
#include "HeapSort.c"
#define ARRAYLEN 8
void ShowData(int arr[], int n)
{
int i;
for (i=0; i<n; i++)
printf("%d ", arr[i]);
printf("\n");
return;
}
int main(int argc, char *argv[])
{
int i;
int a[ARRAYLEN] = {69, 65, 90, 37, 92, 6, 28, 54};
printf("原数据:");
ShowData(a, ARRAYLEN);
HeapSort(a, ARRAYLEN);
printf("排序后:");
ShowData(a, ARRAYLEN);
return 0;
}
运行结果: