浅谈大根堆,小根堆,以及堆排序(python)实现

既然要说堆排序,那么必然要说说什么是大根堆,小根堆了。
大根堆: 若根节点存在左右子节点,那么根节点的值大于或等于左右子节点的值。
小根堆: 若根节点存在左右子节点,那么根节点的值小于或等于左右子节点的值。

通俗来说,那顾名思义,大根堆就是根节点比左右孩子都要大或者等于的二叉树,小根堆就是根节点比左右孩子都要小或者等于的二叉树。

那么我们可以总结出关于大根堆和小根堆的结论:
(1)、堆是一棵完全二叉树;
(2)、小根堆的根节点是堆中最小值,大根堆的根节点是堆中最大值;
(3)、堆适合采用顺序存储。

堆最重要的两个方法就是插入和删除方法。
下面来说说堆的插入方法:
堆的插入算法: 将一个元素插入到堆中,使之依然成为一个堆。
算法描述:先将结点插入到堆的尾部,则从该节点的父节点出发到跟结点就是一个有序序列,那么其实就是将该元素插入到一个有序序列中的操作,类似于简单的插入排序算法。具体步骤是将该节点逐层向上调整,直到依然构成一个堆,调整方法是看每个子树是否符合大(小)根堆的特点,不符合则调整根和叶子节点的位置。
算法步骤:(1)将该元素插入到堆的尾部;(2)如果不满足堆的特性,进行调整,直到所有结点都满足条件。

堆的删除算法: 堆在删除元素时,只可以删除根节点,然后使剩下的结点依然成为一个堆。
算法描述:将根节点删除后用堆尾元素填充,然后调整二叉树,使之依然成为一个堆。
算法步骤:(1)删除根节点,用堆中最后元素进行填充;(2)如果不满足堆的特性,进行调整,直到所有结点都满足条件。

堆的构建过程其实就是构建一个符合大根堆或者小根堆的完全二叉树,那么就可以使用顺序表来进行存储。

下面举例说明堆的构建过程:
将无序序列 [49,38,65,97,76,13,27,40 ]构建成大根堆:
第一步:
第二步:插入结点38
在这里插入图片描述

第三步(1):因为要建立大根堆,49和65不符合大根堆特点,对其进行调整:
在这里插入图片描述

第三步(2):调整后:
在这里插入图片描述

第四步(1):插入结点97,发生冲突,进行调整
在这里插入图片描述
第四步(2):还有冲突,继续调整
在这里插入图片描述
第四步(3):调整后
在这里插入图片描述

第五步(1):插入结点76,发生冲突,进行调整
在这里插入图片描述
第五步(2):调整后

在这里插入图片描述
第六步:插入结点13
在这里插入图片描述
第七步:插入结点27
在这里插入图片描述
第八步(1):插入结点40,发生冲突,进行调整
在这里插入图片描述
第八步(2):调整后
在这里插入图片描述
至此大根堆建立完成。可以看出,根节点是最大值。

下面在此基础上演示插入算法: 向该大根堆中插入结点99:
步骤(1):插入结点99,发生冲突,进行调整
在这里插入图片描述
步骤(2):还有冲突,继续调整
在这里插入图片描述
步骤(3):再进行调整
在这里插入图片描述
步骤(4):调整后
在这里插入图片描述
至此,结点99添加至大根堆中;

下面在此基础上删除结点: 因为堆中删除只能删除根节点,所以删除结点99:
步骤(1):找到堆中最后的结点40
在这里插入图片描述
步骤(2):删除结点99,有堆中的最后元素40填充位置,发生冲突,进行调整,找改位置左右孩子中最大的元素97,进行换位
在这里插入图片描述
步骤(3):还有冲突,继续调整,同上述步骤
在这里插入图片描述
步骤(4):调整后
在这里插入图片描述
至此,结点99删除完成。

那么,堆建立好了,堆排序是怎么实现的呢?
其实,堆排序就是堆的删除过程,每次删除的都是堆的根节点,删除后再进行调整,使得剩下的结点还是堆,然后再删除根节点,重复进行,直到堆中只有一个元素,直接输出,那么删除过程产生的这个序列就是一个有序序列。

同样以上面的例子为例,来看看排序过程:
上面已经删除结点99了:
在这里插入图片描述
继续删除根节点97:
在这里插入图片描述
删除根节点76:
在这里插入图片描述
删除根节点65:
在这里插入图片描述
删除根结点49:
在这里插入图片描述
删除根结点40:
在这里插入图片描述
删除根节点38:
在这里插入图片描述
删除根节点27:
在这里插入图片描述
堆中仅剩一个元素13

所以最后排序后的序列为:[99,97,76,65,49,40,38,27,13],排序完成。

下面使用python实现堆排序:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def heap_sort(ary) :
    n = len(ary)
    first = int(n/2-1)       #最后一个非叶子节点
    for start in range(first,-1,-1) :     #构造大根堆
        max_heapify(ary,start,n-1)
    for end in range(n-1,0,-1):           #堆排,将大根堆转换成有序数组
        ary[end],ary[0] = ary[0],ary[end]
        max_heapify(ary,0,end-1)
    return ary


#最大堆调整:将堆的末端子节点作调整,使得子节点永远小于父节点
#start为当前需要调整最大堆的位置,end为调整边界
def max_heapify(ary,start,end):
    root = start
    while True :
        child = root*2 +1               #调整节点的子节点
        if child > end : break
        if child+1 <= end and ary[child] < ary[child+1] :
            child = child+1             #取较大的子节点
        if ary[root] < ary[child] :     #较大的子节点成为父节点
            ary[root],ary[child] = ary[child],ary[root]     #交换
            root = child
        else :
            break
if __name__ == '__main__':
    print("堆排序:")
    a =  [49,38,65,97,76,13,27,40,99]
    print("排序前:")
    print(a)
    heap_sort(a)
    print("排序后:")
    print(a)

运行结果:

1
2
3
4
5
堆排序:
排序前:
[49, 38, 65, 97, 76, 13, 27, 40, 99]
排序后:
[13, 27, 38, 40, 49, 65, 76, 97, 99]