处理动态中位数等问题,灵活运用了堆的性质,本质是维护两个堆。

大根堆Q1:维护集合中较小值的部分的最大值。

小根堆Q2:维护集合中较大值的部分的最小值。

注意到两个堆中的元素各自是单调的,两个堆间也是单调的。也就是说,Q1中的任何一个元素都不大于Q2中的任何一个元素。

那么假设高度为权值,两个堆可以形象化的表示成:

对顶堆_中位数

如果两个堆的大小相差不超过1,较大的那个堆的堆顶必定是中位数(偶数个数时中位数是排序后中间的两个之一)

UPD: 图中的 Q1和 Q2 标反了,权值是越高越大。

具体操作



  • 插入
    先找到当前正确的集合插入,比较与堆顶的大小关系,大于小根堆的堆顶就插入大根堆,反之小根堆。
    调整两个堆的大小关系,因为两个堆不管是总体上还是各自内都是满足单调性的,所以每次取更大的堆的堆顶,插入另一个堆,直到两个堆的大小相差不超过1。
    代码使用的是STL的priority_queuepriority_queue,默认大根堆所以Q1需要通过相反数实现。

void insert(int x)
{
if (!q2.size() || x > q2.top())
q2.push(x);
else
q1.push(x);
if (q1.size() > q2.size() + 1) {
q2.push(q1.top());
q1.pop();
}
if (q2.size() > q1.size() + 1) {
q1.push(q2.top());
q2.pop();
}
}
  • 查询直接输出比较大的堆的堆顶就好,注意Q1的输出。
if (i & 1){
printf("%d ", q1.size() > q2.size() ? q1.top() : q2.top());
}
  • 扩展
    动态K大值:维护思想不变,只需要让Q1的大小为K即可。
    带删除K大值//中位数:手写可删堆//打标记懒惰删除法