堆排序


 

1.     基本概念

堆,分為大頂堆(大堆)和小頂堆(小堆),是順序存儲的完全二叉樹,並且滿足以下特性之一:

(1)    任意非終端結點關鍵字不小於左右子結點(大堆)

ki >= k2i+1並且ki>=k2i+2 其中,0 <= i <= (n-1)/2,n是數組元素個數

(2)    任意非終端結點關鍵字不大於左右子結點(小堆)

ki <= k2i+1並且ki<=k2i+2 其中,0 <= i <= (n-1)/2,n是數組元素個數

 

 

調整(也有叫篩選):

從當前結點(要求是非終端結點)開始,

對於大堆,要求當前結點關鍵字不小於子結點,如不符合,則將最大的子結點與當前結點交換。循環迭代交換后的子樹,確保所有子樹都符合大堆特性。

 

小堆調整過程類似。

 

2.     基本思想

堆排序就是利用構建堆和輸出堆頂元素的過程,不斷對堆進行調整以保證當前結點及其孩子結點滿足堆特性,從而達到對初始數組元素進行排序的目的。

大堆通常對應升序序列,小堆通常對應降序排列。

 

核心步驟:

1)      構建堆(大堆/小堆)

從最后一個非終端結點開始,向前進行調整,保證當前結點及其子樹符合堆特性;

2)      輸出有序序列

交換堆頂與末尾葉子結點,堆頂輸出到數組的有序序列末尾,而不參與堆的調整。從交換后的堆頂開始調整,以確保當前結點及其子樹符合堆特性。

 

3.     實例

下面舉個例子,利用小堆進行降序排列。

初始序列

49

38

65

97

76

13

27

49‘

位置

0

1

2

3

4

5

6

7

說明:

  1. 符號’表示第二個相同元素

 

3.1.  構建堆

1)      初始序列對應初始堆

 

從最后一個非葉子結點開始,向前進行調整,確保符合特性

最后一個非葉子結點位置:(n-1) / 2 = 3, n=8

總共調整次數:(n-1)/2 +1 = 4

 

第1次調整:選擇最后一個非葉子結點元素為97(位置3)為當前父結點,與其子結點進行比較,選擇最小的結點作為當前父結點。

 

第1次調整后序列

49

38

65

49’

76

13

27

97

位置

0

1

2

3

4

5

6

7

 

 

 

第2次調整:選擇上一次結點的前一個結點65(位置2)為當前結點進行調整。

第2次調整后序列

49

38

13

49’

76

65

27

97

位置

0

1

2

3

4

5

6

7

 

第3次調整:選擇上一次結點的前一個結點38(位置1)為當前結點進行調整。

第3次調整后序列

49

38

13

49’

76

65

27

97

位置

0

1

2

3

4

5

6

7

 

 

第4次調整:選擇上一次結點的前一個結點49(位置0)為當前結點進行調整。

第4次調整后序列

13

38

27

49’

76

65

49

97

位置

0

1

2

3

4

5

6

7

 

 

3.2.  輸出堆頂元素

將已經構建好的小堆,輸出堆頂元素,和末尾元素交換,相當於堆頂移動到數組末尾形成有序序列,未排序元素移動到堆頂。從新的堆頂開始進行調整,直到堆重新符合小堆特性。

 

交換堆頂和末尾(堆的末尾,不包括已經排好序的部分),並將交換后的堆末尾作為有序序列的一部分,而不再屬於堆。 

 

 

一次交換后,發現97新的位置比子結點大,需要繼續調整。

 

 

..

這樣,不斷輸出所有堆頂到數組末尾,最終可以得到

有序序列

97

76

65

49

49’

38

27

13

位置

0

1

2

3

4

5

6

7

 

 

4.     實現代碼

4.1.  核心算法代碼

 1 /**
 2  * 小堆 <=> 升序排列
 3  * 從最后一個非葉子結點開始, 向前進行調整
 4  * @param a 待排序序列(數組)
 5  * @param n 待排序元素個數
 6  */
 7 void HeapSort(int a[], int n)
 8 {
 9     // S1 建堆
10     // 從最后一個非葉子結點開始, 向前進行調整
11     for(int i = LocOfLastNonLeaf(a, n); i >= 0; i --)
12     {// LocOfLastNonLeaf : (n-1) / 2
13         HeapAdjust(a, i, n);
14     }
15 
16     // S2 輸出並調整
17     for(int j = n-1; j > 0; j --)
18     {// 判斷條件不用加"=", 因為j=0時等價於數組只有一個元素, 即只有一個根節點, 而無子樹
19         Swap(a[0], a[j]); // 形參為同名參數, 直接交換a[0]和a[j]
20         HeapAdjust(a, 0, j);
21     }
22 }

 

 1 /**
 2  * 篩選位置i, 調整堆
 3  * @param a 待排序序列(數組)
 4  * @param i 篩選位置
 5  * @param len 數組元素個數
 6  */
 7 void HeapAdjust(int a[], int i, int n)
 8 {
 9     if(i > n / 2 - 1)
10     {// 葉子結點, 無子樹
11         return;
12     }
13 
14     // 檢查結點i是否符合小堆特性, 如果不符合, 需要與最小子結點交換
15     for(int k = 2*i +1; k < n; k = 2*k + 1)
16     {
17         // 判斷右子樹是否比左子樹更小
18         if(k+1 < n && a[k+1] < a[k])
19         {
20             k ++; // 更新最小子結點
21         }
22 
23         if(a[i] > a[k])
24         {
25             Swap(a[i], a[k]); // 與最小的子結點交換
26             i = k;  // 將左子結點設為當前結點
27         }
28         else
29         {// 符合小堆特性
30             break;
31         }
32     }
33 }

 

4.2.  調用

 1 int main()
 2 {
 3     int a[] = {
 4 //            10,2,5,9,5,55,21,33,15
 5             49, 38, 65, 97, 76, 13, 27, 49
 6     };
 7     int a_len = sizeof(a) / sizeof(a[0]);
 8 
 9     printf("原始序列: ");
10     PrintArrary(a, a_len); // 打印數組
11 
12     HeapSort(a, a_len); // 堆排序
13 
14     printf("小堆排序后序列(降序): ");
15     PrintArrary(a, a_len); // 打印數組
16 
17     return 0;
18 }

 

4.3.  測試結果

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM