跳到主要内容Python heapq 入门与实战:最小堆、Top-K 和优先队列 | 极客日志PythonAI算法
Python heapq 入门与实战:最小堆、Top-K 和优先队列
heapq 是 Python 标准库里处理最小堆的模块,适合维护最小值、优先级队列、Top-K 和多个有序序列合并。文中梳理了 heapify、heappush、heappop、nlargest、nsmallest、merge 等常用接口,并给出优先级队列、中位数维护、堆排序、最大堆模拟、自定义对象堆和流式 Top-N 的写法。结论很直接:需要局部最值或持续维护候选集时用 heapq,比完整排序更合适;只是做一次排序时,sorted() 往往更省事。
Python heapq 入门与实战:最小堆、Top-K 和优先队列
heapq 是 Python 标准库里处理堆的模块,默认提供的是最小堆。如果你的场景里经常要拿到当前最小值、维护 Top-K,或者做优先级调度,它比自己手写排序要顺手得多。它不负责'自动排序整个列表',只管把堆顶这件事做好。
堆本质上是一个完全二叉树的数组实现。对小顶堆来说,父节点总是不大于子节点,所以最小元素会停在堆顶。插入和弹出都能做到 O(log n),建堆则是 O(n)。这个复杂度在流式数据、任务调度这类场景里很实用。
基础堆操作
| 函数名 | 功能描述 | 时间复杂度 | 使用场景 |
|---|
heapify(x) | 将列表 x 转换为堆结构 | O(n) | 列表初始化堆 |
heappush(heap, item) | 向堆中插入新元素 | O(log n) | 动态添加元素 |
heappop(heap) | 弹出并返回最小元素 | O(log n) | 获取最小元素 |
heapreplace(heap, item) | 弹出最小元素并插入新元素 | O(log n) | 替换堆顶元素 |
heappushpop(heap, item) | 先插入再弹出最小元素 | O(log n) | 高效插入弹出 |
import heapq
data = [3, 1, 4, 1, 5, 9, 2, 6]
heapq.heapify(data)
print(f"堆化后的列表:{data}")
heapq.heappush(data, 0)
print(f"插入 0 后的堆:{data}")
min_element = heapq.heappop(data)
print(f"弹出的最小元素:")
()
{min_element}
print
f"弹出后的堆:{data}"
有个细节很容易被忽略:heapify() 只是把'堆性质'建立起来,不会帮你排成升序。很多人第一次看打印结果会误以为它是半排序状态,其实不是,堆只保证堆顶最小。
批量取前几个元素
| 函数名 | 功能描述 | 时间复杂度 | 适用场景 |
|---|
nlargest(n, iterable) | 返回前 n 个最大元素 | O(n log k) | Top-K 最大元素 |
nsmallest(n, iterable) | 返回前 n 个最小元素 | O(n log k) | Top-K 最小元素 |
import heapq
import random
numbers = [random.randint(1, 1000) for _ in range(100)]
largest_5 = heapq.nlargest(5, numbers)
print(f"最大的 5 个元素:{largest_5}")
smallest_5 = heapq.nsmallest(5, numbers)
print(f"最小的 5 个元素:{smallest_5}")
words = ['apple', 'banana', 'cherry', 'date', 'elderberry']
longest_3 = heapq.nlargest(3, words, key=len)
print(f"最长的 3 个单词:{longest_3}")
nlargest() 和 nsmallest() 适合拿'前几名',但别拿它去替代完整排序。数据量小的时候你感受不明显,数据一大,省下来的就是无意义的比较和内存开销。
合并多个有序序列
heapq.merge(*iterables) 会把多个已经排好序的序列合成一个有序迭代器。它不会一次性把所有数据拉进内存,这一点比先拼接再 sorted() 更省。
import heapq
list1 = [1, 3, 5, 7]
list2 = [2, 4, 6, 8]
list3 = [0, 9, 10]
merged = list(heapq.merge(list1, list2, list3))
print(f"合并后的有序列表:{merged}")
优先级队列
heapq 最常见的实战用法之一就是优先级队列。Python 没有内置的稳定优先队列类型,但用一个三元组 (priority, index, item) 就够用了。index 的作用是打平相同优先级时的比较问题,也能顺便保留先来先服务的顺序。
import heapq
class PriorityQueue:
def __init__(self):
self._heap = []
self._index = 0
def push(self, item, priority):
"""添加元素到优先级队列"""
heapq.heappush(self._heap, (priority, self._index, item))
self._index += 1
def pop(self):
"""弹出优先级最高的元素"""
if self._heap:
return heapq.heappop(self._heap)[-1]
raise IndexError("优先级队列为空")
def is_empty(self):
return len(self._heap) == 0
pq = PriorityQueue()
pq.push("任务 A", 3)
pq.push("任务 B", 1)
pq.push("任务 C", 2)
while not pq.is_empty():
print(f"执行:{pq.pop()}")
数据流中位数
这个例子基本是堆的经典题:用一个最大堆存较小的一半,一个最小堆存较大的一半,随时保持两边平衡。heapq 没有真正的最大堆,只能把数取负来模拟。代码不算优雅,但在线求中位数时很好用。
import heapq
class MedianFinder:
def __init__(self):
self.max_heap = []
self.min_heap = []
def add_num(self, num):
if not self.max_heap or num <= -self.max_heap[0]:
heapq.heappush(self.max_heap, -num)
else:
heapq.heappush(self.min_heap, num)
if len(self.max_heap) > len(self.min_heap) + 1:
heapq.heappush(self.min_heap, -heapq.heappop(self.max_heap))
elif len(self.min_heap) > len(self.max_heap):
heapq.heappush(self.max_heap, -heapq.heappop(self.min_heap))
def find_median(self):
if len(self.max_heap) == len(self.min_heap):
return (-self.max_heap[0] + self.min_heap[0]) / 2
else:
return -self.max_heap[0]
finder = MedianFinder()
for num in [1, 3, 2, 6, 4, 5]:
finder.add_num(num)
print(f"当前中位数:{finder.find_median()}")
堆排序
如果只是想验证堆的行为,堆排序也能写得很短。不过在 Python 里,真要排序通常直接用 sorted(),堆排序更多是教学用途,或者你想借堆结构顺手解决别的问题。
import heapq
def heap_sort(iterable):
"""使用堆排序算法对可迭代对象进行排序"""
heap = list(iterable)
heapq.heapify(heap)
return [heapq.heappop(heap) for _ in range(len(heap))]
unsorted_data = [9, 2, 7, 5, 1, 8, 3, 6, 4]
sorted_data = heap_sort(unsorted_data)
print(f"堆排序结果:{sorted_data}")
最大堆的写法
heapq 没有直接提供最大堆接口,常见做法就是把数取负。这个方案简单,够用,但只适合数值类型;如果是自定义对象,通常还是直接包装比较键更稳。
import heapq
class MaxHeap:
def __init__(self):
self._heap = []
def push(self, item):
heapq.heappush(self._heap, -item)
def pop(self):
return -heapq.heappop(self._heap)
def peek(self):
return -self._heap[0] if self._heap else None
max_heap = MaxHeap()
for num in [3, 1, 4, 1, 5]:
max_heap.push(num)
print("最大堆元素弹出顺序:")
while max_heap._heap:
print(max_heap.pop())
自定义对象的堆
自定义对象可以直接实现 __lt__,不过这类写法要小心:比较规则一旦写死,后面需求变了就容易牵一发动全身。只在对象本身就有明确排序语义时这么做,别为了省几行代码把逻辑绑死。
import heapq
class Task:
def __init__(self, name, priority, duration):
self.name = name
self.priority = priority
self.duration = duration
def __lt__(self, other):
if self.priority == other.priority:
return self.duration < other.duration
return self.priority > other.priority
def __repr__(self):
return f"Task({self.name}, priority:{self.priority}, duration:{self.duration})"
tasks = [
Task("紧急任务", 3, 2),
Task("普通任务", 1, 5),
Task("重要任务", 2, 3)
]
heap = []
for task in tasks:
heapq.heappush(heap, task)
print("任务执行顺序:")
while heap:
print(heapq.heappop(heap))
什么时候该用 heapq
| 操作场景 | 推荐方法 | 时间复杂度 | 优势 |
|---|
| 一次性获取 Top-K | nlargest()/nsmallest() | O(n log k) | 代码简洁 |
| 持续插入和弹出 | heappush() + heappop() | O(log n) | 动态高效 |
| 多个有序序列合并 | heapq.merge() | O(n log k) | 内存友好 |
如果你只是做一次完整排序,sorted() 通常更直接;如果你要的是'保持一个不断变化的候选集',堆就更合适。它不花哨,但在合适的地方很好使。
大数据流里只保留 Top-N
import heapq
def process_large_dataset(data_stream, top_n=10):
"""使用堆处理大数据流,只维护 Top-N 元素"""
heap = []
for item in data_stream:
if len(heap) < top_n:
heapq.heappush(heap, item)
elif item > heap[0]:
heapq.heapreplace(heap, item)
return sorted(heap, reverse=True)
import random
data_stream = (random.randint(1, 10000) for _ in range(100000))
top_10 = process_large_dataset(data_stream, 10)
print(f"大数据流中的 Top-10: {top_10}")
这一段的关键不在'快一点',而在'别把所有数据都塞进内存'。只保留前 N 个元素,很多场景下就够了,尤其是日志、指标和排行榜一类的数据处理。
heapq 的使用门槛不高,真正容易出错的是选错结构。它适合维护局部最值、优先级和有序流,不适合拿来当通用排序工具。把这个边界记住,后面写起来会省很多事。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- RSA密钥对生成器
生成新的随机RSA私钥和公钥pem证书。 在线工具,RSA密钥对生成器在线工具,online
- Mermaid 预览与可视化编辑
基于 Mermaid.js 实时预览流程图、时序图等图表,支持源码编辑与即时渲染。 在线工具,Mermaid 预览与可视化编辑在线工具,online
- 随机西班牙地址生成器
随机生成西班牙地址(支持马德里、加泰罗尼亚、安达卢西亚、瓦伦西亚筛选),支持数量快捷选择、显示全部与下载。 在线工具,随机西班牙地址生成器在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online