前言
在理解了堆的基础概念并实现基本结构后,我们来看两个核心应用场景:高效的堆排序算法和解决海量数据筛选的 TopK 问题。
一、建堆基础
建堆是将无序数组转化为符合堆规则的完全二叉树的过程。这是堆排序和 TopK 等所有应用的前提。相比于逐个插入元素(向上调整),基于数组原地实现的向下调整建堆,空间复杂度仅为 O(1),且效率更高。
1.1 向上调整算法回顾
场景: 假设已有一个小堆,现在要插入一个新元素并保持堆结构。
思路: 新元素插入堆尾后,如果它比父节点更小(小堆性质),就需要'上浮'交换,直到满足堆序或到达根节点。
示例分析:
假设原小堆为 [15, 18, 19, 25, 28, 34, 65, 49, 27, 37],size=10。插入元素 10 到索引 10 处。
- 计算父节点索引:
parent = (child - 1) / 2,即索引 4,值为 28。 - 比较
arr[child](10) 与arr[parent](28)。10 < 28,不满足小堆,交换。 - 更新 child 和 parent 索引,继续向上比较,直到满足条件或到达根。
void AdjustUp(HPDataType* a, int child) {
// 已知孩子节点的下标为 child,父亲节点下标为 (child-1)/2
int parent = (child - 1) / 2;
while (child > 0) {
if (a[child] < a[parent]) {
swap(&a[child], &a[parent]);
child = parent;
parent = (child - 1) / 2;
} else {
break;
}
}
}
若构建大堆,只需将比较符号反转(< 变为 >)。
1.2 利用向上调整建堆
通过循环调用 AdjustUp,对数组中每个元素进行向上调整,即可完成建堆。但这种方法的时间复杂度是 O(N log N),因为每个节点都可能上浮到根。
1.3 向下调整算法回顾
场景: 假设堆顶元素被替换为一个更大的值(破坏小堆性质),需要修复。
思路: 当父节点位置不合适时,让父节点'下沉'。找到左右孩子中较小的一个(小堆),如果父节点比它大,则交换,继续向下调整,直到到达叶节点或满足堆序。
关键点: 判断是否到达叶节点,只需检查子节点索引是否在有效范围内。
void AdjustDown(HPDataType* a, int size, int parent) {
child = parent * + ;
(child < size) {
(child + < size && a[child + ] < a[child]) {
child++;
}
(a[child] < a[parent]) {
swap(&a[child], &a[parent]);
parent = child;
child = parent * + ;
} {
;
}
}
}


