PrepStack · Markdown HTML

双堆维护数据流中位数

堆、选择与流 · HTML

数学 / 逻辑形态

lower 为 max-heap,upper 为 min-heap;max(lower) <= min(upper),且 lower 与 upper 等大或 lower 恰多一项。

识别信号

容易误判的相似信号

SignalWhy it is different
静态数组只查一次Quickselect 期望线性;双堆为在线能力支付 log n。
滑动窗口中位数仍可用双堆,但必须加 delayed deletion、有效大小计数和堆顶清理。
值域很小频次桶与秩扫描可能更简单,并天然支持删除。

核心不变量

lower 中每项不大于 upper 中每项;lower.size 等于 upper.size 或恰好多 1。

状态、转移与处理顺序

ItemDefinition
State存负值的 lower 小根堆、upper 小根堆;删除版另有 delayed 计数与逻辑大小。
Transition按 lower 顶把新值放入一侧,再把超出大小约定的一侧堆顶移动到另一侧。
Frontier / orderlower 顶是较小半区最大值,upper 顶是较大半区最小值;它们构成中位边界。

Python 骨架

from heapq import heappop, heappush

class MedianStream:
    def __init__(self):
        self.lower, self.upper = [], []

    def add(self, x):
        if not self.lower or x <= -self.lower[0]:
            heappush(self.lower, -x)
        else:
            heappush(self.upper, x)
        if len(self.lower) > len(self.upper) + 1:
            heappush(self.upper, -heappop(self.lower))
        elif len(self.upper) > len(self.lower):
            heappush(self.lower, -heappop(self.upper))

    def median(self):
        if not self.lower:
            raise ValueError('empty stream')
        if len(self.lower) > len(self.upper):
            return -self.lower[0]
        return (-self.lower[0] + self.upper[0]) / 2

正确性思路

  1. 按边界放入后,跨堆有序性保持。
  2. 若一侧过大,其堆顶是最靠近分界且唯一应跨界的元素;移动它同时恢复大小并保持顺序。
  3. 大小规则使中间一个或两个秩恰位于堆顶,因此查询正确。

复杂度

TimeSpaceParameters
add O(log n),median O(1)O(n)n 为当前有效元素数。

边界条件

错误模式

PatternWhy it fails / fix
只平衡大小两个随机半区即使等大,堆顶也不是中位边界;还必须维护跨堆顺序。
lower 的负号遗漏比较、移动、返回必须区分物理负值与逻辑值。
滑窗用 list.remove破坏堆复杂度;应 delayed counter,并维护有效大小。
未约定哪边多一项插入、再平衡和奇数查询会彼此矛盾。

选择与排除规则

相关模板

RelationTemplateBoundary

原创微型例子

环节内容
Result边界为 4 与 6,中位数为 5。
Setup温度流依次为 4,10,6,2。
Trace4 在 lower;10 在 upper;6 先入 upper 后移到 lower;2 入 lower 后把 6 移回 upper。

Recall prompts

我的补充