- 「数据结构详解·一」树的初步
- 「数据结构详解·二」二叉树的初步
- 「数据结构详解·三」栈
- 「数据结构详解·四」队列
- 「数据结构详解·五」链表
- 「数据结构详解·六」哈希表
- 「数据结构详解·七」并查集的初步
- 「数据结构详解·八」带权并查集 & 扩展域并查集
- 「数据结构详解·九」图的初步
- 「数据结构详解·十」双端队列 & 单调队列的初步
- 「数据结构详解·十一」单调栈
- 「数据结构详解·十二」有向无环图 & 拓扑排序
- 「数据结构详解·十三」优先队列 & 二叉堆的初步
- 「数据结构详解·十四」对顶堆
1. 对顶堆的概念
对顶堆,是二叉堆的一种应用。它是将处理的数据,分成一个小根堆、一个大根堆,然后可以快速求解出一些东西。
下面我们直接通过例题来理解。
2. 例题详解
2-1. P1168 中位数
我们可以开一个大根堆
q
max
q_{\max}
qmax,维护较小的值;再开一个小根堆
q
min
q_{\min}
qmin,维护较大的值。
每次加入一个数
x
x
x:若
x
x
x 大于大根堆堆顶
t
max
t_{\max}
tmax,则加入小根堆;若
x
≤
t
max
x \le t_{\max}
x≤tmax,则加入大根堆。
为什么要这么做呢?
首先,这样可以保证大根堆的元素值都小于小根堆的元素值。
那么这样的话,对于
t
max
t_{\max}
tmax,有
∣
q
min
∣
|q_{\min}|
∣qmin∣ 个元素大于
t
max
t_{\max}
tmax,有
∣
q
max
∣
−
1
|q_{\max}|-1
∣qmax∣−1 个元素小于等于
t
max
t_{\max}
tmax;对于
t
min
t_{\min}
tmin 是正好相反的。
那么我们要找到当前中位数,只要让
∣
∣
q
max
∣
−
∣
q
min
∣
∣
≤
1
||q_{\max}|-|q_{\min}||\le1
∣∣qmax∣−∣qmin∣∣≤1(即两个堆的元素数差值不大于)即可。
至于怎么做,不难想到,只要元素个数多的堆的堆顶取出,放入元素个数少的堆即可。然后奇数项的中位数就是元素个数多的堆的堆顶。
而时间复杂度,对于查询操作,自然是
O
(
1
)
O(1)
O(1) 的;而每次插入元素后很难不发现最多调整一次,因此总时间复杂度是
O
(
N
log
N
)
O(N\log N)
O(NlogN)。
下面我们来模拟一下样例二。
对于下面的示意图,上面是
q
min
q_{\min}
qmin,下面是
q
max
q_{\max}
qmax。
首先遇到
3
3
3,大根堆为空,加入
q
max
q_{\max}
qmax。
此时中位数为
t
max
=
3
t_{\max}=3
tmax=3。
接着遇到
1
1
1,
1
≤
t
max
=
3
1\le t_{\max}=3
1≤tmax=3,故加入
q
max
q_{\max}
qmax。
注意到
∣
q
max
∣
−
∣
q
min
∣
=
2
>
1
|q_{\max}|-|q_{\min}|=2>1
∣qmax∣−∣qmin∣=2>1,因此将
q
max
q_{\max}
qmax 的堆顶元素调整到
q
min
q_{\min}
qmin 中。
然后遇到
5
5
5,
5
>
t
max
=
1
5> t_{\max}=1
5>tmax=1,故加入
q
min
q_{\min}
qmin。
此时中位数为 t min = 3 t_{\min}=3 tmin=3。
紧接着遇到
9
9
9,
9
>
t
max
=
1
9> t_{\max}=1
9>tmax=1,故加入
q
min
q_{\min}
qmin。
注意到
∣
q
min
∣
−
∣
q
max
∣
=
2
>
1
|q_{\min}|-|q_{\max}|=2>1
∣qmin∣−∣qmax∣=2>1,因此将
q
min
q_{\min}
qmin 的堆顶元素调整到
q
max
q_{\max}
qmax 中。
再下来遇到
8
8
8,
9
>
t
max
=
3
9> t_{\max}=3
9>tmax=3,故加入
q
min
q_{\min}
qmin。
此时中位数为
t
min
=
5
t_{\min}=5
tmin=5。
再接下来遇到
7
7
7,
7
>
t
max
=
3
7> t_{\max}=3
7>tmax=3,故加入
q
min
q_{\min}
qmin。
注意到
∣
q
min
∣
−
∣
q
max
∣
=
2
>
1
|q_{\min}|-|q_{\max}|=2>1
∣qmin∣−∣qmax∣=2>1,因此将
q
min
q_{\min}
qmin 的堆顶元素调整到
q
max
q_{\max}
qmax 中。
最后遇到
6
6
6,
6
>
t
max
=
5
6> t_{\max}=5
6>tmax=5,故加入
q
min
q_{\min}
qmin。
此时中位数为
t
min
=
6
t_{\min}=6
tmin=6。
参考代码:
#include<bits/stdc++.h>
using namespace std;
priority_queue<int>a;
priority_queue<int,vector<int>,greater<int>>b;
int read()
{
int s=0,f=1;
char ch=getchar();
while(ch<'0'||ch>'9')
{
if(ch=='-') f=-1;
ch=getchar();
}
while(ch>='0'&&ch<='9')
{
s=s*10+ch-48;
ch=getchar();
}
return s*f;
}
int main()
{
int n=read();
a.push(read());
printf("%d\n",a.top());
for(int i=2;i<=n;i++)
{
int m=read();
if(m>a.top()) b.push(m);
else a.push(m);
while(abs((int)a.size()-(int)b.size())>1)
{
if(a.size()>b.size())
{
b.push(a.top());
a.pop();
}
else
{
a.push(b.top());
b.pop();
}
}
if(i&1) printf("%d\n",a.size()>b.size()?a.top():b.top());
}
return 0;
}
2-2. P1801 黑匣子
虽然变成了第
k
k
k 小,但其实是同理。
我们仍然可以使用对顶堆解决问题。
只需要改变堆大根堆与小根堆内的元素个数即可。
代码留给读者自行思考。