(注:本文的相關敘述和圖片摘自《數據結構與算法分析新視角》(周幸妮等),因此本文只是我的一個復習記錄,詳細的論述請參考該書。)
1. 最大(小)堆
對於一個完全二叉樹來說,如果所有的結點(葉子結點除外)的值都大於(小於)其左右孩子結點的值,那么這個完全二叉樹就被成為一個大(小)根堆。如下圖所示。按照堆的定義可以發現,堆頂結點(二叉樹的根結點)一定對應整個序列中的最大(小)記錄。這樣一來,可以設計一種排序思路,每次將堆的堆頂記錄輸出,同時調整剩余的記錄,使它們從新排成一個堆。重復這個過程,就能最終得到一個有序的序列,完成排序的過程,這種方法稱之為堆排序。
因此,對於堆排序來說,主要有兩個問題:
- 一個無序序列的所有記錄如何排列成一個堆?
- 在輸出了堆頂記錄后,如何將剩下的記錄再排成一個堆?
2. 堆排序
- 由無序序列生成堆
以序列{49,38,65,97,76,13,27,49,55,04}為例,介紹生成最小堆的思路。我們知道一棵完全二叉樹的最后一個非葉子結點的索引為[n/2],因此在本例中,選擇第5個記錄{76}作為初始篩選點。首先比較{76}與其左右孩子結點記錄的大小,並按照篩選的結果進行交換,因此得到下圖2的結果。接下來對倒數第二個非葉子結點進行處理,結果如下圖3所示。重復這個步驟,可以得到一個結構完整的最小堆,如下圖4所示。
(1)序列直接構成完全二叉樹 (2)一次篩選之后的完全二叉樹 (3)二次篩選之后的完全二叉樹
(4)經過多次篩選之后的最小堆
- 堆排序
以序列{13,38,27,49,76,65,49,97}為例,介紹堆排序的思路。
初始堆 輸出堆頂元素后的情形
最后一個記錄暫放於堆頂 一次調整之后的情形
二次調整之后的情況 輸出記錄{27}之后再排成一個堆
重復上述步驟,最終就可以生成一個有序序列。
3. 相關代碼
- 生成最大堆代碼(最小堆類似)
1 //調整堆結點
2 //arr:數組首地址;n:結點在數組中的位置;len:數組的長度 3 void adjust_node(int *arr, int n, int len) 4 { 5 int l, r, max, tmp; 6 l = 2 * n + 1; //左右孩子的索引,注意數組下標從0開始。 7 r = 2 * n + 2; 8 max = n; 9 10 if (l<len&&arr[l]>arr[n]) 11 max = l; 12 if (r<len&&arr[r]>arr[max]) 13 max = r; 14 15 if (max != n) 16 { 17 tmp = arr[n]; 18 arr[n] = arr[max]; 19 arr[max] = tmp; 20 adjust_node(arr, max, len); //保證最大堆 21 } 22 }
- 堆排序
1 //堆排序(升序) 2 void sort_heap(int *arr, int len) 3 { 4 for (int i = len / 2; i >= 0; i--) 5 adjust_node(arr, i, len); 6 int tmp; 7 for (int i = len - 1; i >= 0; i--) 8 { 9 tmp = arr[0]; 10 arr[0] = arr[i]; 11 arr[i] = tmp; 12 adjust_node(arr, 0, i); 13 } 14 }
- 驗證
1 #include <iostream> 2 using namespace std; 3 4 int main() 5 { 6 int arr[] = { 49,38,65,97,76,13,27,49,55,04 }; 7 int len = 10; 8 9 for (int i = 0; i < len; i++) 10 cout << arr[i] << ' '; 11 cout << endl; 12 15 sort_heap(arr, len); 16 17 for (int i = 0; i < len; i++) 18 cout << arr[i] << ' '; 19 cout << endl; 20 21 return 0; 22 }
輸出結果:
49 38 65 97 76 13 27 49 55 4
4 13 27 38 49 49 55 65 76 97