Python堆排序

一、什么是堆?

        在学习堆排序之前要弄清什么是堆以及什么是大顶堆和小顶堆。如下图(图片来源于网络,如有侵权请联系删除):

       

        可以理解为堆是一颗完全二叉树,分为大顶堆和小顶堆。图中,其逻辑存储结构中,大根堆的每一个父节点的值都大于左孩子和右孩子节点的值;小根堆的每一个父节点的值都小于左孩子和右孩子节点的值;物理存储结构就是一个序列。

二、堆排序思想

        堆排序过程主要分为两步:1.建堆。2.排序。

        建堆:建堆的过程就是把元素序列调整为一个大顶堆或小顶堆。建堆从最后一个有子节点的节点开始,往前进行调整。

        如果父节点下标为:i,则左孩子下标为i*2+1,右孩子下标为:i*2+2。

        反之一样,如:最后一个节点元素的下标为n-1,则它的父节点应为:(n-1)//2,这个可以自己推出来。

        所以建堆的代码如下:

def build_heap(list_target,n):
    """
    实现建堆
    :param list_target: 待建堆的列表元素
    :param n: 列表的长度
    :return:
    """
    for i in range((n-1)//2,-1,-1):
        heapify(list_target,len(list_target),i)

        以上代码重点关注i的取值,是从最后一个节点n-1的父节点(n-1)//2开始的,所以建堆要倒着来。其中heapify函数是以i所在的节点为当前父节点来调整堆的,其代码如下:

def heapify(list_target,n,i):
    """
     实现对以i为第一个父节点的堆进行调整
    :param list_target:待排序元素列表
    :param n:列表长度
    :param i:待维护元素下标
    :return:
    """
    lchild = i*2+1
    rchild = i*2+2
    max = i
    if i >= n:
        return False
    if lchild <n and list_target[lchild] > list_target[max]:
        max = lchild
    if rchild <n and list_target[rchild] > list_target[max]:
        max = rchild
    if max != i :
        list_target[max],list_target[i] = list_target[i],list_target[max]
        heapify(list_target,n,max)

        把建堆和调整堆分开写,就是一个分而治之的思想。建堆就是从最后一个元素的父节点开始,一直到所有元素的根节点,倒序获取所有元素的根节点传给heapify方法进行调整。heapify方法调整的时候又是从父节点到子节点从上到下调整。这也是我觉得比较难理解的地方,可以多画图体会过程中思想的精妙。

        排序:以大顶堆为例,会发现建堆后最大的元素是L[0],相当于找出了序列的最大值,把它和最后一个元素进行交换,也就是相当于把最大值存到了序列的最后面(通常用列表存储)。交换后会发现堆不再是大顶堆,这是就需要把它调整为大顶堆。

        调整时从L[0]开始,调整的序列不再是先前的整个序列(因为最大值已经放到了最后面),而是慢慢变短,当调整到L[0]时,又变成了大顶堆。

        以上循环下去,一直到待调整的元素个数为0时,就全部排序好了。排序的代码如下:

def heap_sort(list_target,n):
    #1.建堆
    build_heap(list_target,len(list_target))
    #2.排序:第0个和最后1个交换,维护堆性质;第0个和倒数第2个,维护堆性质;第0个和倒数第3个,维护堆性质;......第0个和第1个,维护堆性质。
    for i in range(len(list_target)-1,-1,-1):
        list_target[0],list_target[i] = list_target[i],list_target[0]
        #注意:这里交换之后,最后的元素已经是有序的了,维护堆性质的第二个参数(传入i,从n-1到1,长度慢慢减小到1)
        heapify(list_target,i,0)

        排序是从最后一个元素开始的,先把L[0]和L[i]进行交换,然后再调整堆。这里需要注意的是,用了一个很巧妙的办法,然i不断减小后传入i的值作为下一次调整时列表长度的参数,即作为heapify(list_target,i,0)的第2个参数,列表长度本身并没有减小,而是取的范围变小,相当于障眼法。

        要理解整个排序的动态过程,可以参考网址:

排序算法:堆排序【图解+代码】_哔哩哔哩_bilibili

        思考1:建堆的时候为什么要从最后一个节点的父节点开始?

        答:这是因为大顶堆的大元素都在上面,小元素都在下面,从最后一个节点的父节点开始,可以把大的元素从堆底往上冒,向冒泡一样。

        思考2:排序的时候,调整堆为什么不从最后面一个元素的父节点开始,而是从第0个节点(即根节点)开始?

        答:这里我觉得很巧妙,画图模拟后会发现,交换元素后,只有根顶部的元素发生了变化,最后一个元素去掉了,所以只需要从根节点开始调整。

        总结:建堆的时候,从最后一个元素的父节点开始到根节点,调整很多次;而排序的时候,交换一次,只需调整一次(当然要递归调整下层父节点,如果有的话)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值