堆排序稳定性举例_6. 堆排序 HeapSort

06dac80660f860d1ad7060af467f4494.gif

        堆排序是一种选择排序。

        选择排序:每趟从待排序的记录中选出关键字最小的记录,顺序放在已排序的记录序列末尾,直到全部排序结束为止。

1. 算法思想

        堆排序是利用堆的性质进行的一种选择排序。

        动态效果示意图:https://upload-images.jianshu.io/upload_images/24359221-9e683513d6e330bc.gif?imageMogr2/auto-orient/strip

        堆是一棵顺序存储的完全二叉树。

        其中每个结点的关键字都不大于其孩子结点的关键字,这样的堆称为小根堆。

        其中每个结点的关键字都不小于其孩子结点的关键字,这样的堆称为大根堆。

        举例来说,对于n个元素的序列{R0, R1, ... , Rn}当且仅当满足下列关系之一时,称之为堆:

        Ri <= R2i+1 且 Ri <= R2i+2 (小根堆)

        Ri >= R2i+1 且 Ri >= R2i+2 (大根堆)

        其中i=1,2,…,n/2向下取整;

065dcefa48abdc0db32955ec35ac37d6.png

        如上图所示,序列R{3, 8, 15, 31, 25}是一个典型的小根堆。

        堆中有两个结点,元素3和元素8。

        元素3在数组中以R[0]表示,它的左孩子结点是R[1],右孩子结点是R[2]。

        元素8在数组中以R[1]表示,它的左孩子结点是R[3],右孩子结点是R[4],它的父结点是R[0]。可以看出,它们满足以下规律:

    设当前元素在数组中以R[i]表示,那么,

        (1) 它的左孩子结点是:R[2*i+1];

        (2) 它的右孩子结点是:R[2*i+2];

        (3) 它的父结点是:R[(i-1)/2];

        (4) R[i] <= R[2*i+1] 且 R[i] <= R[2i+2]。

        首先,按堆的定义将数组R[0..n]调整为堆(这个过程称为创建初始堆),交换R[0]和R[n];

        然后,将R[0..n-1]调整为堆,交换R[0]和R[n-1];

        如此反复,直到交换了R[0]和R[1]为止。

        以上思想可归纳为两个操作:

        (1)根据初始数组去构造初始堆(构建一个完全二叉树,保证所有的父结点都比它的孩子结点数值大)。

        (2)每次交换第一个和最后一个元素,输出最后一个元素(最大值),然后把剩下元素重新调整为大根堆。

        当输出完最后一个元素后,这个数组已经是按照从小到大的顺序排列了。

        先通过详细的实例图来看一下,如何构建初始堆。

        设有一个无序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 }。

88a3dddd2d647e58e60f991dd0e8c1fb.png

        构造了初始堆后,我们来看一下完整的堆排序处理:

        还是针对前面提到的无序序列 { 1, 3, 4, 5, 2, 6, 9, 7, 8, 0 } 来加以说明。

e4ca0ad29b67fda4beebade74183309f.png

         比如如下数组 {57, 40, 38, 11, 13, 34, 48, 75, 6, 19, 9, 7}堆排序前如下:

89fd12e6c7d9fc437bee5e3a2a3e127b.png

进行堆排序后如下:

ca47b7d7105b683abfef4c1bdba7dc40.png

        最大堆的存储结构如下:

3ffb2e1e1cf3e53460980de4092e8078.png

        接着,最后一步,堆排序,进行(n-1)次循环。

4a20cd48a35147d43906c0385b5e66fb.png

99549fb6121b8c896f69ed97511fabdf.png

b7cf4d8d2e0ebe780ba844e2295f276e.png

667339dc42ae4a0ab4dda870c9916344.png

         相信,通过以上两幅图,应该能很直观的演示堆排序的操作处理。 

        看完上面所述的流程你至少有一个疑问:

    如何确定最后一个非叶子结点?

        其实这是有一个公式的,设二叉树结点总数为 n,则最后一个非叶子结点是第⌊n/2⌋个。

2. 代码+结果:

9c213c2fa02e34cc20b28f036bf286fd.png

3ffb5485faf67f0df63574f407e48eee.png

3. 算法分析

    3.1 堆排序算法的总体情况

d0e144738a134875219362cc1490f274.png

    3.2 时间复杂度

        首先计算建堆的时间,也就是下面的代码,

// 循环建立初始堆

for (int i = length / 2; i >= 0; i--){

HeapAdjust(list, i, length);

}

        n 个结点,从第 0 层至第logn层。对于第 i 层的2i个点如果需要往下走logn−i步,那么把走的所有步相加得:

c1d66232755ed1a6c1393ea0c983211a.png

        接下来就是排序的时间,即下面的代码:

// 进行n-1次循环,完成排序

for (int i = length - 1; i > 0; i--){

// 最后一个元素和第一元素进行交换

int temp = list[i];

list[i] = list[0];

list[0] = temp;

// 筛选 R[0] 结点,得到i-1个结点的堆

HeapAdjust(list, 0, i);

}

        HeapAdjust() 耗时logn,共 n 次,故排序时间为O(nlogn)。

        堆的存储表示是顺序的。因为堆所对应的二叉树为完全二叉树,而完全二叉树通常采用顺序存储方式。

        当想得到一个序列中第k个最小的元素之前的部分排序序列,最好采用堆排序。

    3.3 算法稳定性

        堆排序是一种不稳定的排序方法。

        因为在堆的调整过程中,关键字进行比较和交换所走的是该结点到叶子结点的一条路径,因此对于相同的关键字就可能出现排在后面的关键字被交换到前面来的情况。 

842df0902ad40840465b7e6e92af45de.png

推荐阅读:

★ 求职经验:点这里

★ 算法刷题:点这里

★ 投资理财:点这里

★ AI很简单:

★ 扫盲科普:点这里

♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠♥◆♣♠

     880c5209d4fa1c67c84e9605cb0d7a6f.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值