一、二叉堆的定义与特性
二叉堆是一种基于完全二叉树的数据结构,分为最大堆和最小堆两类:
- 最大堆:任意父节点的键值 ≥ 子节点;
- 最小堆:任意父节点的键值 ≤ 子节点;
- 完全二叉树特性:除最后一层外,其他层节点全满,最后一层节点左对齐。
二、存储结构与索引计算
二叉堆通常用数组实现,根据根节点下标不同分为两种存储方式:
- 根节点下标为1:
- 子节点位置:
2n
(左)、2n+1
(右) - 父节点位置:
n//2
(向下取整)
- 子节点位置:
- 根节点下标为0:
- 子节点位置:
2n+1
(左)、2n+2
(右) - 父节点位置:
⌊(n-1)/2⌋
- 子节点位置:
示例:根节点为1的堆数组
[_,1,2,3,4,5,6,7]
(首元素占位),节点3的子节点为6和7
三、核心操作与算法
1. 插入操作(上浮)
- 步骤:插入到数组末尾,逐层与父节点比较并交换,直到满足堆性质;
- 时间复杂度: O(log n)
2. 删除堆顶(下沉)
- 步骤:
- 用末尾元素替换堆顶;
- 从根节点开始,与较大(最大堆)/较小(最小堆)子节点交换,直到满足堆性质;
- 时间复杂度:O(log n)
3. 建堆算法
- 自顶向下法:逐个插入元素,总时间复杂度O(n log n);
- 自底向上法:从最后一个非叶子节点开始调整(Floyd算法),时间复杂度O(n)。
四、性能与应用场景
- 时间复杂度:
- 查询极值:O(1);
- 插入/删除:O(log n);
- 合并堆:O(n+k)(数组拼接后重建堆)。
- 典型应用:
- 堆排序:通过反复取出堆顶实现排序;
- 优先队列:任务调度、Dijkstra最短路径算法;
- Top K问题:维护大小为K的最小堆快速筛选最大元素。
代码实现
#include <bits/stdc++.h>
using namespace std;
class PriorityQueue {
private:
vector<int> a; // 使用vector来存储堆的元素
// 上浮操作,用于在插入新元素后恢复堆的性质
void up() {
int n = a.size(); // 获取堆的大小
int i = n - 1; // 新插入元素的位置
// 当元素不是根节点且比父节点小的时候,执行上浮操作
while (i > 0 && a[i/2] > a[i]) {
swap(a[i/2], a[i]); // 交换当前节点和父节点的值
i /= 2; // 更新当前节点索引为其父节点的索引
}
}
// 下沉操作,用于在删除堆顶元素后恢复堆的性质
void down() {
int n = a.size(); // 获取堆的大小
int i = n - 1; // 最后一个元素的位置
a[0] = a[i]; // 将最后一个元素移动到堆顶
a.pop_back(); // 删除最后一个元素
i--; // 更新堆的大小
int cur = 0; // 当前节点位置
int nxt; // 下一个节点位置
// 当当前节点有子节点时,执行下沉操作
while (cur <= i) {
nxt = cur << 1; // 计算左子节点的位置
// 如果右子节点存在且小于左子节点,选择右子节点
if (cur << 1 | 1 <= i && a[cur << 1] > a[cur << 1 | 1]) {
nxt = cur << 1 | 1;
}
// 如果当前节点小于其子节点,则不需要继续下沉
if (nxt > i || a[cur] <= a[nxt]) break;
swap(a[cur], a[nxt]); // 交换当前节点和最小子节点的值
cur = nxt; // 更新当前节点索引为最小子节点的索引
}
}
public:
PriorityQueue() {
a.clear(); // 构造函数,初始化堆
}
// 向堆中添加一个元素
void push(int v) {
a.push_back(v); // 将新元素添加到数组的末尾
up(); // 执行上浮操作以恢复堆的性质
}
// 获取堆顶元素
int top() {
return a[0]; // 堆顶元素即为数组的第一个元素
}
// 删除堆顶元素
void pop() {
down(); // 执行下沉操作以恢复堆的性质
}
};
int main() {
ios::sync_with_stdio(false); // 关闭同步,提高cin和cout的效率
cin.tie(0); // 解除cin和cout的绑定
cout.tie(0); // 解除cout的绑定
auto q = PriorityQueue(); // 创建一个优先队列对象
int n;
cin >> n; // 读取操作的数量
for (int i = 0; i < n; i++) {
int op;
cin >> op; // 读取操作类型
switch (op) {
case 1:
int x;
cin >> x; // 读取要添加的元素
q.push(x); // 将元素添加到优先队列
break;
case 2:
cout << q.top() << endl; // 打印堆顶元素
break;
case 3:
q.pop(); // 删除堆顶元素
break;
}
}
return 0;
}