Python的堆与优先队列

堆与优先队列

堆(英语:Heap)是计算机科学中的一种特别的树状数据结构。堆有如下特点:给定堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值”。

按照母节点与子结点的大小关系,堆又可以分为以下两种:若母节点的值恒小于等于子节点的值,此堆称为最小堆(min heap);反之,若母节点的值恒大于等于子节点的值,此堆称为最大堆(max heap)。

在堆中最顶端的那一个节点,称作根节点(root node),根节点本身没有母节点(parent node)。

优先队列是计算机科学中的一类抽象数据类型。优先队列中的每个元素都有各自的优先级,优先级最高的元素最先得到服务;优先级相同的元素按照其在优先队列中的顺序得到服务。优先队列往往用堆来实现。

堆的实现

堆的实现通过构造二叉堆(binary heap),实为二叉树的一种;由于其应用的普遍性,当不加限定时,均指该数据结构的这种实现。这种数据结构具有以下性质。

  • 任意节点小于(或大于)它的所有后裔,最小元(或最大元)在堆的根上(堆序性)。
  • 堆总是一棵完全树。即除了最底层,其他层的节点都被元素填满,且最底层尽可能地从左到右填入。

将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。常见的堆有二叉堆、斐波那契堆等。

优先队列的实现

普通的队列是一种先进先出的数据结构,元素在队列尾追加,而从队列头删除。在优先队列中,元素被赋予优先级。当访问元素时,具有最高优先级的元素最先删除。优先队列具有最高级先出 (first in, largest out)的行为特征。

而基于堆的堆顶元素的特性,我们可以用堆来实现优先队列。由于堆中任意节点P和C,若P是C的母节点,那么P的值会小于等于(或大于等于)C的值”,所以堆顶元素永远是堆中最小(或最大)的元素,这与优先队列定义的最高级先出 (first in, largest out)所符合,我们从堆顶取元素即取到了“最高优先级”的元素。

Python内置的堆与优先队列

Python内置的堆

Python提供了对堆的操作,但是并没有提供“堆”这种数据类型,它是直接把列表当成堆处理的。Python提供的 heapq 包中有一些函数,当程序用这些函数来操作列表时,该列表就会表现出“堆”的行为。

注:Python提供的是基于最小堆的操作,因此Python可以对 list 中的元素进行最小堆排列,这样程序每次获取堆顶元素时,总会取得堆中最小的元素

在交互式解释器中先导入 heapq 包,然后输入 heapq.__ all __ 命令来查看 heapq 包下的全部函数,输出结果如下:

In [1]: import heapqIn [2]: heapq.__all__
Out[2]:
['heappush','heappop','heapify','heapreplace','merge','nlargest','nsmallest','heappushpop']

上面列出的函数就是执行堆操作的工具函数,这些函数的功能如下:

  • heappush(heap, item) :将 item 元素入堆
  • heappop(heap) :将堆中最小元素弹出
  • heapify(heap):将堆树形应用到列表上
  • heapreplace(heap, x):将堆中最小元素弹出,并将元素 x 入堆
  • merge(*iterables, key=None, reverse=False):将多个有序的堆合并成一个大的有序堆,然后再输出
  • heappushpop(heap, item) :将 item 入堆,然后弹出并返回堆中最小元素
  • nlargest(n, iterable, key=None):返回堆中最大的 n 个元素
  • nsmallest(n, iterable, key=None):返回堆中最小的 n 个元素

heapq的使用

下面我们通过实操来看一下 heapq 中部分方法的用法。

In [3]: # 构造一个列表In [4]: test_data = list(range(10))In [5]: test_data.append(0.5)In [6]: test_data
Out[6]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0.5]

此时 test_data 是一个列表,下面我们对其应用堆属性

In [7]: heapq.heapify(test_data)In [8]: test_data
Out[8]: [0, 0.5, 2, 3, 1, 5, 6, 7, 8, 9, 4]

如上输出所示,列表 test_data 此时具有堆属性。test_data 中的元素看上去是杂乱无序的,单实际上,它满足最小堆的特征。我们将它转化为完全二叉树可以更加直观地看出来:


如上,满足最小堆的定义。接下来我们继续执行其它操作。

In [9]: # 弹出堆中的元素In [10]: heapq.heappop(test_data)
Out[10]: 0In [11]: heapq.heappop(test_data)
Out[11]: 0.5In [12]: test_data
Out[12]: [1, 3, 2, 7, 4, 5, 6, 9, 8]

如上,弹出的两个元素是堆中最小的元素,弹出两个元素之后, test_data 仍然满足最小堆的特征。

In [13]: # 向堆中加入元素In [14]: heapq.heappush(test_data, 0)In [15]: heapq.heappush(test_data, 0.5)In [16]: test_data
Out[16]: [0, 0.5, 2, 7, 1, 5, 6, 9, 8, 4, 3]

