大家好呀~今天咱们来聊聊 Python 里超实用的堆排序工具 ——heapq模块。不管你是刚入门的编程小白,还是需要优化代码效率的开发者,掌握heapq都能帮你在数据处理上省不少事,快跟着文章一起学起来吧!
一、heapq 模块:到底是干嘛的?
heapq的核心作用,就是实现最小堆排序算法,而且能直接和 Python 的列表(list)配合使用,特别方便。
首先得明确一个概念:什么是 “堆”?
堆是一种类似树的数据结构,其中子节点和父节点之间存在固定的排序关系。咱们常用的 “二叉堆”,可以用列表(或数组)来表示 —— 如果元素的索引是 N(从 0 开始计数),那它的两个子节点索引就分别是2*N+1和2*N+2。
这种结构的好处很明显:堆的调整可以 “原地” 进行,添加或删除元素时不用重新分配大量内存,效率超高!
二、两种堆:最大堆 vs 最小堆
堆主要分两种,咱们得先搞清楚区别:
•最大堆(max-heap):父节点的值 ≥ 两个子节点的值;
•最小堆(min-heap):父节点的值 ≤ 两个子节点的值。
划重点!Python 的heapq模块实现的是最小堆,这一点在后续实战中会频繁用到~
三、实战准备:示例数据与辅助工具
接下来的例子,都会用到两份基础文件,先给大家展示一下:
1. 示例数据文件:heapq_heapdata.py
这份文件里是咱们要处理的随机数据,代码很简单:
# 数据由random模块生成 data = [19, 9, 4, 10, 11] |
2. 堆可视化工具:heapq_showtree.py
为了更直观地看到堆的结构,咱们用这个工具打印堆。代码如下:
import math from io import StringIO def show_tree(tree, total_width=36, fill=' '): """美化打印堆的结构""" output = StringIO() last_row = -1 for i, n in enumerate(tree): if i: # 计算当前节点所在的“行”(树的层级) row = int(math.floor(math.log(i + 1, 2))) else: row = 0 # 根节点在第0行 if row != last_row: output.write('\n') # 换行开始新的一行 # 计算每列的宽度,保证堆的结构对齐 columns = 2 ** row col_width = int(math.floor(total_width / columns)) output.write(str(n).center(col_width, fill)) # 居中显示节点值 last_row = row print(output.getvalue()) print('-' * total_width) # 打印分隔线 print() |
四、创建堆:两种核心方法
有了基础数据和工具,咱们就来实战创建堆。heapq提供了两种常用方式:heappush()和heapify(),各自适用不同场景。
1. 逐个添加:heappush ()
如果数据是动态生成的(比如从文件 / 接口逐步读取),用heappush()逐个添加元素,能实时维持堆的排序结构。
示例代码:heapq_heappush.py
import heapq from heapq_showtree import show_tree # 导入可视化工具 from heapq_heapdata import data # 导入示例数据 heap = [] print('原始数据 :', data) print() # 逐个将数据加入堆 for n in data: print(f'添加元素 {n:>3}:') heapq.heappush(heap, n) # 核心操作:将n加入堆 show_tree(heap) # 打印当前堆的结构 |
运行结果(终端输入python3 heapq_heappush.py):
原始数据 : [19, 9, 4, 10, 11] 添加元素 19: 19 ------------------------------------ 添加元素 9: 9 19 ------------------------------------ 添加元素 4: 4 19 9 ------------------------------------ 添加元素 10: 4 10 9 19 ------------------------------------ 添加元素 11: 4 10 9 19 11 ------------------------------------ |
能看到每次添加元素后,堆都会自动调整,始终保持 “父节点≤子节点” 的最小堆结构~
2. 原地调整:heapify ()
如果数据已经全部在内存里(比如一个现成的列表),用heapify()直接原地调整列表为堆,效率比heappush()逐个添加更高。
示例代码:heapq_heapify.py
import heapq from heapq_showtree import show_tree from heapq_heapdata import data print('原始数据 :', data) heapq.heapify(data) # 核心操作:原地将列表转为堆 print('调整后的堆 :') show_tree(data) |
运行结果(终端输入python3 heapq_heapify.py):
原始数据 : [19, 9, 4, 10, 11] 调整后的堆 : 4 9 19 10 11 ------------------------------------ |
注意哦!heapify()的结果和 “逐个heappush()” 最终得到的堆结构是一致的,但效率更高,推荐在数据已就绪时使用~
五、访问堆内容:取出最小元素
堆建好后,最常用的操作就是取出 “最小元素”(因为heapq是最小堆),核心方法是heappop()。
示例代码:heapq_heappop.py
import heapq from heapq_showtree import show_tree from heapq_heapdata import data print('原始数据 :', data) heapq.heapify(data) print('调整后的堆 :') show_tree(data) print() # 两次弹出最小元素 for i in range(2): smallest = heapq.heappop(data) # 核心操作:弹出最小元素 print(f'弹出最小元素 {smallest:>3}:') show_tree(data) |
运行结果(终端输入python3 heapq_heappop.py):
原始数据 : [19, 9, 4, 10, 11] 调整后的堆 : 4 9 19 10 11 ------------------------------------ 弹出最小元素 4: 9 10 19 11 ------------------------------------ 弹出最小元素 9: 10 11 19 ------------------------------------ |
每次heappop()后,堆都会自动重新调整结构,确保下一次弹出的还是当前最小元素。利用这个特性,咱们甚至可以用 “heapify()+ 多次heappop()” 来实现列表排序~
六、替换元素:heapreplace ()
如果想 “删除堆中最小元素,同时插入新元素”,不用分两步(先heappop()再heappush()),直接用heapreplace()一步搞定,效率更高。
示例代码:heapq_heapreplace.py
import heapq from heapq_showtree import show_tree from heapq_heapdata import data heapq.heapify(data) print('初始堆:') show_tree(data) # 替换两次元素 for n in [0, 13]: smallest = heapq.heapreplace(data, n) # 核心操作:弹出最小+插入新元素 print(f'用 {n:>2} 替换 {smallest:>2}:') show_tree(data) |
运行结果(终端输入python3 heapq_heapreplace.py):
初始堆: 4 9 19 10 11 ------------------------------------ 用 0 替换 4: 0 9 19 10 11 ------------------------------------ 用 13 替换 0: 9 10 19 13 11 ------------------------------------ |
这个方法特别适合维护 “固定大小的堆”,比如 “优先级任务队列”—— 始终只保留优先级最高的 N 个任务,新任务进来时直接替换掉当前优先级最低的任务。
七、快速找极值:nlargest () 与 nsmallest ()
如果想从一堆数据里快速找出 “前 K 个最大元素” 或 “前 K 个最小元素”,heapq的nlargest()和nsmallest()能帮上忙。
示例代码:heapq_extremes.py
import heapq from heapq_heapdata import data print('全部数据 :', data) print('前3个最大元素 :', heapq.nlargest(3, data)) # 前3大 print('排序后取前3大 :', list(reversed(sorted(data)[-3:]))) # 对比:排序法 print('前3个最小元素:', heapq.nsmallest(3, data)) # 前3小 print('排序后取前3小 :', sorted(data)[:3]) # 对比:排序法 |
运行结果(终端输入python3 heapq_extremes.py):
全部数据 : [19, 9, 4, 10, 11] 前3个最大元素 : [19, 11, 10] 排序后取前3大 : [19, 11, 10] 前3个最小元素: [4, 9, 10] 排序后取前3小 : [4, 9, 10] |
小贴士:nlargest()和nsmallest()只在 K 较小时(比如 K=3、5)效率高;如果 K 接近数据总量,直接用sorted()排序后切片反而更快哦~
八、高效合并有序序列:merge ()
如果有多个 “已经排好序的序列”,想把它们合并成一个新的有序序列,heapq.merge()是个好选择 —— 它不用先把所有数据合并再排序,而是用堆动态取最小元素,内存占用特别低。
示例代码:heapq_merge.py
import heapq import random random.seed(2016) # 固定随机种子,保证结果可复现 # 生成4个“已排序的随机序列” data = [] for i in range(4): new_data = list(random.sample(range(1, 101), 5)) # 1-100中随机选5个数 new_data.sort() # 排序,模拟“已有序的序列” data.append(new_data) # 打印原始的4个有序序列 for i, d in enumerate(data): print(f'{i}: {d}') # 合并序列并打印 print('\n合并后的有序序列:') for i in heapq.merge(*data): # 核心操作:合并多个有序序列 print(i, end=' ') print() |
运行结果(终端输入python3 heapq_merge.py):
0: [33, 58, 71, 88, 95] 1: [10, 11, 17, 38, 91] 2: [13, 18, 39, 61, 63] 3: [20, 27, 31, 42, 45] 合并后的有序序列: 10 11 13 17 18 20 27 31 33 38 39 42 45 58 61 63 71 88 91 95 |
关键优势:merge()的内存占用只和 “序列个数” 有关,和每个序列的长度无关。哪怕每个序列有 100 万条数据,合并时也只需要维护 4 个元素的堆,超适合大数据场景!
总结:heapq 模块核心用法
最后给大家整理一下heapq的核心功能,方便记忆:
1.创建堆:heappush()(逐个加)、heapify()(原地调);
2.取最小元素:heappop();
3.替换元素:heapreplace()(弹最小 + 插新);
4.找极值:nlargest(K, 数据)(前 K 大)、nsmallest(K, 数据)(前 K 小);
5.合并有序序列:merge(*序列们)(低内存)。

