堆數據結構是一種數組對象,它可以被視為一棵完全二叉樹。
二叉堆有兩種:最大堆和最小堆。
最大堆的特性是指除了根以外每個節點的值最多和其父節點的值一樣大。
堆可以被看成是一棵樹,其高度為。(練習證明)
保持堆的性質
1 /* 2 *這個函數是維持堆的性質,注意當調用這個函數的時候,我們假定該節點的左右兒子樹都是最大堆。 3 *但是有可能該節點小於它的子樹,所以通過這個函數使該節點下降,使以該節點為根的子樹成為最大堆。 4 */ 5 void max_heapify(int A[], int length, int i){ 6 int l = 2 * i; //左兒子節點下標 7 int r = 2 * i + 1; //右兒子節點下標 8 int largest; //記錄該節點與左右兒子節點中值最大的下標 9 10 if(l <= length && A[l] > A[i]) 11 largest = l; 12 else 13 largest = i; 14 15 if(r <= length && A[r] > A[largest]) 16 largest = r; 17 18 if(largest != i){ 19 int temp = A[largest]; 20 A[largest] = A[i]; 21 A[i] = temp; 22 max_heapify(A, length, largest); 23 } 24 }
過程如下圖
函數作用與一棵以節點i為根的、大小為n的子樹上時,調整節點i與其兒子的關系時,所用時間為,再加上對以i的某個子節點為根的子樹調用維持堆的性質的函數所需時間為:
T(n) <= T(2n/3) + (最壞情況為最底層恰好半滿)(PS:why is 2n/3? 第一層都倒數第二層節點個數為:1、2、4.......2^x、2^x。2^x / (2^(x-1) + 2^x + 2^x) = 2n/3)
根據主定理(維基)的情況二,遞歸式的解為T(n) =
建堆
1 /* 2 *練習6.3-2:為什么從下標floor(length/2)降到1,而不是從1升到floor(legnth/2)? 3 *max_heapify函數假設左右兒子為根的二叉樹都為最大堆,如果從1開始的話,其左右兒子為根的二叉樹不一定為最大堆。 4 *而從floor(length/2)開始則可以保證左右兒子為根的二叉樹都是最大堆 5 */ 6 void build_max_heap(int A[], int length){ 7 int i; 8 for(i = length/2; i >= 1; i--) 9 max_heapify(A, length, i); //調用維持最大堆的性質函數 10 }
過程如下圖:
這個函數的上界可以為O(n * lgn)。O(n)次調用維持最大堆的性質函數(O(lgn))。但是不是每個節點調用這個函數所需的時間一致。
一個n元素的堆的高度為floor(lgn),在任意高度h上,最多有ceiling(n/2^(h+1))個節點(PS:證明稍候上)
然后直接上具體證明運行時間界O(n)(PS:本人數學不是很好。。。。)
堆排序算法
/* *數組A[1...n]的最大元素為A[1],通過與A[n]交換達到最終正確的位置。原來根的子女依然是最大堆, *但是新的根元素可能違背最大堆的性質。因此要調用維持最大堆性質的函數 *然后在A[1...n-1]里重復這個過程。 */ void heap_sort(int A[], int length){ int i; int size = length; build_max_heap(A, length); //建堆 for(i = length; i >= 2; i--){ int temp = A[i]; A[i] = A[1]; A[1] = temp; //交換,將最大的元素放到數組的還沒排序的尾部。 size--; max_heapify(A, size, 1); //調用維持最大堆的性質的函數(此時根節點的左右兒子子樹為最大堆) } }
過程如下圖:
該過程的時間代價為O(n * lgn),調用建堆的時間為O(n),n-1次調用維持最大堆的性質(O(lgn))

1 #include <stdio.h> 2 #include <stdlib.h> 3 4 /* 5 int parent(int i); //返回父節點下標 6 7 int left(int i); //返回左兒子節點下標 8 9 int right(int i); //返回右兒子節點下標 10 */ 11 12 void max_heapify(int A[], int length, int i); //保持堆的性質 13 14 void build_max_heap(int A[], int length); //在輸入數組基礎上構造出最大堆 15 16 void heap_sort(int A[], int length); //對一個數組原地進行排序 17 18 int main(){ 19 int num, i; 20 printf("Input the number:\n"); 21 scanf("%d", &num); 22 int *array = malloc((num + 1) * sizeof(int)); 23 printf("Input the element:"); 24 for(i = 1; i <= num; i++) 25 scanf("%d", &array[i]); 26 27 heap_sort(array, num); 28 29 for(i = 1; i <= num; i++) 30 printf("%d ", array[i]); 31 printf("\n"); 32 return 0; 33 } 34 35 /* 36 *這個函數是維持堆的性質,注意當調用這個函數的時候,我們假定該節點的左右兒子樹都是最大堆。 37 *但是有可能該節點小於它的子樹,所以通過這個函數使該節點下降,使以該節點為根的子樹成為最大堆。 38 */ 39 void max_heapify(int A[], int length, int i){ 40 int l = 2 * i; //左兒子節點下標 41 int r = 2 * i + 1; //右兒子節點下標 42 int largest; //記錄該節點與左右兒子節點中值最大的下標 43 44 if(l <= length && A[l] > A[i]) 45 largest = l; 46 else 47 largest = i; 48 49 if(r <= length && A[r] > A[largest]) 50 largest = r; 51 52 if(largest != i){ 53 int temp = A[largest]; 54 A[largest] = A[i]; 55 A[i] = temp; 56 max_heapify(A, length, largest); 57 } 58 } 59 60 /* 61 *練習6.3-2:為什么從下標floor(length/2)降到1,而不是從1升到floor(legnth/2)? 62 *max_heapify函數假設左右兒子為根的二叉樹都為最大堆,如果從1開始的話,其左右兒子為根的二叉樹不一定為最大堆。 63 *而從floor(length/2)開始則可以保證左右兒子為根的二叉樹都是最大堆 64 */ 65 void build_max_heap(int A[], int length){ 66 int i; 67 for(i = length/2; i >= 1; i--) 68 max_heapify(A, length, i); //調用維持最大堆的性質函數 69 } 70 71 /* 72 *數組A[1...n]的最大元素為A[1],通過與A[n]交換達到最終正確的位置。原來根的子女依然是最大堆, 73 *但是新的根元素可能違背最大堆的性質。因此要調用維持最大堆性質的函數 74 *然后在A[1...n-1]里重復這個過程。 75 */ 76 void heap_sort(int A[], int length){ 77 int i; 78 int size = length; 79 80 build_max_heap(A, length); //建堆 81 for(i = length; i >= 2; i--){ 82 int temp = A[i]; 83 A[i] = A[1]; 84 A[1] = temp; //交換,將最大的元素放到數組的還沒排序的尾部。 85 86 size--; 87 max_heapify(A, size, 1); //調用維持最大堆的性質的函數(此時根節點的左右兒子子樹為最大堆) 88 } 89 }