如上,向堆中压入 0 和 0.5 两个元素之后,堆节点的值与一开始的顺序不同,但此时 test_data 仍然满足最小堆的特征。将其转化为完全二叉树可以更容易看出来:


由此可知:数据相同的堆可能有多种不同的结构。
其他堆操作请读者自己实践

需要注意的是,堆节点不一定是单个元素,节点可以是一个元组、列表等可迭代对象,形如下:(priority_number, data),此时的大小比较会从左到右进行,直至当前元素可确定整个对象的大小。例如下面的列表转化为堆:

In [27]: test_data1 = [[6,3],[1,5],[6,2],[8,7]]In [28]: heapq.heapify(test_data1)In [29]: test_data1
Out[29]: [[1, 5], [6, 3], [6, 2], [8, 7]]

从堆中弹出两个元素

In [30]: heapq.heappop(test_data1)
Out[30]: [1, 5]In [31]: heapq.heappop(test_data1)
Out[31]: [6, 2]

如上结果所示,第一个被弹出的元素毫无疑问是 [1, 5] ,而接下来的堆中有两个对象第一个元素的值相等,即 [6, 3] 和 [6, 2] ,此时向下比较第二个元素,由于 2 比 3 小,所以 [6, 2]首先被 pop 出。

对于有 n 个元素的对象也是如此,若当前位置无法确定优先级,则取下一个位置进行比较,直至所有位置比较完仍无法确定,则优先级相等。

需要注意的是:若在比较过程中出现不可比元素,则会报错,如下:

In [52]: test_data2 = [[6,3],[1,5],[6,'test'],[8,7]]In [53]: heapq.heapify(test_data2)In [54]: heapq.heappop(test_data2)
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-54-e576df2a455f> in <module>
----> 1 heapq.heappop(test_data2)TypeError: '<' not supported between instances of 'int' and 'str'

在对 test_data2 应用堆属性之后, pop 出对象会报错,因为 [6, 3] 与 [6, ‘test’] 的第二个元素是不可比的。

Python的优先队列

Python的 queue 模块提供了优先队列的构造函数:queue.PriorityQueue(maxsize=0)。
参数maxsize是一个整数,用于设置可以放入队列中的项目数的上限。一旦达到此大小,插入将被阻塞,直到消耗队列项目为止。如果 maxsize小于或等于零,则队列大小为无限。

在 PriorityQueue 中,最低值的条目将首先被检索,条目的典型模式是形式为的元组:(priority_number, data)。如果 data 元素没有可比性,数据将被包装在一个类中,忽略数据值,仅仅比较优先级数字 。

注意:使用优先级存数据取数据,队列中的数据必须是同一类型

PriorityQueue 的基本用法如下:
首先导入 PriorityQueue 并查看其方法:

In [71]: import queueIn [72]: for meb in dir(queue.PriorityQueue()):...:     if not meb.startswith('_'):...:         print(meb)...:
all_tasks_done
empty
full
get
get_nowait
join
maxsize
mutex
not_empty
not_full
put
put_nowait
qsize
queue
task_done
unfinished_tasks

如上,这里我们仅仅介绍三个方法:

  1. empty() 判断优先队列是否为空
  2. put(item, block=True, timeout=None) 向优先队列添加一个元素
  3. get(block=True, timeout=None) 从优先队列取出优先级最高的一个元素

注:block、timeout参数用于线程编程中。

下面我们示范其用法:

In [75]: q = queue.PriorityQueue()In [76]: q.put([1,5])In [77]: q.put([6,3])In [78]: q.put([6,2])In [79]: q.put([8,7])

如上,创建了一个优先队列并向其中添加了四个元素,接下来我们输出四个元素,查看输出结果。

In [80]: while not q.empty():...:     print(q.get())...:
[1, 5]
[6, 2]
[6, 3]
[8, 7]

如上结果所示,优先级小的元素首先被输出,若当前位置优先级相同,则比较下一个位置,其基本用法与 heapq 相同,需要注意的是,若在比较过程中出现不可比元素,同样也会出错,这个错误提示是在向优先队列加入元素时提示的,如下:

In [81]: q = queue.PriorityQueue()In [82]: q.put([6,3])In [83]: q.put([6,'test'])
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-10-686657662746> in <module>
----> 1 q.put([6,'test'])D:\software2\Anaconda3\Anaconda3\lib\queue.py in put(self, item, block, timeout)147                             raise Full148                         self.not_full.wait(remaining)
--> 149             self._put(item)150             self.unfinished_tasks += 1151             self.not_empty.notify()D:\software2\Anaconda3\Anaconda3\lib\queue.py in _put(self, item)231232     def _put(self, item):
--> 233         heappush(self.queue, item)234235     def _get(self):TypeError: '<' not supported between instances of 'str' and 'int'

如上,同样出现了元素不可比的情况。

总结

堆是计算机科学中的一种特别的树状数据结构,而优先队列可以是基于堆实现的,这两种数据结构在实际工作中会有许许多多的应用,但是在应用过程中应该注意一些细节问题,避免误用。

