我們先看看究竟什么是堆?以大頂堆為例:
對於一棵完全二叉樹而言,當每個結點不小於其子結點時,便可稱之為堆(大頂堆),比如:

原始的待排序的數組為:30, 20, 40, 10, 0, 60, 80, 70其對應的完全二叉樹為:

接下來,我們來圖解堆排序,並用程序來實現堆排序。在這個過程中,希望大家感受到堆之美。
圖解堆排序
一. 構建堆
第1步:
如上圖,最后一個非葉子結點是10,發現10比70小,所以70必須上浮,得到的結果為:

第2步:
如上圖,倒數第二個非葉子結點為40,在40,60,80這三個數中,80最大,所以80必須上浮,得到的結果如下:

第3步:
如上圖,倒數第三個非葉子結點為20,而20比70小,所以70必須上浮,20下沉后,發現比下面的10還大,所以沒有必要沉底,得到的結果為:

第4步:
如上圖,倒數第四個非葉子結點為30,在30,70,80中,80最大,所以80要上浮,30下沉。然而,30比60和40都小,所以要繼續下沉,得到的結果是:

到此為止,可以看到,一個大頂堆已經形成,可以看到,最大的80已經被選擇出來了。
二. 調整堆
我們把堆頂的最大值80調整到最后,保存下來,得到的結果是:

接下來的工作就是對上面紅框中的的7個結點進行調整,使之形成新的堆。
很顯然,根據之前調整的過程可知,兩個藍色框中的結點,已經分別成堆了,所以這次的調整就簡單多了,直接瞄准待調整的10即可。
之前已經把8個結點調整成堆,那么調整上面紅色框中的7個結點成堆便不在話下。於是,這7個結點中最大的70被調到了堆頂,如下:

80是最大的值,放在最后。堆頂的70是第二大的值,放在倒數第二的位置,所以跟40進行交換,得到的結果為:

可見,通過2次從堆頂摘下最大元素,分別把80和70選出來了。接下來,用相同的方法,把60選出來,依此循環,最后得到的二叉樹為:

終於,實現了排序,這就是所謂的堆排序,其平均時間復雜度為O(N*logN), 比冒泡排序好多啦。
堆排序實現
接下來,我們用代碼來實現堆排序,如下:
#include<iostream> using namespace std; void print(int a[], int n) { int i; for(i = 0; i < n; i++) { cout << a[i] << " "; } cout << endl; } void heapAdjust(int a[], int low, int high) { int pivotKey = a[low - 1]; int i; for(i = 2 * low; i <= high; i *= 2) { if(i < high && a[i - 1] < a[i]) { i++; //i指向較大值 } if(pivotKey >= a[i - 1]) { break; } a[low - 1] = a[i - 1]; low = i; } a[low - 1] = pivotKey; } void heapSort(int a[], int n) { int i, tmp; for(i = n/2; i > 0; i--) { heapAdjust(a, i, n); print(a, n); } for(i = n; i > 1; i--) { tmp = a[i -1]; a[i - 1] = a[0]; a[0] = tmp; heapAdjust(a, 1, i - 1); print(a, n); } } int main() { int a[] = {30, 20, 40, 10, 0, 60, 80, 70}; int n = sizeof(a) / sizeof(a[0]); heapSort(a, n); print(a, n); return 0; }
最終的排序結果如下:
0 10 20 30 40 60 70 80
堆是一種重要的數據結構,堆排序也是非常重要的算法。在筆試面試中,經常考到堆的相關應用。
