最大堆排序
? 最大堆排序
1.堆
??堆實際上是一棵完全二叉樹,其任何一非葉節點滿足性質:
? Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]或者Key[i]>=Key[2i+1]&&key>=key[2i+2]
? 即任何一非葉節點的關鍵字不大于或者不小于其左右孩子節點的關鍵字。
? 堆分為大頂堆和小頂堆,滿足Key[i]>=Key[2i+1]&&key>=key[2i+2]稱為大頂堆,滿足 Key[i]<=key[2i+1]&&Key[i]<=key[2i+2]稱為小頂堆。由上述性質可知大頂堆的堆頂的關鍵字肯定是所有關鍵字中最大的,小頂堆的堆頂的關鍵字是所有關鍵字中最小的。
2.堆排序的思想
?? 利用大頂堆(小頂堆)堆頂記錄的是最大關鍵字(最小關鍵字)這一特性,使得每次從無序中選擇最大記錄(最小記錄)變得簡單。
??? 其基本思想為(大頂堆):
??? 1)將初始待排序關鍵字序列(R1,R2....Rn)構建成大頂堆,此堆為初始的無序區;
??? 2)將堆頂元素R[1]與最后一個元素R[n]交換,此時得到新的無序區(R1,R2,......Rn-1)和新的有序區(Rn),且滿足R[1,2...n-1]<=R[n];?
??? 3)由于交換后新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,......Rn-1)調整為新堆,然后再次將R[1]與無序區最后一個元素交換,得到新的無序區(R1,R2....Rn-2)和新的有序區(Rn-1,Rn)。不斷重復此過程直到有序區的元素個數為n-1,則整個排序過程完成。
??? 操作過程如下:
???? 1)初始化堆:將R[1..n]構造為堆;
???? 2)將當前無序區的堆頂元素R[1]同該區間的最后一個記錄交換,然后將新的無序區調整為新的堆。
??? 因此對于堆排序,最重要的兩個操作就是構造初始堆和調整堆,其實構造初始堆事實上也是調整堆的過程,只不過構造初始堆是對所有的非葉節點都進行調整。
????下面舉例說明:
???? 給定一個整形數組a[]={16,7,3,20,17,8},對其進行堆排序。
??? 首先根據該數組元素構建一個完全二叉樹,得到
?然后需要構造初始堆,則從最后一個非葉節點開始調整,調整過程如下:
20和16交換后導致16不滿足堆的性質,因此需重新調整
這樣就得到了初始堆。
即每次調整都是從父節點、左孩子節點、右孩子節點三者中選擇最大者跟父節點進行交換(交換之后可能造成被交換的孩子節點不滿足堆的性質,因此每次交換之后要重新對被交換的孩子節點進行調整)。有了初始堆之后就可以進行排序了。此時3位于堆頂不滿堆的性質,則需調整繼續調整
這樣整個區間便已經有序了。 從上述過程可知,堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,為了從R[1...n]中選擇最大記錄,需比較n-1次,然后從R[1...n-2]中選擇最大記錄需比較n-2次。事實上這n-2次比較中有很多已經在前面的n-1次比較中已經做過,而樹形選擇排序恰好利用樹形的特點保存了部分前面的比較結果,因此可以減少比較次數。對于n個關鍵字序列,最壞情況下每個節點需比較log2(n)次,因此其最壞情況下時間復雜度為nlogn。堆排序為不穩定排序,不適合記錄較少的排序。
代碼詳解:
#include <stdio.h> #include <stdlib.h> #include <malloc.h> #include <time.h>void PrintArr(int *pnArr, int nLen) {for (int i = 0; i < nLen; i++){printf("%d ", pnArr[i]);}printf("\n"); }//返回i父節點下標 int Parent(int i) {return (i - 1) / 2; }//返回i左孩子下標 int LeftChild(int i) {return i * 2 + 1; }//返回i右孩子下標 int RightChild(int i) {return i * 2 + 2; }void Swap(int *a, int *b) {int nTmp = *a;*a = *b;*b = nTmp; } void MaxHeapify(int *pnArr, int nLen, int i) {int LChild = LeftChild(i);int RChild = RightChild(i);int nMaxPos;if (LChild < nLen && pnArr[LChild] > pnArr[i]){nMaxPos = LChild;}else{nMaxPos = i;}if (RChild < nLen && pnArr[RChild] > pnArr[nMaxPos]){nMaxPos = RChild;}if (nMaxPos != i){Swap(&pnArr[nMaxPos], &pnArr[i]);MaxHeapify(pnArr, nLen,nMaxPos);} //節點下的元素也要進行考慮,循環保證節點最大} //進行最大值排序,保證每個節點是最大值, void BuildMaxHeap(int *pnArr, int nLen) {for (int i = Parent(nLen -1); i >= 0; i--){MaxHeapify(pnArr, nLen, i);} //目的使堆的數據按節點最大排列 }void HeapSort(int *pnArr, int nLen) {BuildMaxHeap(pnArr, nLen);//建立一個堆,for (int i = nLen - 1; i > 0; i--){Swap(&pnArr[i], &pnArr[0]);nLen--;MaxHeapify(pnArr, nLen, 0); //存儲新的pnArr的值,因為地址不變 取的是地址的值} } int main() {int nArr[10] = {4,1,3,2,16,9,10,14,8,7};PrintArr(nArr, 10);HeapSort(nArr, 10);PrintArr(nArr, 10);system("pause");return 0; }
總結