堆 / 优先队列
💡 核心思想
堆是一种特殊的完全二叉树,满足堆性质:大根堆中每个节点的值 ≥ 子节点的值。C++ STL 的 priority_queue 默认是大根堆。支持 $O(\log n)$ 插入和取出最值,$O(1)$ 查看最值。常用于 Dijkstra、贪心、Top-K 等问题。
竞赛中几乎不需要手写堆——
priority_queue 够用了。但你需要知道它的原理:上浮(push)和下沉(pop)。小根堆的声明是 priority_queue, greater> 。堆的一个经典应用是"合并果子"(哈夫曼树的贪心构造)。🎯 直觉理解
堆就像一个"自动排序"的队伍。无论你什么时候加人进来,最厉害的那个人(大根堆)或最弱的那个(小根堆)永远在最前面。你想找最值不用翻遍整个队伍,直接看第一个就行。
📝 算法流程
- C++ 默认大根堆:priority_queue
- 小根堆:priority_queue
, greater > - push:$O(\log n)$ 插入
- pop:$O(\log n)$ 取出最值
$$\text{堆操作:插入/删除 } O(\log n),\text{取最值 } O(1)$$
📊 复杂度分析
| 指标 | 复杂度 |
|---|---|
| 时间 | 插入/删除 $O(\log n)$,取最值 $O(1)$ |
| 空间 | $O(n)$ |
💻 参考实现(C++)
C++ (C++17)
#include <bits/stdc++.h>
using namespace std;
int main() {
int n; cin >> n;
priority_queue<int, vector<int>, greater<int>> pq; // 小根堆
for (int i = 0; i < n; i++) { int x; cin >> x; pq.push(x); }
long long ans = 0;
while (pq.size() > 1) {
int a = pq.top(); pq.pop();
int b = pq.top(); pq.pop();
ans += a + b;
pq.push(a + b);
}
cout << ans << endl; // 合并果子
return 0;
}⚠️ 常见坑点
小根堆声明语法复杂容易写错
堆中存结构体需要重载 < 或传比较器
priority_queue 没有 decrease-key 操作(Dijkstra用懒惰删除代替)
自定义比较器时要确保严格弱序
📚 相关题目
| 题目 | 来源 | 难度 | 备注 |
|---|---|---|---|
| P1090 合并果子 | 洛谷 | CSP-J | 贪心+堆 |
| P1168 中位数 | 洛谷 | CSP-S | 对堆求中位数 |