排序算法(六) 堆排序
2017-11-19 16:49
204 查看
一、什么是堆排序?
堆排序也是一种选择类排序,但它选择元素的方式和简单选择排序大不相同。
它将待排序数组看成一个完全二叉树,并通过建立大根堆或小根堆的算法使之成为一棵特殊的完全二叉树。为什么说其特殊呢? 用大根堆完全二叉树来说,任何一个结点的值大于等于它的左右子树的根的值。而小根堆完全二叉树恰好相反,任何一个结点的值小于等于它的左右子树的根的值。
举个栗子,大家就明白了 :
二、而堆排序呢(以大根堆排序为例),就是:
<1>、先将初始序列建成一个大根堆。
<2>、抽取大根堆的堆顶与最后一个元素进行交换,此时认为最后一个元素是递增有序的,而前面的所有元素都是无序的。
<3>、然后再将剩余所有无序的元素再弄成一个大根堆。
<4>、再将堆顶元素与倒数第二个元素进行交换,此时认为最后两个元素是递增有序的,而前面的所有元素都是无序的。
<5>、之后再将剩余所有无序的元素弄成一个大根堆......直到剩余无序的元素就剩下最后一个时,堆排序完成,从第一个元素先后遍历就是一个递增序列。
三、上述算法中,在堆顶元素与后面元素交换后,需要将剩余的无序元素调整为一个大根堆,那怎样将一个序列调整为一个大根堆呢?
<1>、首先将与堆对应的完全二叉树的堆顶元素挪出来另行保存,期待找到合适的位置将其放置进去。该堆顶元素称之为待调整元素。
<2>、然后,堆顶元素位置空置,从空置结点的左右子树中选一个关键字值比较大的结点,如果该值大于待调整元素,则将该值上移至空置结点中。
<3>、此时,原来那个关键字较大的子结点相当于一个空置结点,然后从该空置结点的左右子树中选出一个关键字较大的结点,如果该结点值仍大于待调整元素的值,则将该结点上移至空置结点中。
<4>、重复上面的过程,直到空置结点的左右子树的关键字值都小于待调整元素的值。此时,将待调整记录放入空置结点即可。
我们以一个实例来观察一下具体的过程:
四、源代码实现:
运行截图:
堆排序也是一种选择类排序,但它选择元素的方式和简单选择排序大不相同。
它将待排序数组看成一个完全二叉树,并通过建立大根堆或小根堆的算法使之成为一棵特殊的完全二叉树。为什么说其特殊呢? 用大根堆完全二叉树来说,任何一个结点的值大于等于它的左右子树的根的值。而小根堆完全二叉树恰好相反,任何一个结点的值小于等于它的左右子树的根的值。
举个栗子,大家就明白了 :
二、而堆排序呢(以大根堆排序为例),就是:
<1>、先将初始序列建成一个大根堆。
<2>、抽取大根堆的堆顶与最后一个元素进行交换,此时认为最后一个元素是递增有序的,而前面的所有元素都是无序的。
<3>、然后再将剩余所有无序的元素再弄成一个大根堆。
<4>、再将堆顶元素与倒数第二个元素进行交换,此时认为最后两个元素是递增有序的,而前面的所有元素都是无序的。
<5>、之后再将剩余所有无序的元素弄成一个大根堆......直到剩余无序的元素就剩下最后一个时,堆排序完成,从第一个元素先后遍历就是一个递增序列。
三、上述算法中,在堆顶元素与后面元素交换后,需要将剩余的无序元素调整为一个大根堆,那怎样将一个序列调整为一个大根堆呢?
<1>、首先将与堆对应的完全二叉树的堆顶元素挪出来另行保存,期待找到合适的位置将其放置进去。该堆顶元素称之为待调整元素。
<2>、然后,堆顶元素位置空置,从空置结点的左右子树中选一个关键字值比较大的结点,如果该值大于待调整元素,则将该值上移至空置结点中。
<3>、此时,原来那个关键字较大的子结点相当于一个空置结点,然后从该空置结点的左右子树中选出一个关键字较大的结点,如果该结点值仍大于待调整元素的值,则将该结点上移至空置结点中。
<4>、重复上面的过程,直到空置结点的左右子树的关键字值都小于待调整元素的值。此时,将待调整记录放入空置结点即可。
我们以一个实例来观察一下具体的过程:
四、源代码实现:
/* **函数功能 : record[k .. m]是一棵以record[k]为根结点的完全二叉树,而且 ** 分别以record[2k]和record[2k+1]为根的左右子树为大根堆,调整 ** record[k],使整个序列record[k .. m]满足大根堆的性质 **参数说明 : **@record : 完全二叉树数组 **@k : 待调整为大根堆的完全二叉树的根的数组下标 **@m : 待调整为大根堆的完全二叉树的最后一个元素的下标 **返回值 : 无 */ void AdjustHeap(int record[], int k, int m) { int i, j; int finished = 0; //已完成标志,初始为0 int temp = record[k]; //将根暂时保存 i = k; //当前空出的位置下标 j = 2 * i; //当前空出的位置的左右子树根结点值较大的结点的下标,初始为左子树的根 while (j <= m && !finished) { //用j表示挑选出来的左右子树根结点值较大的结点的下标 if (j < m && record[j] < record[j + 1]) { j = j + 1; } if (temp >= record[j]) { //筛选完毕,因为temp比record[j] 和 record[j+1]都大。 //已满足大根堆性质,此时i下标就是放置record[k]的恰当位置 finished = 1; } else { record[i] = record[j]; //将较大结点元素上移 i = j; j = 2 * i; //继续筛选 } } //将record[k]填入恰当的位置,调整完成。 record[i] = temp; } /* **函数功能 : 建立初始大根堆 **参数说明 : **@record : 待排序数组 **@len : 待排序数组元素的最大下标,即数组长度为len+1, ** 但下标0所对应空间不使用,所以len也是数组元素个数。 **返回值 : 无 */ void CreateBigHeap(int record[], int len) { int i; /* 根据完全二叉树的性质 : length/2 是完全二叉树的最后一个非叶结点, ** 从它开始调用 "调整堆函数(AdjustHeap)",因为叶结点(单元素)可以看做一 ** 个已经调整好的大根堆。自底向上逐层把所有子树都调整为大根堆,直到将 ** 整个完全二叉树调整为大根堆 */ for (i = len / 2; i >= 1; i--) { AdjustHeap(record, i, len); } } /* **函数功能 : 堆排序法升序排序待排序列 **参数说明 : **@record : 待排序列数组(数组元素下标从1开始) **@len : 待排序数组元素的最大下标,即数组长度为len+1, ** 但下标0所对应空间不使用,所以len也是数组元素个数。 **返回值: 无 */ void HeapSort(int record[], int len) { int i; CreateBigHeap(record, len); //建成初始大根堆 //i指的是当前大根堆的堆尾下标, 当堆尾下标为1时,即堆排序结束 for (i = len; i >= 2; i--) { //将大根堆的堆顶元素(堆顶下标为1)与堆尾元素互换 int temp = record[1]; record[1] = record[i]; record[i] = temp; //对 record[1] -- record[i-1]之间的元素进行调整,使之成为大根堆 //此时,i之后的所有节点(包括i本身)是递增有序的。 AdjustHeap(record, 1, i - 1); } }
int main(void) { //数组元素下标从1开始,为了方便使用完全二叉树的性质,0下标弃之不用。 int record[] = { 0,46,55,13,42,94,17,05,70 }; int len = sizeof(record) / sizeof(record[0]); int i = 0; printf("堆排序前: \n"); for (i = 1;i < len;i++) { printf("%d ", record[i]); } puts(""); HeapSort(record, len - 1); printf("堆排序后: \n"); for (i = 1;i < len;i++) { printf("%d ", record[i]); } puts(""); return 0; }
运行截图:
相关文章推荐
- 堆排序 一个综合了插入排序和二路归并特点的排序算法(未测试)
- Java排序算法总结之堆排序
- 排序算法--堆排序
- 排序算法之(7)——堆排序
- 排序算法--堆排序
- 改进排序算法:堆排序(对简单选择排序的改进)
- 16 - 12 - 17 十大排序算法总结(二) 之 桶排序,堆排序
- 八种常见排序算法:插入、冒泡、选择、希尔、归并、快排、堆排序、基数排序
- 排序算法:堆排序
- 【算法分析】排序算法:希尔、归并、快速、堆排序
- 排序算法-堆排序
- 常见的五类排序算法图解和实现(选择类:简单选择排序,锦标赛排序,树形选择排序,堆排序)
- 排序算法之堆排序详解(附最大堆示例代码)
- 排序算法6——堆排序
- 排序算法(2)——堆排序
- 排序算法——堆排序
- 一.排序算法大全之堆排序
- 【图解算法】排序算法——堆排序
- 排序算法之堆排序
- 三种改进型排序算法-快速排序,堆排序,希尔排序