堆排序

堆的定义

什么是堆呢?书里是这样说的:完全二叉树中所有非终端结点的值均不大于(或不小于)其左右孩子结点的值。堆可以有两种情况,直接看下面例子吧:

 

             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)。相对快速排序来说,这是堆排序的最大优点。此外,堆排序仅需要一个纪录大小供交换用的辅助存储空间。

最后,堆排序也是不稳定排序。

 

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

robbie1314

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值