参考引用

堆作为完全二叉树的一个特例,具有以下特性
- 最底层节点靠左填充,其他层的节点都被填满
- 将二叉树的根节点称为 “堆顶”,将底层最靠右的节点称为 “堆底”
- 对于大顶堆(小顶堆),堆顶元素(即根节点)的值分别是最大(最小)的

/* 初始化堆 */
priority_queue<int, vector<int>, greater<int>> minHeap; // 初始化小顶堆
priority_queue<int, vector<int>, less<int>> maxHeap; // 初始化大顶堆
/* 元素入堆 */
maxHeap.push(1);
maxHeap.push(3);
maxHeap.push(2);
maxHeap.push(5);
maxHeap.push(4);
/* 获取堆顶元素 */
int peek = maxHeap.top(); // 5
/* 堆顶元素出堆 */
// 出堆元素会形成一个从大到小的序列
maxHeap.pop(); // 5
maxHeap.pop(); // 4
maxHeap.pop(); // 3
maxHeap.pop(); // 2
maxHeap.pop(); // 1
/* 获取堆大小 */
int size = maxHeap.size();
/* 判断堆是否为空 */
bool isEmpty = maxHeap.empty();
/* 输入列表并建堆 */
vector<int> input{1, 3, 2, 5, 4};
priority_queue<int, vector<int>, greater<int>> minHeap(input.begin(), input.end());
完全二叉树非常适合用数组来表示。由于堆正是一种完全二叉树,将采用数组来存储堆。当使用数组表示二叉树时,元素代表节点值,索引代表节点在二叉树中的位置。节点指针通过索引映射公式来实现
如下图所示,给定索引 i,其左子节点索引为 2i+1,右子节点索引为 2i+2,父节点索引为 (i-1)/2(向下取整)

/* 获取左子节点索引 */
int left(int i) {
return 2 * i + 1;
}
/* 获取右子节点索引 */
int right(int i) {
return 2 * i + 2;
}
/* 获取父节点索引 */
int parent(int i) {
return (i - 1) / 2; // 向下取整
}
int peek() {
return maxHeap[0];
}
给定元素 val,首先将其添加到堆底。添加之后,由于 val 可能大于堆中其他元素,堆的成立条件可能已被破坏。因此,需要修复从插入节点到根节点的路径上的各个节点,这个操作被称为堆化(heapify)
考虑从入堆节点开始,从底至顶执行堆化
堆化完成后,最大堆的性质得到修复

/* 元素入堆 */
void push(int val) {
// 添加节点
maxHeap.push_back(val);
// 从底至顶堆化
siftUp(size() - 1);
}
/* 从节点 i 开始,从底至顶堆化 */
void siftUp(int i) {
while (true) {
// 获取节点 i 的父节点
int p = parent(i);
// 当 “越过根节点” 或 “节点无须修复” 时,结束堆化
if (p < 0 || maxHeap[i] <= maxHeap[p])
break;
// 交换两节点
swap(maxHeap[i], maxHeap[p]);
// 循环向上堆化
i = p;
}
}
堆化完成后,最大堆的性质得到修复

/* 元素出堆 */
void pop() {
// 判空处理
if (isEmpty()) {
throw out_of_range("堆为空");
}
// 交换根节点与最右叶节点(即交换首元素与尾元素)
swap(maxHeap[0], maxHeap[size() - 1]);
// 删除节点
maxHeap.pop_back();
// 从顶至底堆化
siftDown(0);
}
/* 从节点 i 开始,从顶至底堆化 */
void siftDown(int i) {
while (true) {
// 判断节点 i, l, r 中值最大的节点,记为 ma
int l = left(i), r = right(i), ma = i;
if (l < size() && maxHeap[l] > maxHeap[ma])
ma = l;
if (r < size() && maxHeap[r] > maxHeap[ma])
ma = r;
// 若节点 i 最大或索引 l, r 越界,则无须继续堆化,跳出
if (ma == i)
break;
swap(maxHeap[i], maxHeap[ma]);
// 循环向下堆化
i = ma;
}
}
实际可以实现一种更为高效的建堆方法,共分为两步
每当堆化一个节点后,以该节点为根节点的子树就形成一个合法的子堆。而由于是倒序遍历,因此堆是 “自下而上” 被构建的
叶节点没有子节点,天然就是合法的子堆,因此无需堆化
- 如以下代码所示,最后一个非叶节点是最后一个节点的父节点,从它开始倒序遍历并执行堆化
/* 构造方法,根据输入列表建堆 */
MaxHeap(vector<int> nums) {
// 将列表元素原封不动添加进堆
maxHeap = nums;
// 堆化除叶节点以外的其他所有节点
for (int i = parent(size() - 1); i >= 0; i--) {
siftDown(i);
}
}
给定一个长度为 n 的无序数组 nums,请返回数组中前 k 大的元素



该方法适用于动态数据流使用场景。在不断加入数据时,可以持续维护堆内的元素,从而实现最大个元素的动态更新
/* 基于堆查找数组中最大的 k 个元素 */
priority_queue<int, vector<int>, greater<int>> topKHeap(vector<int> &nums, int k) {
priority_queue<int, vector<int>, greater<int>> heap;
// 将数组的前 k 个元素入堆
for (int i = 0; i < k; i++) {
heap.push(nums[i]);
}
// 从第 k+1 个元素开始,保持堆的长度为 k
for (int i = k; i < nums.size(); i++) {
// 若当前元素大于堆顶元素,则将堆顶元素出堆、当前元素入堆
if (nums[i] > heap.top()) {
heap.pop();
heap.push(nums[i]);
}
}
return heap;
}