Python数据结构—heapq库让你的代码提速50倍
·
包含编程资料、学习路线图、源代码、软件安装包等!【[点击这里]】!
还在用排序+切片解决最大最小值问题?还在手写优先队列代码?Python标准库中的heapq模块可能是你忽略的效率倍增神器!

一、什么是堆?为什么要学习heapq?
堆(Heap)是一种特殊的树形数据结构,它能让你快速找到集合中的最大值或最小值。
- 想象一下,你有一堆纸条,每张纸条上写着一个数字。如果你想找出最小的数字,通常需要挨个看一遍。但如果这些纸条按照堆的规则摆放,你只需要看最上面的那张就行了!
heapq库的核心优势:
- 快速获取最大值/最小值:O(1)时间
- 添加或删除元素:O(log n)时
- 是Python标准库,无需安装
- 使用简单,API友好
在以下场景中特别有用:
- 需要频繁获取最大/最小元素
- 需要维护一个优先队列
- 需要找出集合中最大/最小的k个元素
二、基本使用:10分钟入门heapq
1. 创建堆
- Python的heapq默认实现的是最小堆,即堆顶总是最小的元素。
import heapq
# 方法1:从列表创建堆
numbers = [10, 4, 8, 3, 5]
heapq.heapify(numbers) # 原地将列表转换为堆
print(numbers) # 输出:[3, 4, 8, 10, 5]
# 方法2:创建空堆然后添加元素
heap = []
heapq.heappush(heap, 10)
heapq.heappush(heap, 4)
heapq.heappush(heap, 8)
heapq.heappush(heap, 3)
heapq.heappush(heap, 5)
print(heap) # 输出:[3, 4, 8, 10, 5]
- 注意,转换后的列表看起来不是完全排序的,这是因为堆只保证父节点小于子节点,而不是完全按顺序排列。
2. 基本操作
import heapq
# 创建堆
heap = [3, 4, 8, 10, 5]
# 获取最小元素但不移除
print(heap[0]) # 输出:3
# 弹出最小元素
smallest = heapq.heappop(heap)
print(smallest) # 输出:3
print(heap) # 输出:[4, 5, 8, 10]
# 添加元素
heapq.heappush(heap, 2)
print(heap) # 输出:[2, 4, 8, 10, 5]
# 替换堆顶元素(弹出并添加)
heapq.heapreplace(heap, 6) # 弹出2,添加6
print(heap) # 输出:[4, 5, 8, 10, 6]
3. 获取最大/最小的n个元素
- 这是heapq最实用的功能之一:
import heapq
scores = [85, 92, 78, 98, 65, 81, 75, 88]
# 获取最大的3个元素
top_3 = heapq.nlargest(3, scores)
print("最高的3个分数:", top_3) # 输出:[98, 92, 88]
# 获取最小的3个元素
bottom_3 = heapq.nsmallest(3, scores)
print("最低的3个分数:", bottom_3) # 输出:[65, 75, 78]
对于字典或复杂对象,可以使用key参数:
import heapq
students = [
{"name": "张三", "score": 85},
{"name": "李四", "score": 92},
{"name": "王五", "score": 78},
{"name": "赵六", "score": 98},
{"name": "钱七", "score": 65}]
# 获取成绩最高的2个学生
top_students = heapq.nlargest(2, students, key=lambda s: s["score"])
print("成绩最高的学生:", [s["name"] for s in top_students]) # 输出:['赵六', '李四']
三、实用案例:解决日常编程问题
案例1:找出列表中第K大的元素
import heapq
def find_kth_largest(nums, k):
"""找出列表中第K大的元素"""
# 使用heapq的内置函数
return heapq.nlargest(k, nums)[-1]
# 测试
nums = [3, 2, 1, 5, 6, 4]
k = 2 # 找第2大的元素
print(f"第{k}大的元素是:", find_kth_largest(nums, k)) # 输出:5
如果你好奇这和传统方法相比有多快,我们来做个简单对比:
import heapq
import time
import random
def find_kth_largest_sort(nums, k):
"""使用排序找出第K大的元素"""
return sorted(nums, reverse=True)[k-1]
def find_kth_largest_heap(nums, k):
"""使用堆找出第K大的元素"""
return heapq.nlargest(k, nums)[-1]
# 生成测试数据
data = [random.randint(1, 1000000) for _ in range(1000000)]
k = 100
# 测试排序方法
start = time.time()
result1 = find_kth_largest_sort(data, k)
end = time.time()
print(f"排序方法耗时: {end-start:.6f}秒")
# 测试堆方法
start = time.time()
result2 = find_kth_largest_heap(data, k)
end = time.time()
print(f"堆方法耗时: {end-start:.6f}秒")
print(f"结果一致: {result1 == result2}")
- 在处理大数据集时,堆方法通常快10到50倍,因为它只需要遍历数据一次,并维护一个大小为k的堆。
案例2:实现简单的优先队列
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)[2]
def is_empty(self):
return len(self._queue) == 0
# 使用优先队列
pq = PriorityQueue()
pq.push("发送邮件", 1) # 优先级=1
pq.push("写报告", 3) # 优先级=3
pq.push("修复bug", 5) # 优先级=5
# 按优先级处理任务
while not pq.is_empty():
task = pq.pop()
print(f"执行任务: {task}")
# 输出:
# 执行任务: 修复bug
# 执行任务: 写报告
# 执行任务: 发送邮件
案例3:合并多个有序列表
import heapq
def merge_sorted_lists(lists):
"""合并多个有序列表"""
return list(heapq.merge(*lists))
# 示例
list1 = [1, 5, 9]
list2 = [2, 6, 10]
list3 = [3, 7, 11]
merged = merge_sorted_lists([list1, list2, list3])
print("合并后的有序列表:", merged)
# 输出:[1, 2, 3, 5, 6, 7, 9, 10, 11]
- heapq.merge返回一个迭代器,它会按顺序产生所有列表中的元素,非常适合处理大型数据流。
四、进阶技巧:让heapq更上一层楼
技巧1:实现最大堆
- Python的heapq默认实现的是最小堆,但有时我们需要最大堆。一个简单的方法是将元素取负值:
import heapq
def max_heap_demo():
data = [5, 2, 8, 1, 9, 3]
# 创建最大堆 (存储元素的负值)
max_heap = [-x for x in data]
heapq.heapify(max_heap)
# 取出最大元素
largest = -heapq.heappop(max_heap)
print("最大元素:", largest) # 输出:9
# 添加新元素
heapq.heappush(max_heap, -7)
# 打印堆中所有元素 (从大到小)
result = []
while max_heap:
result.append(-heapq.heappop(max_heap))
print("所有元素(从大到小):", result) # 输出:[8, 7, 5, 3, 2, 1]
max_heap_demo()
技巧2:多条件排序
- 有时需要根据多个条件排序,可以使用元组:
import heapq
def multi_criteria_sort():
# 学生信息:(姓名, 成绩, 年龄)
students = [
("张三", 85, 20),
("李四", 92, 22),
("王五", 78, 19),
("赵六", 92, 20),
("钱七", 85, 21)]
# 创建堆 - 先按成绩降序,成绩相同按年龄升序
heap = [(-score, age, name) for name, score, age in students]
heapq.heapify(heap)
# 从堆中取出元素,此时已按条件排序
result = []
while heap:
score, age, name = heapq.heappop(heap)
result.append((name, -score, age))
print("排序结果:")
for name, score, age in result:
print(f"{name}: 成绩={score}, 年龄={age}")
multi_criteria_sort()
# 输出:
# 排序结果:
# 李四: 成绩=92, 年龄=22
# 赵六: 成绩=92, 年龄=20
# 张三: 成绩=85, 年龄=20
# 钱七: 成绩=85, 年龄=21
# 王五: 成绩=78, 年龄=19
技巧3:保持固定大小的堆
- 当我们只需要保留最大/最小的k个元素时,可以维护一个固定大小的堆:
import heapq
import random
def keep_smallest_k(stream, k):
"""
从数据流中保留最小的k个元素
"""
result = [] # 最大堆,存放当前最小的k个元素
for item in stream:
if len(result) < k:
# 堆未满,直接添加
# 注意:这里存负值来模拟最大堆
heapq.heappush(result, -item)
elif -result[0] > item:
# 如果新元素比堆中最大元素小,替换它
heapq.heapreplace(result, -item)
# 返回结果(从小到大)
return sorted([-x for x in result])
# 生成模拟数据流
stream = [random.randint(1, 100) for _ in range(20)]
print("原始数据:", stream)
# 保留最小的5个元素
k = 5
smallest_k = keep_smallest_k(stream, k)
print(f"最小的{k}个元素:", smallest_k)
# 验证(与排序结果比较)
sorted_stream = sorted(stream)
print(f"排序后的前{k}个:", sorted_stream[:k])
print(f"结果一致: {smallest_k == sorted_stream[:k]}")
五、性能比较:heapq vs 其他方法
为了直观展示heapq的优势,我们来比较几种不同方法在解决TopK问题上的性能差异:
import heapq
import random
import time
def test_performance(n=1000000, k=10):
"""比较不同方法在大数据集上找出最大的k个元素的性能"""
# 生成测试数据
data = [random.randint(1, n) for _ in range(n)]
# 方法1:排序后切片
start = time.time()
result1 = sorted(data, reverse=True)[:k]
t1 = time.time() - start
print(f"排序法耗时: {t1:.6f}秒")
# 方法2:使用heapq.nlargest
start = time.time()
result2 = heapq.nlargest(k, data)
t2 = time.time() - start
print(f"heapq.nlargest耗时: {t2:.6f}秒")
# 方法3:维护固定大小的最小堆
start = time.time()
min_heap = []
for x in data:
if len(min_heap) < k:
heapq.heappush(min_heap, x)
elif x > min_heap[0]:
heapq.heapreplace(min_heap, x)
result3 = sorted(min_heap, reverse=True)
t3 = time.time() - start
print(f"固定大小堆耗时: {t3:.6f}秒")
# 检查结果是否一致
print(f"结果一致: {result1 == result2 == result3}")
# 计算性能提升
speedup = t1 / min(t2, t3)
print(f"堆方法比排序快约 {speedup:.1f} 倍")
# 运行性能测试
test_performance()
- 在大多数情况下,你会发现使用堆的方法比传统排序快10-50倍,尤其是当数据量大而k值小的情况下。
六、实战案例:解决经典算法问题
1. LeetCode 215: 数组中的第K个最大元素
import heapq
def findKthLargest(nums, k):
"""
找出数组中第K个最大的元素
例如,[3,2,1,5,6,4] 中第2大的元素是 5
"""
# 方法1:直接使用nlargest
return heapq.nlargest(k, nums)[-1]
# 方法2:维护大小为k的最小堆
# heap = []
# for num in nums:
# if len(heap) < k:
# heapq.heappush(heap, num)
# elif num > heap[0]:
# heapq.heappop(heap)
# heapq.heappush(heap, num)
# return heap[0]
# 测试
print(findKthLargest([3,2,1,5,6,4], 2)) # 输出:5
2. LeetCode 347: 前K个高频元素
import heapq
from collections import Counter
def topKFrequent(nums, k):
"""
给定一个数组,返回出现频率前k高的元素
例如,[1,1,1,2,2,3] 中出现频率前2高的元素是 [1,2]
"""
# 统计元素频率
counter = Counter(nums)
# 使用堆找出频率最高的k个元素
return heapq.nlargest(k, counter.keys(), key=counter.get)
# 测试
print(topKFrequent([1,1,1,2,2,3], 2)) # 输出:[1, 2]
3. LeetCode 23: 合并K个排序链表
import heapq
# 链表节点定义
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def mergeKLists(lists):
"""
合并K个排序链表
"""
# 辅助函数:构建链表
def build_linked_list(values):
dummy = ListNode(0)
current = dummy
for val in values:
current.next = ListNode(val)
current = current.next
return dummy.next
# 辅助函数:链表转列表
def linked_list_to_list(head):
result = []
while head:
result.append(head.val)
head = head.next
return result
# 将所有链表的值合并到一个列表
all_values = []
for lst in lists:
current = lst
while current:
all_values.append(current.val)
current = current.next
# 使用堆排序
heapq.heapify(all_values)
# 构建结果链表
dummy = ListNode(0)
current = dummy
while all_values:
current.next = ListNode(heapq.heappop(all_values))
current = current.next
return dummy.next
# 测试
# 创建测试数据
list1 = ListNode(1, ListNode(4, ListNode(5)))
list2 = ListNode(1, ListNode(3, ListNode(4)))
list3 = ListNode(2, ListNode(6))
# 合并链表
merged = mergeKLists([list1, list2, list3])
# 打印结果
result = []
while merged:
result.append(merged.val)
merged = merged.next
print(result) # 输出:[1, 1, 2, 3, 4, 4, 5, 6]
通过本文,我们学习了:
- heapq的基本使用:创建堆、添加元素、弹出元素
- 实用的内置函数:nlargest、nsmallest、merge
- 实际应用案例:优先队列、TopK问题、合并有序列表
- 性能优势:与传统方法相比,堆在特定场景下的效率提升
- 进阶技巧:实现最大堆、多条件排序、固定大小堆
记住,选择合适的数据结构是写出高效代码的关键。在需要频繁获取最大/最小元素的场景中,堆往往是最佳选择!希望这篇文章对你有所帮助!下次当你遇到需要频繁查找最值的问题时,别忘了Python标准库中这个强大的小工具——heapq!

总结
- 最后希望你编程学习上不急不躁,按照计划有条不紊推进,把任何一件事做到极致,都是不容易的,加油,努力!相信自己!
文末福利
- 最后这里免费分享给大家一份Python全套学习资料,希望能帮到那些不满现状,想提升自己却又没有方向的朋友,也可以和我一起来学习交流呀。
包含编程资料、学习路线图、源代码、软件安装包等!【[点击这里]】领取!
- ① Python所有方向的学习路线图,清楚各个方向要学什么东西
- ② 100多节Python课程视频,涵盖必备基础、爬虫和数据分析
- ③ 100多个Python实战案例,学习不再是只会理论
- ④ 华为出品独家Python漫画教程,手机也能学习
可以扫描下方二维码领取【保证100%免费】
更多推荐
所有评论(0)