双堆法优于sorted()因支持O(log n)实时插入与中位数查询,适用于流式数据;用负值模拟大顶堆,通过两次搬运维持堆平衡与语义一致性。

为什么不用 sorted() 而要双堆
因为实时流式数据或在线插入场景下,每次调用 sorted() 或 numpy.median() 都是 O(n log n),而双堆法能将单次插入+中位数查询压到 O(log n)。你不是在处理静态列表,而是在持续接收新数字(比如监控延迟、实时报价),需要每加一个数就立刻知道当前中位数。
关键约束:两个堆必须时刻保持大小平衡——小顶堆(存较大一半)的大小 ≥ 大顶堆(存较小一半)的大小,且差值不超过 1。
heapq 没有大顶堆,怎么模拟
Python 的 heapq 只支持小顶堆,所以大顶堆得靠「存负值」来绕过。比如想把 3, 1, 4 建成大顶堆,实际 push -3, -1, -4,pop 时再取负还原。
常见错误:直接对正数建堆后手动取反逻辑混乱,或忘记在 heappushpop 后还原符号,导致中位数计算错位。
立即学习“Python免费学习笔记(深入)”;
- 大顶堆操作统一用
-x入堆、-heap[0]查顶、-heapq.heappop(heap)出堆 - 小顶堆保持原值,直接操作
- 别混用:不要对大顶堆变量调
heapq.heappush(heap, x)(漏了负号)
如何维持两堆大小平衡并保证堆序
每次插入新数 num,先粗略扔进大顶堆(即 heappush(max_heap, -num)),再把大顶堆顶(最大值)弹出、送入小顶堆;接着检查小顶堆是否过大(size > 大顶堆 size),若是,就把小顶堆最小值弹出、送回大顶堆。这样能自动把数按大小“筛”到正确堆里。
这个策略比“先比大小再决定进哪个堆”更鲁棒——它不依赖初始判断,靠两次“搬运”自然收敛到平衡状态,避免边界 case(如连续插入递增序列)下堆倾斜。
示例代码核心片段:
heapq.heappush(max_heap, -num)
heapq.heappush(min_heap, -heapq.heappop(max_heap))
if len(min_heap) > len(max_heap):
heapq.heappush(max_heap, -heapq.heappop(min_heap))
取中位数时的三种 size 组合怎么处理
双堆法中位数只取决于堆顶,但具体逻辑要看总长度奇偶和两堆大小关系:
- 总长为奇数 → 小顶堆多一个,中位数就是
min_heap[0] - 总长为偶数且两堆等长 → 中位数是
(-max_heap[0] + min_heap[0]) / 2 - 绝不会出现大顶堆比小顶堆多的情况(上面的平衡步骤已排除)
容易漏掉的是:当只插入一个数时,max_heap 为空,不能直接取 -max_heap[0] —— 必须确保访问前堆非空,或者初始化时各 push 一个哨兵值(如 float('-inf') 和 float('inf')),但更稳妥的做法是用 len() 判断分支。
真正的难点不在堆操作本身,而在维护「两堆语义一致性」:大顶堆顶必须 ≤ 小顶堆顶。一旦因插入顺序或手动修改破坏这点,后续所有中位数都会错。建议每次插入后加一句断言:assert not max_heap or not min_heap or -max_heap[0] 。


















