一.对顶堆
1.作用
可以动态维护一个序列上第 k 大的数,k 值会发生变化。比写 线段树 或 BST 简单。
2.算法思想
对顶堆由一个大根堆与一个小根堆组成,小根堆维护前 k 大的数(包含第 k 个,小根堆装大的数),大根堆维护比第 k 大数小的数(大根堆装小的数)
- 插入:若插入的元素 ≥ 小根堆堆顶元素,则将其插入小根堆,否则将其插入大根堆。
- 维护:当小根堆的大小 > k 时,不断将小根堆堆顶元素取出并插入大根堆,直到小根堆的大小等于 k;
当小根堆的大小 < k 时,不断将大根堆堆顶元素取出并插入小根堆,直到小根堆的大小等于 k。 - 查询第 k 大元素:小根堆堆顶元素。
- 删除第 k 大元素:删除小根堆堆顶元素。
题意:给你一个数x,之后每次固定向里面加入两个数字,之后每次求这个序列的中位数
解题思路:
这是动态求第k大的问题,我们使用对顶堆
cpp
#include<iostream>
#include<queue>
#include<vector>
using namespace std;
class DuiDing {
private:
// 大根堆:存较小的那一半数
priority_queue<int> big;
// 小根堆:存较大的那一半数(前 k 大)
priority_queue<int, vector<int>, greater<int>> small;
public:
// 插入单个元素
void insert(int x, int k) {
// 1. 决定放入哪个堆
// 注意:如果 small 为空,或者 x 比 small 的堆顶大(或相等),放入 small
if (small.empty() || x >= small.top()) {
small.push(x);
}
else {
big.push(x);
}
// 2. 平衡两个堆的大小
// 情况 A: small 里的数太多了(超过了 k 个),把最小的移给 big
while (small.size() > k) {
big.push(small.top());
small.pop();
}
// 情况 B: small 里的数太少了(不足 k 个),且 big 里有货,把最大的移给 small
// 判断,防止空指针异常
while (small.size() < k && !big.empty()) {
small.push(big.top());
big.pop();
}
}
// 获取第 k 大
// 增加了一个安全检查,防止在元素不足 k 个时访问空堆
int get_k() {
if (small.empty()) return -1; // 或者抛出异常
return small.top();
}
};
int main() {
int x; cin >> x;
int q; cin >> q;
DuiDing d;
int cnt = 1;
d.insert(x, 1);
while (q--) {
vector<int>num(2);
int a, b; cin >> a >> b;
d.insert(a, ++cnt);
d.insert(b, cnt);
cout << d.get_k()<<'\n';
}
return 0;
}