堆的定义
什么是堆呢?书里是这样说的:完全二叉树中所有非终端结点的值均不大于(或不小于)其左右孩子结点的值。堆可以有两种情况,直接看下面例子吧:
96
/ /
83 27
/ / /
38 11 09
{96, 83, 27, 38, 11, 09} 如上图所示,所有非终端结点值都大于左右孩子的值,这个为堆顶元素取最大值;
12
/ /
36 24
/ / / /
85 47 30 53
/
91
{12, 36, 24, 85, 47, 30, 53, 91}如上图所示,所有非终端结点值都小于左右孩子的值,这个为堆顶元素取最小值;
堆排序
搞清楚什么是堆了吧。。呵呵,那什么是堆排序呢?
若在输出堆顶的最小值后,使剩余n-1个元素又重建一个堆,则得到n个元素的次小值,如此反复执行,即可得到一个有序序列,这个就叫堆排序。(这里讲的是堆顶元素取最小值,也可以是堆顶元素取最大值,后来贴出来的代码就是做的大顶堆)
从上面定义我们可以知道,堆排序分两步:1)使无序的数列建成一个堆; 2)在输出堆顶元素后,调整剩余元素建成一个新的堆;第2步循环即可得到最后的有序序列。
(1) 建堆
废话不多讲了,书上都有,其实上面1 2步都是一个“筛选”的过程;
看看如何建堆吧,直接看例子,通俗易懂:
{49, 38, 65, 49, 76, 13, 27, 97},我们建一个“大顶堆”吧,什么是大顶堆,就是我们说的堆顶元素取大值;
我们把这个数列看成是一个完全二叉树:
49
/ /
38 65
/ / / /
49 76 13 27
/
97
第1步,如上图,第一个非终端结点(什么是非终端结点,就是有儿子的)下标是(n/2 -1),这里是49,其下标是3,这个不用解释了吧。。;筛选就从这个元素开始,将49和它的儿子比较大小,由于49>97,所以交换成下图所示;
49
/ /
38 65
/ / / /
97 76 13 27
/
49
第2步,如上图,第二个非终端结点是65(为什么?汗。。最后一个n/2 -1,倒数第二个就再减1),其下标是2。将65和它的左右儿子比较大小,由于65都大于13和27,所以不交换;
49
/ /
38 65
/ / / /
97 76 13 27
/
49
第3步,如上图,第三个非终端结点是38,其下标是1。这里38都小于97和76,将97与38交换位置,即大的数往堆顶放;这样就完了吗?没有,因为原来97位置上现在如果放38的话,明显38<49,按照堆得定义,就不是堆了,我们还需要继续拿38和原来97的儿子比较大小,筛选完后如下图所示:
49
/ /
97 65
/ / / /
49 76 13 27
/
38
第4步,如上图,第四个非终端结点是49,下标为0,这也是最后一个非终端结点啦,这次调整完后,即堆就建好啦。同理,49小于97, 将49与97调整位置;而49又小于76,将49与76交换一下,这样就大功告成了,看看下图所示,是不是跟堆的定义一样。哈哈,是不是很简单
97
/ /
76 65
/ / / /
49 49 13 27
/
38
(2)输出堆顶元素,重新调整
堆已经建好了,由图可知,我们已经找到最大的元素。我们主要把最大的元素97“拿走”(呵呵 不知道怎么表达好),然后用最后一个元素38替代。这时仅需自上而下调整即可,如下:
38
/ /
76 65
/ / / /
49 49 13 27
如上图,将因为38<76,因此,交换一下位置,又因为38<49,故继续交换位置,调整后,如下图:
76
/ /
49 65
/ / / /
38 49 13 27
看到了吧,第二大的元素已经找到了76;
同理,“拿走”76,用最后一个元素27替代,继续调整,如此循环,直至排序完成。
呵呵 原理讲完了,下面贴上代码:
#define SWAP(a, b) /
do {typeof(a) c = (a); (a) = (b); (b) = c;} while(0)
/****************************************************************
Function: sort_HeapAdjust
Date: 2010-06-14
Author: Robbie
Description: 建一个大顶堆,筛选过程
Input: INT *piData 待调整的数列
INT iParent 非终端结点
UINT uiNum 数列个数
Output: None
Return: None
Modify:
*****************************************************************/
VOID sort_HeapAdjust(IN INT *piData, IN INT iParent, IN UINT uiNum)
{
INT i;
INT iTmp = piData[iParent]; /* 非终端结点值 */
for (i = 2*(iParent + 1); i < uiNum; i = (2 * (i + 1))) /* 向下筛选 */
{
if (piData[i - 1] > piData[i]) /* 下标i为数较大的下标 */
{
i--;
}
if (iTmp > piData[i]) /* 与非终端结点值比较, 如果非终端结点值大,则退出循环 */
{
break;
}
piData[iParent] = piData[i];
iParent = i;
}
piData[iParent] = iTmp;
}
/****************************************************************
Function: SORT_Heap
Date: 2010-06-14
Author: Robbie
Description: 堆排序
Input: INT *piData 待排序的数列
UINT uiNum 数列个数
Output: None
Return: None
Modify:
*****************************************************************/
VOID SORT_Heap(IN INT *piData, IN UINT uiNum)
{
INT i;
for (i = (uiNum/2 - 1); i >= 0; i--) /* 将数列建成一个大顶堆 */
{
sort_HeapAdjust(piData, i, uiNum);
}
for (i = uiNum - 1; i > 0; i--)
{
SWAP(piData[0], piData[i]); /* 将最大的数放在最后,这样找到最大的数 */
sort_HeapAdjust(piData, 0, i - 1); /* 继续将数列重新调整为大顶堆 */
}
}
堆排序对数据较少不值得提倡,但对n较大的数列还是很有效的。因为其运行时间主要耗费在建初始堆和调整建新堆时进行的反复“筛选”上。堆排序在最坏的情况下,其时间复杂度为O(nlogn)。相对快速排序来说,这是堆排序的最大优点。此外,堆排序仅需要一个纪录大小供交换用的辅助存储空间。
最后,堆排序也是不稳定排序。