python数据结构与算法-15_堆与堆排序

堆(heap)

前面我们讲了两种使用分治和递归解决排序问题的归并排序和快速排序,中间又穿插了一把树和二叉树,
本章我们开始介绍另一种有用的数据结构堆(heap), 以及借助堆来实现的堆排序,相比前两种排序算法要稍难实现一些。
最后我们简单提一下 python 标准库内置的 heapq 模块。

什么是堆?

堆是一种完全二叉树(请你回顾下上一章的概念),有最大堆和最小堆两种。

  • 最大堆: 对于每个非叶子节点 V,V 的值都比它的两个孩子大,称为 最大堆特性(heap order property)
    最大堆里的根总是存储最大值,最小的值存储在叶节点。
  • 最小堆:和最大堆相反,每个非叶子节点 V,V 的两个孩子的值都比它大。
    在这里插入图片描述

堆的操作

堆提供了很有限的几个操作:

  • 插入新的值。插入比较麻烦的就是需要维持堆的特性。需要 sift-up 操作,具体会在视频和代码里解释,文字描述起来比较麻烦。
  • 获取并移除根节点的值。每次我们都可以获取最大值或者最小值。这个时候需要把底层最右边的节点值替换到 root 节点之后
    执行 sift-down 操作。

在这里插入图片描述

在这里插入图片描述

堆的表示

上一章我们用一个节点类和二叉树类表示树,这里其实用数组就能实现堆。

在这里插入图片描述

仔细观察下,因为完全二叉树的特性,树不会有间隙。对于数组里的一个下标 i,我们可以得到它的父亲和孩子的节点对应的下标:

parent = int((i-1) / 2)    # 取整
left = 2 * i + 1
right = 2 * i + 2

超出下标表示没有对应的孩子节点。

实现一个最大堆

我们将在视频里详细描述和编写各个操作

class MaxHeap(object):
    def __init__(self, maxsize=None):
        self.maxsize = maxsize
        self._elements = Array(maxsize)
        self._count = 0

    def __len__(self):
        return self._count

    def add(self, value):
        if self._count >= self.maxsize:
            raise Exception('full')
        self._elements[self._count] = value
        self._count += 1
        self._siftup(self._count-1)  # 维持堆的特性

    def _siftup(self, ndx):
        if ndx > 0:
            parent = int((ndx-1)/2)
            if self._elements[ndx] > self._elements[parent]:    # 如果插入的值大于 parent,一直交换
                self._elements[ndx], self._elements[parent] = self._elements[parent], self._elements[ndx]
                self._siftup(parent)    # 递归

    def extract(self):
        if self._count <= 0:
            raise Exception('empty')
        value = self._elements[0]    # 保存 root 值
        self._count -= 1
        self._elements[0] = self._elements[self._count]    # 最右下的节点放到root后siftDown
        self._siftdown(0)    # 维持堆特性
        return value

    def _siftdown(self, ndx):
        left = 2 * ndx + 1
        right = 2 * ndx + 2
        # determine which node contains the larger value
        largest = ndx
        if (left < self._count and     # 有左孩子
                self._elements[left] >= self._elements[largest] and
                self._elements[left] >= self._elements[right]):  # 原书这个地方没写实际上找的未必是largest
            largest = left
        elif right < self._count and self._elements[right] >= self._elements[largest]:
            largest = right
        if largest != ndx:
            self._elements[ndx], self._elements[largest] = self._elements[largest], self._elements[ndx]
            self._siftdown(largest)


def test_maxheap():
    import random
    n = 5
    h = MaxHeap(n)
    for i in range(n):
        h.add(i)
    for i in reversed(range(n)):
        assert i == h.extract()

实现堆排序

上边我们实现了最大堆,每次我们都能 extract 一个最大的元素了,于是一个倒序排序函数就能很容易写出来了:

def heapsort_reverse(array):
    length = len(array)
    maxheap = MaxHeap(length)
    for i in array:
        maxheap.add(i)
    res = []
    for i in range(length):
        res.append(maxheap.extract())
    return res


def test_heapsort_reverse():
    import random
    l = list(range(10))
    random.shuffle(l)
    assert heapsort_reverse(l) == sorted(l, reverse=True)

Python 里的 heapq 模块

python 其实自带了 heapq 模块,用来实现堆的相关操作,原理是类似的。请你阅读相关文档并使用内置的 heapq 模块完成堆排序。
一般我们刷题或者写业务代码的时候,使用这个内置的 heapq 模块就够用了,内置的实现了是最小堆。

Top K 问题

面试题中有这样一类问题,让求出大量数据中的top k 个元素,比如一亿个数字中最大的100个数字。
对于这种问题有很多种解法,比如直接排序、mapreduce、trie 树、分治法等,当然如果内存够用直接排序是最简单的。
如果内存不够用呢? 这里我们提一下使用固定大小的堆来解决这个问题的方式。

