heapq.merge是合并多个已排序列表的最优方法,时间复杂度o(n),支持流式处理、内存友好,但不支持key参数和自动去重,需预处理或边合并边去重。

用 heapq.merge 直接合并已排序列表,不额外排序
如果你手上有多个已升序排列的列表(比如从不同文件或数据库分片读出的结果),heapq.merge 是最轻量、最高效的选择。它基于堆实现,时间复杂度是 O(n)(n 为所有元素总数),且不一次性加载全部数据到内存——适合处理大列表。
常见错误是先用 + 拼接再 sorted(),这会触发完整重排序,复杂度升至 O(n log n),还可能爆内存。
-
heapq.merge要求每个输入可迭代对象本身已有序,不校验,错序会导致结果错误 - 支持任意数量的可迭代对象,包括生成器、文件句柄、数据库游标等
- 返回的是迭代器,不是列表;需要列表就包一层
list(),但建议按需消费以节省内存 - 默认按升序合并;若某列表是降序,得先反转或统一预处理,
heapq.merge不支持 per-iterable 的 reverse 参数
合并含重复元素的有序列表时,去重不能靠 set()
如果目标是“合并 + 去重”,别用 list(set(heapq.merge(...))) —— 这会破坏顺序,且 set 无序。正确做法是边合并边去重,利用已排序特性做一次扫描。
示例:合并两个升序列表并跳过重复项
import heapq <p>def merge_unique(<em>iterables): last = object() for item in heapq.merge(</em>iterables): if item != last: yield item last = item</p><h1>使用</h1><p>a = [1, 2, 4, 4, 6] b = [2, 3, 4, 5] list(merge_unique(a, b)) # → [1, 2, 3, 4, 5, 6]</p>
- 该函数保持原始顺序,只跳过紧邻重复项(适用于已排序输入)
- 若需跨段去重(如
[1,3]和[2,3]中的3仍算重复),就得缓存全局 seen 集合,但会增加内存开销 - 不要在
heapq.merge外套dict.fromkeys()或OrderedDict,它们内部仍要遍历全部元素,且不利用有序性
从文件流式读取多个排序块时,避免一次性 readlines()
IO 压力大的本质常是把整文件读进内存。合并多个排序文件时,应让每个文件以生成器方式逐行读取,并确保每行解析后仍保持有序(例如按时间戳或 ID 排序)。
- 用
open(filename)后直接传给heapq.merge不行——文件对象迭代返回字符串,需先转换为可比较类型(如int(line.strip())) - 推荐封装成生成器函数,例如:
def read_sorted_ints(path): yield from (int(line.strip()) for line in open(path)) - 注意文件编码和空行处理,否则
int()或json.loads()会抛ValueError或JSONDecodeError,中断整个合并流程 - 如果文件极大,考虑用
io.BufferedReader或mmap提升读取速度,但对heapq.merge透明,无需修改合并逻辑
Python 3.12+ 中 heapq.merge 的 key 参数仍不可用
有人误以为可以像 sorted(..., key=...) 那样给 heapq.merge 加 key 参数来按字段合并对象,但目前(包括 3.12)它**不支持** key。试图传入会报 TypeError: merge() got an unexpected keyword argument 'key'。
解决办法只有两个:
- 预处理:提前把对象映射为可比键值,例如
[ (obj.timestamp, obj) for obj in iterable ],再按元组合并 - 自定义比较类:实现
__lt__,但仅适用于你完全控制对象类型的情况 - 别用
functools.cmp_to_key包装——它返回的是带__call__的对象,不能用于堆比较逻辑
这个限制容易被文档忽略,查源码或试运行才能确认。实际项目中,预处理成本通常远低于写一个稳定、可维护的自定义堆合并逻辑。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











