1. 堆的基本概念
堆是一种特殊的完全二叉树,它满足以下性质:
- 结构性:除最后一层外,其他层的节点都是满的,最后一层的节点都靠左排列。
- 堆序性:父节点的值总是大于或等于(最大堆)或小于或等于(最小堆)其子节点的值。
堆通常用数组实现,对于数组中的任意元素i:
- 左子节点:2i + 1
- 右子节点:2i + 2
- 父节点:(i - 1) / 2
2. 最大堆(Max Heap)
在最大堆中,父节点的值总是大于或等于其子节点的值。根节点是整个堆中的最大元素。
最大堆的操作
下面是最大堆的基本操作实现:
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void max_heapify(int arr[], int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest])
largest = left;
if (right < n && arr[right] > arr[largest])
largest = right;
if (largest != i) {
swap(&arr[i], &arr[largest]);
max_heapify(arr, n, largest);
}
}
void build_max_heap(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--)
max_heapify(arr, n, i);
}
void insert_max_heap(int arr[], int *n, int key) {
if (*n >= MAX_SIZE) {
printf("堆已满,无法插入\n");
return;
}
(*n)++;
int i = *n - 1;
arr[i] = key;
while (i > 0 && arr[(i-1)/2] < arr[i]) {
swap(&arr[i], &arr[(i-1)/2]);
i = (i-1)/2;
}
}
int extract_max(int arr[], int *n) {
if (*n <= 0) {
printf("堆为空\n");
return -1;
}
int max = arr[0];
arr[0] = arr[*n - 1];
(*n)--;
max_heapify(arr, *n, 0);
return max;
}
void print_heap(int arr[], int n) {
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[MAX_SIZE] = {4, 10, 3, 5, 1};
int n = 5;
printf("原始数组: ");
print_heap(arr, n);
build_max_heap(arr, n);
printf("构建最大堆后: ");
print_heap(arr, n);
insert_max_heap(arr, &n, 15);
printf("插入15后: ");
print_heap(arr, n);
int max = extract_max(arr, &n);
printf("提取最大值 %d 后: ", max);
print_heap(arr, n);
return 0;
}
结果:
原始数组: 4 10 3 5 1
构建最大堆后: 10 5 3 4 1
插入15后: 15 5 10 4 1 3
提取最大值 15 后: 10 5 3 4 1
应用场景
- 优先队列:最大堆可以高效地实现优先队列,其中优先级最高的元素总是在堆顶。
- 任务调度:在操作系统中,可以用最大堆来管理进程优先级。
- 热门项目排行:比如在社交媒体平台中,可以用最大堆来维护热门话题或帖子的排行。
3. 最小堆(Min Heap)
最小堆与最大堆相反,父节点的值总是小于或等于其子节点的值。根节点是整个堆中的最小元素。
最小堆的操作
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void min_heapify(int arr[], int n, int i) {
int smallest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] < arr[smallest])
smallest = left;
if (right < n && arr[right] < arr[smallest])
smallest = right;
if (smallest != i) {
swap(&arr[i], &arr[smallest]);
min_heapify(arr, n, smallest);
}
}
void build_min_heap(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--)
min_heapify(arr, n, i);
}
void insert_min_heap(int arr[], int *n, int key) {
if (*n >= MAX_SIZE) {
printf("堆已满,无法插入\n");
return;
}
(*n)++;
int i = *n - 1;
arr[i] = key;
while (i > 0 && arr[(i-1)/2] > arr[i]) {
swap(&arr[i], &arr[(i-1)/2]);
i = (i-1)/2;
}
}
int extract_min(int arr[], int *n) {
if (*n <= 0) {
printf("堆为空\n");
return -1;
}
int min = arr[0];
arr[0] = arr[*n - 1];
(*n)--;
min_heapify(arr, *n, 0);
return min;
}
void print_heap(int arr[], int n) {
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[MAX_SIZE] = {4, 10, 3, 5, 1};
int n = 5;
printf("原始数组: ");
print_heap(arr, n);
build_min_heap(arr, n);
printf("构建最小堆后: ");
print_heap(arr, n);
insert_min_heap(arr, &n, 0);
printf("插入0后: ");
print_heap(arr, n);
int min = extract_min(arr, &n);
printf("提取最小值 %d 后: ", min);
print_heap(arr, n);
return 0;
}
结果:
原始数组: 4 10 3 5 1
构建最小堆后: 1 4 3 5 10
插入0后: 0 1 3 5 10 4
提取最小值 0 后: 1 4 3 5 10
应用场景
- Dijkstra算法:用于在图中找到最短路径。
- 合并K个排序链表:可以高效地合并多个已排序的链表。
- 数据流中位数:使用一个最大堆和一个最小堆可以高效地维护数据流的中位数。
4. 堆排序(Heap Sort)
堆排序是一种基于比较的排序算法,它利用堆的性质来进行排序。
堆排序的实现
#include <stdio.h>
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
void max_heapify(int arr[], int n, int i) {
int largest = i;
int left = 2 * i + 1;
int right = 2 * i + 2;
if (left < n && arr[left] > arr[largest])
largest = left;
if (right < n && arr[right] > arr[largest])
largest = right;
if (largest != i) {
swap(&arr[i], &arr[largest]);
max_heapify(arr, n, largest);
}
}
void build_max_heap(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--)
max_heapify(arr, n, i);
}
void heap_sort(int arr[], int n) {
build_max_heap(arr, n);
for (int i = n - 1; i > 0; i--) {
swap(&arr[0], &arr[i]);
max_heapify(arr, i, 0);
}
}
void print_array(int arr[], int n) {
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
printf("\n");
}
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组: ");
print_array(arr, n);
heap_sort(arr, n);
printf("排序后的数组: ");
print_array(arr, n);
return 0;
}
结果:
原始数组: 12 11 13 5 6 7
排序后的数组: 5 6 7 11 12 13
应用场景
- 大规模数据排序:堆排序在处理大量数据时表现良好。
- 外部排序:当数据量太大无法一次性加载到内存时,可以使用堆排序。
- K个最大/最小元素:可以使用堆来高效地找出一个大数据集中的K个最大或最小元素。
5. 总结
堆是一种非常有用的数据结构,它在许多重要的算法和应用中扮演着关键角色。最大堆和最小堆各有其特点和应用场景,而堆排序则是一种高效的排序算法。
理解和掌握堆的概念和操作对于提高算法设计和问题解决能力非常重要。在实际应用中,我们常常需要根据具体问题选择合适的堆类型和操作来优化算法性能。