一开始的思路可能是,既然求最大的 k 个数,是不是应该维护一个包含 k 个元素的最大堆呢?
稍微尝试下你会发现走不通。我们先用数组的前面 k 个元素建立最大堆,然后对剩下的元素进行比对,但是最大堆只能每次获取堆顶
最大的一个元素,如果我们取下一个大于堆顶的值和堆顶替换,你会发现堆底部的小数一直不会被换掉。如果下一个元素小于堆顶
就替换也不对,这样可能最大的元素就被我们丢掉了。

相反我们用最小堆呢?
先迭代前 k 个元素建立一个最小堆,之后的元素如果小于堆顶最小值,跳过,否则替换堆顶元素并重新调整堆。你会发现最小堆里
慢慢就被替换成了最大的那些值,并且最后堆顶是最大的 topk 个值中的最小值。
(比如1000个数找10个,最后堆里剩余的是 [990, 991, 992, 996, 994, 993, 997, 998, 999, 995],第一个 990 最小)

按照这个思路很容易写出来代码:

import heapq


class TopK:
    """获取大量元素 topk 大个元素,固定内存
    思路:
    1. 先放入元素前 k 个建立一个最小堆
    2. 迭代剩余元素:
        如果当前元素小于堆顶元素,跳过该元素(肯定不是前 k 大)
        否则替换堆顶元素为当前元素,并重新调整堆
    """

    def __init__(self, iterable, k):
        self.minheap = []
        self.capacity = k
        self.iterable = iterable

    def push(self, val):
        if len(self.minheap) >= self.capacity:
            min_val = self.minheap[0]
            if val < min_val:  # 当然你可以直接 if val > min_val操作,这里我只是显示指出跳过这个元素
                pass
            else:
                heapq.heapreplace(self.minheap, val)  # 返回并且pop堆顶最小值,推入新的 val 值并调整堆
        else:
            heapq.heappush(self.minheap, val)  # 前面 k 个元素直接放入minheap

    def get_topk(self):
        for val in self.iterable:
            self.push(val)
        return self.minheap


def test():
    import random
    i = list(range(1000))  # 这里可以是一个可迭代元素,节省内存
    random.shuffle(i)
    _ = TopK(i, 10)
    print(_.get_topk())  # [990, 991, 992, 996, 994, 993, 997, 998, 999, 995]


if __name__ == '__main__':
    test()

源码

