Python数据结构与算法 算法基础5

文章详细介绍了堆排序的概念,包括大根堆和小根堆的定义,以及堆排序的过程,强调了时间复杂度为O(nlogn)。堆排序通过建立堆、获取最大(或最小)元素、调整堆来实现排序。此外,文章还讨论了如何利用大根堆解决TopK问题,给出了解决方案及其时间复杂度分析。
摘要由CSDN通过智能技术生成

本节之前,需要了解完全二叉树的概念

堆排序:

        堆是特殊的完全二叉树,分为大根堆与小根堆

                大根堆:满足任意节点都比其孩子节点大

            

                小根堆:满足任意节点都比其孩子节点小

           

         堆的排序过程如下:

                1.建立堆;

                2.得到堆顶元素为最大元素;

                3.去掉堆顶,将最后一个元素放到堆顶,此时通过一次调整,重新使堆有序;

                4.堆顶元素为第二大元素;

                5.重复步骤3,直到堆排空。

        在开始之前,我们要先明白向下调整是怎样完成的,即某一节点与其左右子节点较大的数作比,若子节点较大,则交换,否则不变。

        时间复杂度为O(nlogn)

        建立堆时,从最后一个非叶子节点向根节点进行向下调整

        

def sift(li, low, high):    # 向下调整函数
    """

    :param li: 传入列表
    :param low: 树的根节点
    :param high: 树的最后一个节点位置
    :return:
    """

    empty = li[low] # 将根节点空出
    i = low     # i用来记录空出的位置
    j = 2 * i + 1 # i的左孩子记作j
    while j <= high :
        if j + 1 <= high and li[j + 1] > li[j] : # 如果右孩子存在并且比左孩子大
            j = j + 1   # 指向右孩子
        if empty < li[j] :  # 某孩子比empty大
            li[i] = li[j]   # 将该位置的数放入空位置
            i = j           # 将空位置更新
            j = 2 * i + 1   # j指向下一层,寻找empty元素应在的位置
        else :  # 左右孩子都不如empty大
            break   # 当前i的位置就是empty应在的位置
    li[i] = empty

def heap_sort(li) :
    # 建堆
    l = len(li)
    for i in range((l - 2) // 2, -1, -1):
        # i表示向下调整时,子树根节点坐标
        sift(li, i, l - 1)
    # 建堆完成
    # for i in range(0, l - 1):
    #     li[0], li[l - 1 - i] = li[l - 1 - i], li[0]
    #     sift(li, 0, l - 2 -i)
    for i in range(l - 1, -1, -1):  # 用i记录树的最后一个叶子节点
        li[0], li[i] = li[i], li[0] # 交换过后,li[i]不再是堆内元素
        print(i)
        print(li)
        sift(li, 0, i-1)
        print(li)


    return li

s = [2, 5, 1, 6, 7, 3, 9, 8, 4]
print(heap_sort(s))
8
[4, 8, 3, 6, 7, 2, 1, 5, 9]
[8, 7, 3, 6, 4, 2, 1, 5, 9]
7
[5, 7, 3, 6, 4, 2, 1, 8, 9]
[7, 6, 3, 5, 4, 2, 1, 8, 9]
6
[1, 6, 3, 5, 4, 2, 7, 8, 9]
[6, 5, 3, 1, 4, 2, 7, 8, 9]
5
[2, 5, 3, 1, 4, 6, 7, 8, 9]
[5, 4, 3, 1, 2, 6, 7, 8, 9]
4
[2, 4, 3, 1, 5, 6, 7, 8, 9]
[4, 2, 3, 1, 5, 6, 7, 8, 9]
3
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[3, 2, 1, 4, 5, 6, 7, 8, 9]
2
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[2, 1, 3, 4, 5, 6, 7, 8, 9]
1
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
0
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]
[1, 2, 3, 4, 5, 6, 7, 8, 9]

应用:topk问题

        给定m个数,找到其中最小的k个数

                1、建立一个元素个数为k的大根堆,因为大根堆中,根节点元素一定最大

                2、将剩余的数与根节点比较,若小于根节点则替换,并向下调整一次,保证根节点最大

                3、将大根堆排序

        代码的时间复杂度为O(mlogk) : 遍历(m - k)个数,其中每次复杂度是logk

def topk(li, k):
    tli = li[0: k]  # 先取出前k个数
    for i in range((k - 2) // 2, -1, -1) :
        sift(tli, i, k-1)    # 将tli从最后一个非叶子节点向根节点逐步向下调整为大根堆
    for i in range(k, len(li) - 1) :    # 将li中剩余的元素依次与当前根节点作比
        if li[i] < tli[0] :
            tli[0] = li[i]
            sift(tli, 0, k-1)     # 保证当前根节点是最大元素
    for i in range(k - 1, -1, -1) :
        tli[0], tli[i] = tli[i], tli[0]
        sift(tli, 0, i-1)
    return tli

import random
li = list(range(50))
random.shuffle(li)    # 打乱li
print(li)
print(topk(li, 10))
[42, 18, 48, 45, 23, 2, 35, 25, 24, 43, 8, 36, 16, 39, 12, 13, 32, 30, 20, 31, 14, 34, 41, 0, 15, 44, 29, 33, 22, 1, 6, 49, 7, 46, 37, 5, 10, 28, 11, 17, 47, 9, 27, 26, 3, 4, 38, 40, 19, 21]
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值