算法简介
1991年的计算机先驱奖获得者、斯坦福大学计算机科学系教授罗伯特·弗洛伊德(Robert W.Floyd)和威廉姆斯(J.Williams)在1964年共同发明了著名的堆排序算法(Heap Sort )
堆的理解
在了解堆排序算法的原理之前,我们必须先来了解一下什么是堆?堆就是一个近似的完全的二叉树。那什么又是完全二叉树呢?完全二叉树的定义如下:
若设二叉树的高度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,第 h 层从右向左连续缺若干结点,这就是完全二叉树。
完全二叉树图示:

由上面这幅图可看出完全二叉树的特点:最后一层从左向右连续缺失节点。
数组化堆
堆就是将数组表示为完全二叉树的形式,那么如何把数组转换成堆呢?接下来以一个图演示数组转换成堆。假设有一个整型数组:{5,4,8,3,2,1}

大根堆和小根堆
了解完堆的概念之后,我们还需要知道大根堆:大根堆中就是每个父节点的数据大于子节点中的数据。小根堆则相反,每个父节点的数据小于子节点。下面就是一个大根堆跟一个小根堆

基本思想
堆排序的原理就是每次将未排序数组构建成一个大根堆或者是一个小根堆,得到根节点便是未排序数组中的最大或者是最小值,然后将根节点即数组中第一个数据跟未排序数组中的最后一个元素即进行交换。然后将未排序数组的数量减一,再次构建大根堆或者小根堆。以此类推,直到未排序数组剩下一个元素。下面以大根堆为例:

按照上图的做法不断循环直到最后未排序数组中剩下一个元素,排序完成。

代码实现
#include <stdio.h>
/**
前提:元素i之后的元素构成大根堆
目的:令元素i包括进来以后仍为大根堆
array:待调整堆数组
i:待调整数组元素位置
nlength:数组长度
*/
void HeapAdjust(int array[], int i, int nLength)
{
int nChild;
int nTemp;
//防止子结点比父结点大出现换位情况可能会导致子结点处不构成大根堆
//循环调整以当前子结点的为父结点的堆
for (; 2*i+1 < nLength; i = nChild)
{
//找到子结点
nChild = 2*i + 1;
//得到较大子结点
if (nChild < nLength-1 && array[nChild+1] > array[nChild])
{
++nChild;
}
//比较较大子结点和父结点大小,子结点较大就上移
if (array[i] < array[nChild])
{
nTemp = array[i];
array[i] = array[nChild];
array[nChild] = nTemp;
}
else
{
break;
}
}
}
//堆排序
void HeapSort(int array[], int length)
{
int i;
//从最后一个非叶结点开始,构建大根堆
for (i = length/2-1; i >= 0; --i)
{
HeapAdjust(array, i, length);
}
//从最后一个元素开始,将最大的元素和最后一个元素交换,并调整。
//循环操作
for (i = length - 1; i > 0; --i)
{
//三个异或操作实现元素交换
array[i] = array[0] ^ array[i];
array[0] = array[0] ^ array[i];
array[i] = array[0] ^ array[i];
HeapAdjust(array, 0, i);
}
}
int main()
{
int i;
int num[] = { 9,8,7,6,5,4,3,2,1,0 };
HeapSort(num, sizeof(num)/sizeof(int));
for (i = 0; i < sizeof(num)/sizeof(int); i++)
{
printf("%d ", num[i]);
}
printf("\nok\n");
return 0;
}
运行结果:
复杂度分析
关于堆排序复杂度计算,大家可以参考:堆排序算法复杂度解析
原文地址:堆排序算法
刚玩博客,如果有什么错误之处或建议,请在评论区告诉我,谢谢!