heapq 多用于建堆,而 queue.PriorityQueue 不仅有根据优先级获取元素的特点,其本身是线程安全类,在进行线程编程时可以加入其他控制条件,而heapq模块不提供线程安全保证。关于 Python 中的 heapq 与 queue.PriorityQueue 的更多区别请读者参考其他资料。

Python的堆与优先队列相关推荐

  1. python之堆heapq模块

    python之堆heapq模块 堆是一种特殊的树形结构,通常我们所说的堆的数据结构指的是完全二叉树,并且根节点的值小于等于该节点所有子节点的值. 堆是非线性的树形的数据结构,有两种堆,最大堆与最小堆. ...

  2. 【数据结构】堆,大根堆,小根堆,优先队列 详解

    目录 堆 1.堆的数组实现 2.小根堆 3.大根堆 4.优先队列 例题 1.SP348 EXPEDI - Expedition(有趣的贪心思路,优先队列) 2.合并果子 堆 要了解堆之前,请先了解树, ...

  3. 数据结构-堆实现优先队列(java)

    队列的特点是先进先出.通常都把队列比喻成排队买东西,大家都很守秩序,先排队的人就先买东西.但是优先队列有所不同,它不遵循先进先出的规则,而是根据队列中元素的优先权,优先权最大的先被取出.这就很像堆的特 ...

  4. 关于二叉堆(优先队列)的其他操作及其应用

    [0]README 0.1)本文总结于 数据结构与算法分析:源代码均为原创, 旨在了解到我们学习了优先队列后,还能干些什么东西出来, 增加学习的interest: 0.2)以下列出了 关于二叉堆(优先 ...

  5. 【从蛋壳到满天飞】JS 数据结构解析和算法实现-堆和优先队列(一)

    前言 [从蛋壳到满天飞]JS 数据结构解析和算法实现,全部文章大概的内容如下: Arrays(数组).Stacks(栈).Queues(队列).LinkedList(链表).Recursion(递归思 ...

  6. 数据结构之堆与优先队列

    堆与优先队列: 堆 堆必须是一个完全二叉树.除了最后一层,其他层的节点个数都是满的,最后一层的节点都靠左排列 堆中的每个节点的值必须大于等于(或者小于等于)其子树中每个节点的值或者说堆中每个节点的值都 ...

  7. 堆与优先队列课内模板

    全部数据结构.算法及应用课内模板请点击:https://blog.csdn.net/weixin_44077863/article/details/101691360 先补充两个概念 最大树(最小树) ...

  8. [转载] python中 堆heapq以及 队列queue的使用

    参考链接: Python中的堆队列(Heap queue或heapq) python中 堆heapq以及 队列queue的使用 1. 堆heapq的使用 ## -------------------- ...

  9. [重修数据结构0x03]并查集、堆、优先队列(2021.8.11)

    前言 在做遍历的题目的时候,发现掌握一些特殊的数据结构和技巧有时对解决题目有着决定性的作用,不可不学.因此特地拿出来两天学习一下并查集.堆.优先队列.以后有更多思考和感悟再加补充吧.内容来自算法笔记, ...

最新文章

  1. 【技术交流】让我们来谈一谈多线程和并发任务
  2. opencl fft实例整理
  3. 关于滑动条滚动 scroll兼容
  4. 中国移动互联网趋势报告:教育、金融类App留存率更高
  5. 8.局部变量/全局变量global/内嵌函数/闭包nonlocal
  6. WebForm编辑器的相关资源
  7. CSS 魔法:学海无涯,而吾生有涯
  8. 用fileupload处理文件上传
  9. InDesign 2022 for mac排版布局和页面设计
  10. JAVA开发常用类(六)
  11. Oracle Active database duplication
  12. 论文校对错别字检测工具
  13. 「微信同声传译」小程序插件:快速实现语音转文字、文本翻译、语音合成等能力
  14. 【HAVENT原创】Mac 下编译 ReactNative(CRN) 踩坑记录
  15. linux下的密码破解软件
  16. python爬取豆瓣读书_爬取豆瓣读书.py
  17. 替代YY语音,自行搭建语音实时服务器
  18. D - Molar mass
  19. 由I2C data信号低电平不到0,再思考I2C及GPIO
  20. 用sendcloud来发邮件

热门文章

  1. 【JS】网页点击悬浮小苹果+小虫子追踪鼠标位置(可替换)
  2. UBUNTU系统镜像定制
  3. 超级电容-一阶RC模型-RLS参数辨识-Simulink仿真
  4. 快手 网络安全工程师 二面总结(归档,凉经)
  5. 【国际禁毒日】和TcaplusDB一起向毒品say NO!
  6. MCV 和 MTV框架基本信息
  7. Vue3-Pinia(小菠萝)使用详解
  8. 微信支付(公众号支付)微信公众平台开发教程(5)
  9. 小杜滑铲 DFS 最长路径
  10. 安规电容(X电容,Y电容)的简单对比介绍