# python3
class MinHeap:
    def __init__(self):
        """
        这里提供一个最小堆实现。如果面试不让用内置的堆非让你自己实现的话,考虑用这个简版的最小堆实现。
        一般只需要实现 heqppop,heappush 两个操作就可以应付面试题了
        parent: (i-1)//2。注意这么写 int((n-1)/2), python3 (n-1)//2当n=0结果是-1而不是0
        left:  2*i+1
        right: 2*i+2
        参考:
        https://favtutor.com/blogs/heap-in-python
        https://runestone.academy/ns/books/published/pythonds/Trees/BinaryHeapImplementation.html
        https://www.askpython.com/python/examples/min-heap
        """
        self.pq = []

    def min_heapify(self, nums, k):
        """递归调用,维持最小堆特性"""
        l = 2*k+1  # 左节点位置
        r = 2*k+2  # 右节点
        if l < len(nums) and nums[l] < nums[k]:
            smallest = l
        else:
            smallest = k
        if r < len(nums) and nums[r] < nums[smallest]:
            smallest = r
        if smallest != k:
            nums[k], nums[smallest] = nums[smallest], nums[k]
            self.min_heapify(nums, smallest)

    def heappush(self, num):
        """列表最后就加入一个元素,之后不断循环调用维持堆特性"""
        self.pq.append(num)
        n = len(self.pq) - 1
        # 注意必须加上n>0。因为 python3 (n-1)//2 当n==0 的时候结果是-1而不是0!
        while n > 0 and self.pq[n] < self.pq[(n-1)//2]:  # parent 交换
            self.pq[n], self.pq[(n-1)//2] = self.pq[(n-1)//2], self.pq[n]  # swap
            n = (n-1)//2

    def heqppop(self):  # 取 pq[0],之后和pq最后一个元素pq[-1]交换之后调用 min_heapify(0)
        minval = self.pq[0]
        last = self.pq[-1]
        self.pq[0] = last
        self.min_heapify(self.pq, 0)
        self.pq.pop()
        return minval

    def heapify(self, nums):
        n = int((len(nums)//2)-1)
        for k in range(n, -1, -1):
            self.min_heapify(nums, k)


def test_MinHeqp():
    import random
    l = list(range(1, 9))
    random.shuffle(l)
    pq = MinHeap()
    for num in l:
        pq.heappush(num)
    res = []
    for _ in range(len(l)):
        res.append(pq.heqppop())  # 利用 heqppop,heqppush 实现堆排序

    def issorted(l): return all(l[i] <= l[i+1] for i in range(len(l) - 1))
    assert issorted(res)

练习题

  • 这里我用最大堆实现了一个 heapsort_reverse 函数,请你实现一个正序排序的函数。似乎不止一种方式
  • 请你实现一个最小堆,你需要修改那些代码呢?
  • 我们实现的堆排序是 inplace 的吗,如果不是,你能改成 inplace 的吗?
  • 堆排序的时间复杂度是多少? siftup 和 siftdown 的时间复杂度是多少?(小提示:考虑树的高度,它决定了操作次数)
  • 请你思考 Top K 问题的时间复杂度是多少?

延伸阅读

  • 《算法导论》第 6 章 Heapsort
  • 《Data Structures and Algorithms in Python》 13.5 节 Heapsort
  • 阅读 Python heapq 模块的文档

Leetcode

合并 k 个有序链表 https://leetcode.com/problems/merge-k-sorted-lists/description/

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/180693.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Unity UI设计 软件构造实验报告

实验1: 仿真系统的UI主界面设计 1.实验目的 &#xff08;1&#xff09;熟悉Unity中UI界面的设计与编写&#xff1b; &#xff08;2&#xff09;熟悉UI界面中场景转换,UI与场景内容相互关联的方式。 &#xff08;3&#xff09;熟悉Unity中MySQL数据库的操作 2.实验内容 新建…

分布式锁之基于redis实现分布式锁(二)

2. 基于redis实现分布式锁 2.1. 基本实现 借助于redis中的命令setnx(key, value)&#xff0c;key不存在就新增&#xff0c;存在就什么都不做。同时有多个客户端发送setnx命令&#xff0c;只有一个客户端可以成功&#xff0c;返回1&#xff08;true&#xff09;&#xff1b;其他…

在线视频课程教育系统源码/网课网校/知识付费/在线教育系统/在线课程培训系统源码

源码简介&#xff1a; 在线视频课程教育系统源码&#xff0c;作为网课/网校/知识付费/在线教育系统&#xff0c;它有文章付费阅读在线点播自动发货付费阅读VIP会员系统等功能。它是实用的在线课程培训系统源码。 发货100-在线视频课程教育系统&#xff0c;它是一款功能实用的…

PC8250(CC-CV控制)5V/8A同步降压恒流恒压软启动带EN功能只需极少外围元件

概述 PC8250是一个同步降压转换器输出电流至8A。它的设计允许操作电源电压范围从9V到42V。外部关闭功能可以通过逻辑电平来控制COMP/EN引脚下降&#xff0c;然后进入待机模式。外部补偿使反馈控制具有良好的线路和负载调节&#xff0c;外部设计灵活。PC8250在CC&#xff08;恒定…

【C语言】深入解开指针(四)

&#x1f308;write in front :&#x1f50d;个人主页 &#xff1a; 啊森要自信的主页 ✏️真正相信奇迹的家伙&#xff0c;本身和奇迹一样了不起啊&#xff01; 欢迎大家关注&#x1f50d;点赞&#x1f44d;收藏⭐️留言&#x1f4dd;>希望看完我的文章对你有小小的帮助&am…

基于Pytorch框架多人多摄像头摔倒跌倒坠落检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 深度学习在计算机视觉领域的应用已经取得了显著的进展&#xff0c;特别是在多人多摄像头场景下的摔倒跌倒检测。通过…

OpenSearch开发环境安装Docker和Docker-Compose两种方式

文章目录 简介常用请求创建映射写入数据查询数据其他 安装Docker方式安装OpenSearch安装OpenSearchDashboard Docker-Compose方式Docker-Compose安装1.设置主机环境2.下载docker-compose.yml文件3.启动docker-compose4.验证 问题问题1&#xff1a;IPv4 forwarding is disabled.…

给数据库查询结果添加一个额外的自增编号

1、在mysql数据库可以执行的sql SELECT( i : i 1 ) num,M.* FROMuser M,( SELECT i : 0 ) AS ID GROUP BYM.ID ORDER BYM.create_time SELECT (i :i 1) 是为了生成自增的序列号字段 SELECT i : 0 是为了将i进行初始化每次查询的序列号都会从1开始进行排序生成序列号 在…

docker部署paddleocr

内容仅供参考学习 欢迎朋友们V一起交流&#xff1a; zcxl7_7 环境 1. CentOS7  2. docker  3. PaddleOCR2.5.2 1.准备 1. 首先准备好需要打包的项目 2. 在该项目中创建Dockerfile文件 touch Dockerfile2. 编写Dockerfile # 从Python 3.8的官方镜像中创建(pyt…

快速排序演示和代码介绍

快速排序的核心是(以升序为例)&#xff1a;在待排序的数据中指定一个数做为基准数&#xff0c;把所有小于基准数的数据放到基准数的左边&#xff0c;所有大于基准数的数据放在右边&#xff0c;这样的话基准数的位置就确定了&#xff0c;然后在两边的数据中重复上述操作

5G智慧工地整体解决方案:文件全文115页,附下载

关键词&#xff1a;5G智慧工地&#xff0c;智慧工地建设方案&#xff0c;智慧工地管理平台系统&#xff0c;智慧工地建设调研报告&#xff0c;智慧工地云平台建设 一、5G智慧工地建设背景 5G智慧工地是利用5G技术、物联网、大数据、云计算、AI等信息技术&#xff0c;围绕“人…

SANSAN新鲜事|理清!如何降低数字工厂建设成本

根据调研结果&#xff0c;在中国&#xff0c;只有10%的企业已经完全实施数字化工厂解决方案或目前处于最后阶段。工厂数字化的进展比企业的预期要慢得多&#xff0c;原因包括复杂的系统环境和多样化的机器版图&#xff0c;以及在整个生产网络中推广单个解决方案的相关挑战。实施…

深度学习之基于Tensorflow卷积神经网络鸟类目标识别检测系统

欢迎大家点赞、收藏、关注、评论啦 &#xff0c;由于篇幅有限&#xff0c;只展示了部分核心代码。 文章目录 一项目简介 二、功能三、系统四. 总结 一项目简介 基于Tensorflow的卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;CNN&#xff09;在鸟类目标识…

景区智慧旅游智能化系统方案:PPT全文58页,附下载

关键词&#xff1a;智慧景区解决方案&#xff0c;智慧文旅解决方案&#xff0c;智慧旅游解决方案&#xff0c;智慧文旅综合运营平台 一、景区智慧旅游智能化系统建设背景 近年来&#xff0c;随着信息技术的快速发展和普及&#xff0c;以及旅游市场的不断扩大和升级&#xff0…

Me-and-My-Girlfriend-1

Me-and-My-Girlfriend-1 一、主机发现和端口扫描 主机发现&#xff0c;靶机地址192.168.80.147 arp-scan -l端口扫描&#xff0c;开放了22、80端口 nmap -A -p- -sV 192.168.80.147二、信息收集 访问80端口 路径扫描 dirsearch -u "http://192.168.80.147/" -e * …

用好说 AI 玩转奥特曼表情包,居然还能和他们聊个天

你喜欢奥特曼吗&#xff1f;你相信光吗&#xff1f; 如果你已经追完了特摄剧、刷完了大电影、用滥了那几个表情包&#xff0c;那不如来试试用 AI 给自己整点活儿新 “物料”。 不管是和奥特曼 “面对面” 聊天还是 “无中生有” 表情包&#xff0c;AI 都能做&#xff01; (※…

重磅!2023两院外籍院士增选名单公布

根据《中国科学院院士章程》《中国科学院外籍院士选举办法》等规定&#xff0c;2023年中国科学院选举产生了30名中国科学院外籍院士。 现予公布。 中国科学院 2023年11月23日 中国工程院2023年外籍院士增选共选举产生16位中国工程院外籍院士。 现予公布。 中国工程院 2023年…

Pyqt5实现多线程程序

主从架构 Pyqt常常使用**主从架构&#xff08;Master-Workers 架构&#xff09;**来避免界面卡死的情况。 Master-Workers 架构就像它的名字&#xff0c;一个master统领着几个workers一起干活。其中某个worker倒下了不会导致整体任务失败。matser不用干活&#xff0c;因此可以…

UltraCompare 23 for Mac文件对比工具

UltraCompare是一款功能强大的文件比较和合并工具&#xff0c; 以下是它的特色介绍&#xff1a; 多种文件格式支持&#xff1a;UltraCompare支持比较和合并多种文件格式&#xff0c;包括文本文件、二进制文件、office文档、PDF文件等。 文件差异高亮显示&#xff1a;UltraComp…

动态规划求二维网格中从左上角到右下角的最短路径( 每次只能向下、向右、向右下走 ) java 实现

dp[i][j] 表示在以点(0,0)作为左上角&#xff0c;点(i,i) 作为右下角的二维网格中 左上角到右下角的最短路径&#xff0c; 动态转移方程为&#xff1a;dp[i][j] min{ dp[i][j-1],dp[i-1][j],dp[i-1][j-1] }.distance weight[i][j] ImageUtils.java&#xff1a; import java.a…