1.5 实现一个优先级队列
解决方案
下面的类利用 heapq
模块实现了一个简单的优先级队列:
1 2 3 4 5 6 7 8 9 10 11 12 13 | import heapq class PriorityQueue: def __init__(self): self._queue = [] self._index = 0 def push(self, item, priority): heapq.heappush(self._queue, (-priority, self._index, item)) self._index += 1 def pop(self): return heapq.heappop(self._queue)[-1] |
下面是它的使用方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | >>> class Item: ... def __init__(self, name): ... self.name = name ... def __repr__(self): ... return 'Item({!r})' .format(self.name) ... >>> q = PriorityQueue() >>> q.push(Item( 'foo' ), 1) >>> q.push(Item( 'bar' ), 5) >>> q.push(Item( 'spam' ), 4) >>> q.push(Item( 'grok' ), 1) >>> q.pop() Item( 'bar' ) >>> q.pop() Item( 'spam' ) >>> q.pop() Item( 'foo' ) >>> q.pop() Item( 'grok' ) >>> |
仔细观察可以发现,第一个 pop()
操作返回优先级最高的元素。 另外注意到如果两个有着相同优先级的元素( foo
和 grok
),pop 操作按照它们被插入到队列的顺序返回的。
讨论
这一小节我们主要关注 heapq
模块的使用。 函数 heapq.heappush()
和 heapq.heappop()
分别在队列 _queue
上插入和删除第一个元素, 并且队列 _queue
保证第一个元素拥有最高优先级。 heappop()
函数总是返回”最小的”的元素,这就是保证队列pop操作返回正确元素的关键。 另外,由于 push 和 pop 操作时间复杂度为 O(log N),其中 N 是堆的大小,因此就算是 N 很大的时候它们运行速度也依旧很快。
在上面代码中,队列包含了一个 (-priority, index, item)
的元组。 优先级为负数的目的是使得元素按照优先级从高到低排序。 这个跟普通的按优先级从低到高排序的堆排序恰巧相反。
index
变量的作用是保证同等优先级元素的正确排序。 通过保存一个不断增加的 index
下标变量,可以确保元素按照它们插入的顺序排序。 而且, index
变量也在相同优先级元素比较的时候起到重要作用。
为了阐明这些,先假定 Item
实例是不支持排序的:
1 2 3 4 5 6 7 | >>> a = Item( 'foo' ) >>> b = Item( 'bar' ) >>> a < b Traceback (most recent call last): File "<stdin>" , line 1, in <module> TypeError: unorderable types: Item() < Item() >>> |
如果你使用元组 (priority, item)
,只要两个元素的优先级不同就能比较。 但是如果两个元素优先级一样的话,那么比较操作就会跟之前一样出错:
1 2 3 4 5 6 7 8 9 10 | >>> a = (1, Item( 'foo' )) >>> b = (5, Item( 'bar' )) >>> a < b True >>> c = (1, Item( 'grok' )) >>> a < c Traceback (most recent call last): File "<stdin>" , line 1, in <module> TypeError: unorderable types: Item() < Item() >>> |
通过引入另外的 index
变量组成三元组 (priority, index, item)
,就能很好的避免上面的错误, 因为不可能有两个元素有相同的 index
值。Python 在做元组比较时候,如果前面的比较已经可以确定结果了, 后面的比较操作就不会发生了:
1 2 3 4 5 6 7 8 | >>> a = (1, 0, Item( 'foo' )) >>> b = (5, 1, Item( 'bar' )) >>> c = (1, 2, Item( 'grok' )) >>> a < b True >>> a < c True >>> |
如果你想在多个线程中使用同一个队列,那么你需要增加适当的锁和信号量机制。
heapq
模块的官方文档有更详细的例子程序以及对于堆理论及其实现的详细说明。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 单元测试从入门到精通
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律