数据结构 - 堆
堆(Heap)是一种特殊的完全二叉树结构,满足堆序性质。堆只保证父子节点之间的大小关系,不要求左右子树之间存在严格的顺序。
堆的定义与存储
堆 (Heap) — 最大堆结构与数组存储
树形视图(最大堆)
15索引0
10索引1
8索引2
53
34
25
数组视图(紧凑存储)
15
[0]10
[1]8
[2]5
[3]3
[4]2
[5]父:(i-1)/2 | 左子:2i+1 | 右子:2i+2
完全二叉树 → 天然适合数组,无指针开销
上浮 Sift Up(插入)O(log n)
新元素放末尾 → 与父比较 → 若大则交换 → 重复至堆顶
下沉 Sift Down(删除堆顶)O(log n)
末尾元素移堆顶 → 与较大子比较 → 若小则交换 → 重复至叶
堆的两种类型:
- 最大堆(Max Heap):每个父节点 ≥ 子节点,根节点为最大值
- 最小堆(Min Heap):每个父节点 ≤ 子节点,根节点为最小值
由于堆是完全二叉树,天然适合用数组存储:
| 节点(索引 i) | 关系 | 位置(0-based) |
|---|---|---|
| 父节点 | parent(i) | (i - 1) / 2 |
| 左子节点 | left(i) | 2 * i + 1 |
| 右子节点 | right(i) | 2 * i + 2 |
堆的插入与删除(堆化过程)
实例
#include <stdio.h>
#define MAX 100
/* 最大堆结构 */
struct MaxHeap {
int arr[MAX];
int size; /* 当前元素个数 */
};
void initHeap(struct MaxHeap* h) { h->size = 0; }
void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; }
/* 上浮 (Sift Up):新插入元素从底部向上调整
与父节点比较,若大于父节点则交换,直到满足堆序 */
void siftUp(struct MaxHeap* h, int idx) {
while (idx > 0) {
int parent = (idx - 1) / 2;
if (h->arr[idx] <= h->arr[parent]) break;
swap(&h->arr[idx], &h->arr[parent]);
idx = parent;
}
}
/* 插入:先将元素放在末尾,再上浮调整 O(log n) */
void insert(struct MaxHeap* h, int value) {
if (h->size >= MAX) return;
h->arr[h->size] = value;
siftUp(h, h->size);
h->size++;
}
/* 下沉 (Sift Down):从根向下调整
与较大的子节点比较,若小于则交换,直到满足堆序 */
void siftDown(struct MaxHeap* h, int idx) {
while (1) {
int largest = idx;
int left = 2 * idx + 1;
int right = 2 * idx + 2;
if (left < h->size && h->arr[left] > h->arr[largest])
largest = left;
if (right < h->size && h->arr[right] > h->arr[largest])
largest = right;
if (largest == idx) break;
swap(&h->arr[idx], &h->arr[largest]);
idx = largest;
}
}
/* 删除堆顶(最大值):将末尾元素移到顶部,再下沉调整 O(log n) */
int extractMax(struct MaxHeap* h) {
if (h->size == 0) return -1;
int maxVal = h->arr[0];
h->arr[0] = h->arr[--h->size]; /* 末尾元素移到顶部 */
siftDown(h, 0); /* 下沉调整 */
return maxVal;
}
int main() {
struct MaxHeap h;
initHeap(&h);
int vals[] = {3, 10, 5, 8, 2, 15};
for (int i = 0; i < 6; i++) insert(&h, vals[i]);
printf("依次取出最大值: ");
while (h.size > 0) {
printf("%d ", extractMax(&h));
}
printf("\n"); /* 输出: 15 10 8 5 3 2 (降序) */
return 0;
}
#define MAX 100
/* 最大堆结构 */
struct MaxHeap {
int arr[MAX];
int size; /* 当前元素个数 */
};
void initHeap(struct MaxHeap* h) { h->size = 0; }
void swap(int* a, int* b) { int t = *a; *a = *b; *b = t; }
/* 上浮 (Sift Up):新插入元素从底部向上调整
与父节点比较,若大于父节点则交换,直到满足堆序 */
void siftUp(struct MaxHeap* h, int idx) {
while (idx > 0) {
int parent = (idx - 1) / 2;
if (h->arr[idx] <= h->arr[parent]) break;
swap(&h->arr[idx], &h->arr[parent]);
idx = parent;
}
}
/* 插入:先将元素放在末尾,再上浮调整 O(log n) */
void insert(struct MaxHeap* h, int value) {
if (h->size >= MAX) return;
h->arr[h->size] = value;
siftUp(h, h->size);
h->size++;
}
/* 下沉 (Sift Down):从根向下调整
与较大的子节点比较,若小于则交换,直到满足堆序 */
void siftDown(struct MaxHeap* h, int idx) {
while (1) {
int largest = idx;
int left = 2 * idx + 1;
int right = 2 * idx + 2;
if (left < h->size && h->arr[left] > h->arr[largest])
largest = left;
if (right < h->size && h->arr[right] > h->arr[largest])
largest = right;
if (largest == idx) break;
swap(&h->arr[idx], &h->arr[largest]);
idx = largest;
}
}
/* 删除堆顶(最大值):将末尾元素移到顶部,再下沉调整 O(log n) */
int extractMax(struct MaxHeap* h) {
if (h->size == 0) return -1;
int maxVal = h->arr[0];
h->arr[0] = h->arr[--h->size]; /* 末尾元素移到顶部 */
siftDown(h, 0); /* 下沉调整 */
return maxVal;
}
int main() {
struct MaxHeap h;
initHeap(&h);
int vals[] = {3, 10, 5, 8, 2, 15};
for (int i = 0; i < 6; i++) insert(&h, vals[i]);
printf("依次取出最大值: ");
while (h.size > 0) {
printf("%d ", extractMax(&h));
}
printf("\n"); /* 输出: 15 10 8 5 3 2 (降序) */
return 0;
}
应用场景
| 场景 | 说明 |
|---|---|
| 优先队列 | 堆是优先队列最常用的底层实现 |
| 堆排序 | O(n log n) 原地排序算法(第 18 章详讲) |
| Top K 问题 | 用最小堆维护前 K 个最大元素 |
