1. 程式人生 > 實用技巧 >堆排序(改進的簡單選擇排序)

堆排序(改進的簡單選擇排序)

  在介紹堆排序之前,先介紹一下這種資料結構:

  堆是一顆完全二叉樹,且具有如下性質,每個結點的值都大於等於其左右孩子結點的值,稱為大頂堆;每個結點的值都小於等於其左右孩子結點的值,稱為小頂堆

  我們以大頂堆為例來介紹堆排序,且採用順序儲存結構(物理結構)陣列來儲存堆的層序遍歷結果。

  堆排序的基本思想:將待排序的n個記錄構造成一個大頂堆,將根結點與堆陣列的倒數第一個元素交換位置,得到最大值;將剩餘的(n-1)個記錄調整為大頂堆,將根結點與堆陣列的倒數第二個元素交換位置,得到次最大值如此反覆執行,最終可以將一個順序表排為有序表。

  以順序表L = {3,1,5,9,8}為例,length = 5,下標從1開始。

  堆排序的步驟:(1)對於順序表L,呼叫調整堆演算法將其構建為一個大頂堆;

         (2)輸出堆頂記錄以後,呼叫調整堆演算法將剩餘記錄調整為一個大頂堆,如此反覆執行。

  堆排序程式碼如下所示:

 1 //堆排序演算法
 2 //將待排序的序列構造成一個大頂堆
 3 //將大頂堆的根結點與堆末尾元素交換(層序遍歷的最後一個結點)
 4 //將剩餘的序列重新調整為一個大頂堆
 5 //將大頂堆的根結點與堆末尾元素交換
 6 //如此反覆執行
 7 void HeapSort(SqList* L)
 8 {
 9     int i,j;
10 
11     //結點i = 1... (length / 2);都是有孩子的結點
12 //構造大頂堆的過程,就是將每個非葉子結點當作根結點,將其和其子樹調整為大頂堆的過程 13 //葉子結點均滿足堆的定義 14 for (i = L->length / 2; i > 0; i--)//將L中的記錄構造成一個大頂堆 15 HeapAdjust(L, i, L->length);//呼叫調整堆演算法 16 17 for (j = L->length; j > 1; j--) 18 { 19 //將堆頂記錄和堆尾記錄交換位置 20 swap(L, 1, j); 21 22 //
將剩餘的序列重新調整為一個大頂堆 23 //除了根結點以外,均滿足堆的定義,符合呼叫調整堆演算法的條件 24 HeapAdjust(L, 1, j - 1); 25 } 26 }

  順序表中的記錄變化如下:

  其實,堆排序的原理還是很簡單明朗的,重點在於如何對堆進行調整。在調整堆演算法中,要求待調整的完全二叉樹除了根結點以外,其餘部分均滿足堆的性質。在呼叫調整堆演算法構建堆的過程中,對於一棵完全二叉樹,只有葉子節點一定滿足堆的定義,其他子樹不一定滿足,所以我們選擇從層序遍歷中序號最大的且有孩子結點的結點開始,依次呼叫調整堆演算法,直到到達完全二叉樹的根結點,最終,將一個順序表構建成一個大頂堆。總而言之,構建大頂堆的過程,就是從下往上,由右至左,將每個非葉子節點當作根結點,將其和其子樹調整成大頂堆的過程

  調整堆程式碼如下所示:

 1 //調整堆演算法
 2 //L中的記錄除L->r[s](根結點)外均滿足堆的定義
 3 //調整L->r[s]的位置,使L->r[s,...m]成為一個大頂堆
 4 void HeapAdjust(SqList* L, int s, int m)
 5 {
 6     //temp當作中間變數,儲存根結點的值
 7     int temp, j;
 8     temp = L->r[s];//temp初始化為根結點的值
 9 
10     //結點j為結點(2*j)和結點(2*j+1)的雙親
11     //第x次for迴圈找出當前根結點的孩子結點的最大值,並賦值給當前根結點
12     for (j = 2 * s; j <= m; j *= 2)
13     {
14 
15         //j < m說明還未到達最後一個結點
16         //若j <= m ,則r[j + 1]超出了索引範圍
17         //L->r[j] < L->r[j + 1]表示沿著關鍵字較大的結點的方向向下篩選
18         if (j < m && L->r[j] < L->r[j + 1])
19             ++j;//j為關鍵字較大的結點的下標
20 
21         if (temp >= L->r[j])//根節點的值大於等於其孩子結點的最大值
22             break;//跳出for迴圈
23 
24         //當前根節點的值小於其孩子結點的最大值
25         L->r[s] = L->r[j];//當前根結點的孩子結點的最大值賦值給當前根結點
26 
27         s = j;//關鍵字最大的孩子結點當作新的根結點
28     }
29 
30     //將原來的根結點放在了正確的位置
31     L->r[s] = temp;
32 }

  我們以呼叫調整堆演算法構建堆的過程為例,來說明調整堆程式碼的實現流程,順序表中的記錄變化如下:

  

  注意,紅色虛線框內的部分為需要調整為堆的完全二叉樹。如上圖所示,為for迴圈條件不滿足導致for迴圈結束的情況,與上述例子相同;為了說明另一種for迴圈結束的情況(break導致),我們以順序表L = {8,1,5,9,3}為例,並做了圖示說明。

相關連結:

氣泡排序https://www.cnblogs.com/yongjin-hou/p/13858510.html
簡單選擇排序https://www.cnblogs.com/yongjin-hou/p/13859148.html
直接插入排序https://www.cnblogs.com/yongjin-hou/p/13861458.html
希爾排序https://www.cnblogs.com/yongjin-hou/p/13866344.html

參考書籍:程傑 著,《大話資料結構》,清華大學出版社。