堆排序演算法的設計與分析
問題描述:設計並分析堆排序
【前置知識:什麼是堆?】
堆(Heap)是一種特殊的樹形資料結構,它滿足以下兩個條件之一:
- 最大堆(Max Heap):
- 每個節點的值都大於或等於其子節點的值。
- 換句話說,根節點的值是整個堆中最大的。
- 最小堆(Min Heap):
- 每個節點的值都小於或等於其子節點的值。
- 換句話說,根節點的值是整個堆中最小的。
堆的基本特點
- 完全二叉樹:堆通常被實現為完全二叉樹,也就是說,除了最後一層,所有層都是滿的,而且最後一層的節點都是從左到右排列的。
- 堆的性質:對於最大堆,任意節點的值都大於或等於其子節點的值;對於最小堆,任意節點的值都小於或等於其子節點的值。
堆的操作
堆支援以下基本操作:
- 插入(Insert):
- 在堆中插入一個新元素,首先將新元素放在堆的末尾,然後透過上浮操作使其恢復堆的性質。
- 刪除(Delete):
- 刪除堆頂元素(最大堆的最大值或最小堆的最小值),用堆的最後一個元素替換堆頂元素,然後透過下沉操作使其恢復堆的性質。
- 堆化(Heapify):
- 將一個無序陣列調整為堆結構,可以透過自下而上的方法調整樹的每個節點來實現。
堆的應用
堆有很多應用場景,包括但不限於:
- 優先佇列(Priority Queue):堆可以高效地實現優先佇列,支援快速插入和刪除操作。
- 堆排序(Heap Sort):一種利用堆的性質進行排序的演算法。
- 求中位數:使用兩個堆(一個最大堆和一個最小堆)可以高效地求動態資料流的中位數。
【演算法設計思想】
構建最大堆:
- 從無序陣列構建一個最大堆。最大堆是指每個節點的值都大於或等於其子節點的值,這樣堆頂元素(即陣列的第一個元素)就是整個陣列的最大值。
交換堆頂和堆的最後一個元素:
- 將堆頂元素(即最大值)和堆的最後一個元素交換,這樣最大值就被放到陣列的末尾,接下來只需對前面部分進行堆調整即可。
堆調整:
- 調整交換後的堆,使其再次成為最大堆。這個過程稱為堆調整(Heapify),確保交換後的堆依然滿足堆的性質。
重複步驟2和步驟3:
- 不斷將堆頂元素和堆的最後一個元素交換,然後對前面的部分進行堆調整,直到所有元素都被排序。
【演算法描述】
// 堆調整函式,維護最大堆性質
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) {
swap(&arr[i], &arr[largest]);
// 遞迴呼叫heapify,維護堆性質
heapify(arr, n, largest);
}
}
// 主函式,進行堆排序
void heapSort(int arr[], int n) {
// 構建最大堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 一個個取出元素進行排序
for (int i = n - 1; i > 0; i--) {
// 將當前根節點移到末尾
swap(&arr[0], &arr[i]);
// 調整堆
heapify(arr, i, 0);
}
}
【完整的測試程式】
#include <stdio.h>
// 交換兩個元素的值
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
// 堆調整函式,維護最大堆性質
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) {
swap(&arr[i], &arr[largest]);
// 遞迴呼叫heapify,維護堆性質
heapify(arr, n, largest);
}
}
// 主函式,進行堆排序
void heapSort(int arr[], int n) {
// 構建最大堆
for (int i = n / 2 - 1; i >= 0; i--)
heapify(arr, n, i);
// 一個個取出元素進行排序
for (int i = n - 1; i > 0; i--) {
// 將當前根節點移到末尾
swap(&arr[0], &arr[i]);
// 調整堆
heapify(arr, i, 0);
}
}
// 列印陣列
void printArray(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("排序前的陣列:\n");
printArray(arr, n);
heapSort(arr, n);
printf("排序後的陣列:\n");
printArray(arr, n);
return 0;
}