堆排序是一种高效的排序算法,它利用堆这种数据结构来实现排序。本文将详细介绍堆的概念、堆的创建、堆排序的实现以及堆的插入和删除操作,并提供完整的C语言代码实现。
堆的基本概念
堆是一种特殊的完全二叉树,它满足以下性质:
-
对于最大堆,每个节点的值都大于或等于其子节点的值
-
对于最小堆,每个节点的值都小于或等于其子节点的值
在本文中,我们将以最大堆为例进行讲解。
堆的存储结构
堆通常用数组来表示,因为堆是完全二叉树,数组可以很好地反映其结构:
-
对于下标为i的节点:(从零开始,下标从1开始的类似)
-
父节点下标:(i-1)/2
-
左子节点下标:2*i+1
-
右子节点下标:2*i+2
-
堆的基本操作实现(大根堆,小根堆类似)
1. 堆的调整(Heapify)
堆调整是堆操作的核心,它确保以某个节点为根的子树满足堆的性质。
// 调整堆,使其满足堆的性质
void 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) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
// 递归调整受影响的子树
heapify(arr, n, largest);
}
}
2. 创建堆(Build Heap)
从无序数组创建堆的过程是从最后一个非叶子节点开始,自底向上进行调整。
// 构建最大堆
void buildHeap(int arr[], int n) {
// 从最后一个非叶子节点开始调整
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
}
3. 堆排序(Heap Sort)
堆排序的基本思想是利用堆的性质,每次取出堆顶元素(最大值),然后调整剩余元素为堆。
// 堆排序函数
void heapSort(int arr[], int n) {
// 首先构建最大堆
buildHeap(arr, n);
// 逐个从堆顶取出元素
for (int i = n - 1; i > 0; i--) {
// 将当前堆顶元素(最大值)与末尾元素交换
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
// 调整剩余元素为堆
heapify(arr, i, 0);
}
}
4. 堆的插入操作
向堆中插入新元素时,先将元素添加到堆的末尾,然后向上调整(sift up)以保持堆的性质。
// 向堆中插入新元素
void insertNode(int arr[], int* n, int key) {
// 增加堆的大小
(*n)++;
int i = (*n) - 1;
// 将新元素插入到堆的末尾
arr[i] = key;
// 向上调整堆
while (i != 0 && arr[(i - 1) / 2] < arr[i]) {
// 交换当前节点与父节点
int temp = arr[i];
arr[i] = arr[(i - 1) / 2];
arr[(i - 1) / 2] = temp;
// 移动到父节点
i = (i - 1) / 2;
}
}
5. 堆的删除操作
通常我们只删除堆顶元素(最大值),删除后将最后一个元素移到堆顶(删除其他元素,一样用堆底替代被删除元素),然后向下调整(sift down)。
// 删除堆顶元素
void deleteRoot(int arr[], int* n) {
// 如果堆为空
if (*n <= 0) {
printf("Heap is empty\n");
return;
}
// 将最后一个元素替换到堆顶
arr[0] = arr[*n - 1];
// 减少堆的大小
(*n)--;
// 从堆顶开始调整堆
heapify(arr, *n, 0);
}
完整示例代码
下面是一个完整的示例程序,演示了如何创建堆、进行堆排序以及插入和删除操作:
#include <stdio.h>
// 函数声明
void heapify(int arr[], int n, int i);
void buildHeap(int arr[], int n);
void heapSort(int arr[], int n);
void insertNode(int arr[], int* n, int key);
void deleteRoot(int arr[], int* n);
void printArray(int arr[], int n);
int main() {
int arr[] = {12, 11, 13, 5, 6, 7};
int n = sizeof(arr) / sizeof(arr[0]);
printf("原始数组: \n");
printArray(arr, n);
// 构建堆
buildHeap(arr, n);
printf("\n构建后的堆: \n");
printArray(arr, n);
// 堆排序
heapSort(arr, n);
printf("\n排序后的数组: \n");
printArray(arr, n);
// 重置数组
int arr2[] = {12, 11, 13, 5, 6, 7};
n = sizeof(arr2) / sizeof(arr2[0]);
buildHeap(arr2, n);
printf("\n\n堆操作演示:\n");
printf("初始堆: \n");
printArray(arr2, n);
// 插入元素
insertNode(arr2, &n, 20);
printf("\n插入20后的堆: \n");
printArray(arr2, n);
// 删除堆顶
deleteRoot(arr2, &n);
printf("\n删除堆顶后的堆: \n");
printArray(arr2, n);
return 0;
}
// 打印数组
void printArray(int arr[], int n) {
for (int i = 0; i < n; ++i)
printf("%d ", arr[i]);
printf("\n");
}
// 调整堆
void 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) {
int temp = arr[i];
arr[i] = arr[largest];
arr[largest] = temp;
heapify(arr, n, largest);
}
}
// 构建堆
void buildHeap(int arr[], int n) {
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
}
// 堆排序
void heapSort(int arr[], int n) {
buildHeap(arr, n);
for (int i = n - 1; i > 0; i--) {
int temp = arr[0];
arr[0] = arr[i];
arr[i] = temp;
heapify(arr, i, 0);
}
}
// 插入节点
void insertNode(int arr[], int* n, int key) {
(*n)++;
int i = (*n) - 1;
arr[i] = key;
while (i != 0 && arr[(i - 1) / 2] < arr[i]) {
int temp = arr[i];
arr[i] = arr[(i - 1) / 2];
arr[(i - 1) / 2] = temp;
i = (i - 1) / 2;
}
}
// 删除堆顶
void deleteRoot(int arr[], int* n) {
if (*n <= 0) {
printf("Heap is empty\n");
return;
}
arr[0] = arr[*n - 1];
(*n)--;
heapify(arr, *n, 0);
}
程序输出示例
运行上述程序将产生类似以下输出:
原始数组:
12 11 13 5 6 7
构建后的堆:
13 11 12 5 6 7
排序后的数组:
5 6 7 11 12 13
堆操作演示:
初始堆:
13 11 12 5 6 7
插入20后的堆:
20 13 12 5 6 7 11
删除堆顶后的堆:
13 11 12 5 6 7
堆排序的时间复杂度分析
-
构建堆:O(n)
-
堆排序:O(n log n)
-
插入操作:O(log n)
-
删除操作:O(log n)
堆排序是一种原地排序算法,不需要额外的存储空间,这使得它在空间复杂度上具有优势(O(1))。
总结
堆排序是一种高效的排序算法,特别适合处理大规模数据。通过本文的介绍和代码实现,我们了解了堆的基本概念、堆的创建、堆排序的实现以及堆的插入和删除操作。掌握这些知识不仅可以帮助你理解堆排序算法,还能为解决优先级队列等相关问题打下基础。
希望这篇博客能帮助你理解堆排序以及相关操作。如果有任何问题或建议,欢迎留言讨论!