一、先解答上次的思考题
对树:10,7,15,3,9,12,18
- 删除 15 后中序:
3 7 9 10 12 18 - 再删除 7 后中序:
3 9 10 12 18
二、今天学习目标
- 什么是堆、大顶堆、小顶堆
- 堆的结构:用数组存完全二叉树
- 核心操作:上浮、下沉
- 堆实现优先队列(完整代码)
三、什么是堆(Heap)?
堆是一棵满足堆序性质的完全二叉树 ,通常用数组存储。
两种常用堆:
- 大顶堆:父节点 ≥ 左右孩子,堆顶是最大值
- 小顶堆:父节点 ≤ 左右孩子,堆顶是最小值
特点:
- 结构是完全二叉树
- 可以用数组高效存储
- 取最值极快:O (1)
- 插入 / 删除:O (log n)
四、数组存储完全二叉树(下标规律)
对于下标 i(从 0 开始):
- 左孩子:
2*i + 1 - 右孩子:
2*i + 2 - 父节点:
(i - 1) / 2
五、核心操作:下沉(向下调整)
以大顶堆为例:如果某个节点比孩子小,就和较大的孩子交换,一直往下沉,直到满足堆性质。
cpp
// 大顶堆下沉调整
void siftDown(int arr[], int n, int i) {
while (1) {
int maxPos = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[maxPos]) maxPos = left;
if (right < n && arr[right] > arr[maxPos]) maxPos = right;
if (maxPos == i) break;
// 交换
int temp = arr[i];
arr[i] = arr[maxPos];
arr[maxPos] = temp;
i = maxPos;
}
}
六、建堆 + 堆实现优先队列(取堆顶)
完整可运行代码
cpp
#include <stdio.h>
// 下沉调整(大顶堆)
void siftDown(int arr[], int n, int i) {
while (1) {
int maxPos = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[maxPos])
maxPos = left;
if (right < n && arr[right] > arr[maxPos])
maxPos = right;
if (maxPos == i)
break;
int temp = arr[i];
arr[i] = arr[maxPos];
arr[maxPos] = temp;
i = maxPos;
}
}
// 建堆
void buildHeap(int arr[], int n) {
// 从最后一个非叶子节点往前建堆
for (int i = n / 2 - 1; i >= 0; i--) {
siftDown(arr, n, i);
}
}
// 删除堆顶(取最大值)
int deleteMax(int arr[], int *n) {
int maxVal = arr[0];
// 最后一个元素放到堆顶
arr[0] = arr[*n - 1];
(*n)--;
siftDown(arr, *n, 0);
return maxVal;
}
// ==================== 测试 ====================
int main() {
int arr[] = {3, 1, 7, 9, 4, 2, 8};
int n = sizeof(arr) / sizeof(arr[0]);
buildHeap(arr, n);
printf("依次取出堆顶(大顶堆):");
while (n > 0) {
printf("%d ", deleteMax(arr, &n));
}
return 0;
}
七、运行结果
cpp
依次取出堆顶(大顶堆):9 8 7 4 3 2 1
八、堆的典型用途
- 优先队列:任务调度、定时器
- 堆排序:O (n log n) 稳定高效
- TopK 问题:找最大 / 最小的 k 个数
九、今日小练习
- 把数组
[5, 2, 8, 1, 3]建成大顶堆 - 依次取出堆顶,观察输出是否降序