NB排序算法 - 堆排序

NB排序算法 - 堆排序

Created: March 9, 2022 8:11 PM
Introduction: 介绍堆排序
Source: 原创
Tags: 算法专栏


堆排序 - 什么是堆

堆:一种特殊的完全二叉树结构,其分为以下两种:

  • 大根堆:一棵完全二叉树,满足任意一节点都比其孩子节点大
  • 小根堆:一棵完全二叉树,满足任意一节点都比其孩子节点小

如图所示:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5RpI10fZ-1647415683892)(NB%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%20-%20%E5%A0%86%202d06c/Untitled.png)]

堆的向下调整

假设:节点的左右子树都是堆,但自身不是堆,即下图这种情况:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8Qm4QMay-1647415683898)(NB%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%20-%20%E5%A0%86%202d06c/Untitled%201.png)]

请问这个是堆吗? 不是,原因就在于不满足大小堆的条件,其根节点比其孩子节点小,其余节点皆比其孩子节点大

此时就可以通过向下调整来将其变换成一个堆:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1pD7dxr0-1647415683901)(NB%E6%8E%92%E5%BA%8F%E7%AE%97%E6%B3%95%20-%20%E5%A0%86%202d06c/Untitled%202.png)]

堆排序的过程

  1. 建立堆;
  2. 得到堆顶元素,为最大元素;
  3. 去掉堆顶,将堆最后一个元素放在堆顶,此时可通过一次调整重新使堆有序;
  4. 堆顶元素为第二大元素;
  5. 重复步骤3,直到堆变空。

向下调整方法:

def sift(li,low,high):
    """
    :param li: 列表
    :param low: 堆的根节点位置
    :param high: 堆的最后一个元素的位置
    :return:
    """
    i = low             # i最开始指向根节点
    j = 2 * i + 1       # j开始是左孩子
    tmp = li[low]       # 把堆顶存起来
    while j <= high:    # 只要j位置有数
        if j + 1 <= high and li[j+1] > li[j]: # 如果右孩子有,比较左右孩子,如果右孩子比较大
            j += 1      # j指向右孩子
        if li[j] > tmp:
            li[i] = li[j]
            i = j       # 往下看一层
            j = 2 * i + 1
        else:           # tmp更大,把tmp放到i的位置上
            li[i] = tmp # 把tmp放到合适的位置
            break
    else:
        li[i] = tmp     # 当j位置比high大时,把tmp放在叶子节点上

堆排序方法:

def heap_sort(li):
    # 1、构造堆
    n = len(li)
    for i in range((n-1-1) // 2,-1,-1):
        # i表示建堆的时候调整的部分的根的下标
        sift(li,i,n)
    # 建堆完成了

    # 挨个出数
    for i in range(n-1,-1,-1):
        # i指向当前堆的最后一次元素
        li[0],li[i] = li[i],li[0]
        sift(li,0,i-1)  # i-1是新的high

堆排序的效率

  • 向下调整过程时间复杂度: O ( l o g n ) O(logn) O(logn)
  • 整个堆排序时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn)

Python内置堆排序模块

python内置好了堆排序模块,要使用只需导入heapq模块即可

import heapq

如果利用其进行堆排序呢?

# 建堆(默认建小根堆)
heapq.heapify(li)

# 弹出堆元素
for i in range(len(li)):
	print(heapq.heappop(li),end=',')

堆排序 - topk问题

问题描述:现有n个数,设计算法得到前k大的数。(k<n)

解决思路:

  • 排序后切片: O ( n l o g n ) O(nlogn) O(nlogn)
  • 排序LowB三人组: O ( k n ) O(kn) O(kn)

如果只需要求前k大的数,其实用LowB排序更好,排序k趟,不用像堆排序一样先全部排好序再取前k大的数。

更好的方法:堆排序思路 O ( n l o g k ) O(nlogk) O(nlogk)

  1. 取列表前k个元素建立一个小根堆。堆顶就是目前第k大的数。
  2. 依次向后遍历原列表,对于列表中的元素,如果小于堆顶,则忽略该元素;如果大于堆顶,则将堆顶更换为该元素,并且对堆进行一次调整;
  3. 遍历列表所有元素后,倒序弹出堆顶。
def topk(li,k):
    # 1、建堆
    heap = li[0:k]
    for i in range((k-2)//2,-1,-1):
        sift(heap,i,k-1)
    # 2、遍历
    for i in range(k,len(li)):
        if li[i] > heap[0]:
            heap[0] = li[i]
            sift(heap,0,k-1)
    # 3、重新调整出数
    for i in range(k-1,-1,-1):
        heap[0],heap[i] = heap[i],heap[0]
        sift(heap,0,i-1)
    return heap

相关参考

清华大学博士讲解Python数据结构与算法:https://www.bilibili.com/video/BV1uA411N7c5?p=21

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

YuZou 邹宇

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

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

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

打赏作者

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

抵扣说明:

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

余额充值