堆是一個完全二叉樹,樹中每個結點對應於原始資料的一個記錄,並且每個結點應滿足以下條件:非葉結點的資料大於或等於其左、右孩子結點的資料(若是按從大到小的順序排序,則要求非葉結點的資料小於或等於其左、右孩子結點的資料)。
由堆的定義可看出,其根結點為最大值,堆排序就是利用這一特點進行的。堆排序過程包括兩個階段:
(1)將無序的資料構成堆(即用無序資料生成滿足堆定義的完全二叉樹)。
(2)利用堆排序(即用上一步生成的堆輸出有序的資料)。
假如有一組要排序的資料:69,65,90,37,92,6,28,54
1.首先,根據堆的定義,將這組資料生成堆
a. 從最後一個非葉節點37開始,比較其子節點,若發現左右節點中有一個比該節點大,則將兩個節點進行交換
b. 按照上面的步驟,一遍下來可以將樹中最大的節點92篩選到最上面的根節點
2.利用堆進行排序
a. 因為上面排序一遍過後,最大的節點在根節點,因此將根節點與最後一個節點交換,使最大的節點排在最後
b. 由於根節點和最後一個節點交換,破壞了原來的堆結構,所以需要對除了最後一個節點外的其他n-1個節點重新生成堆
c. 重複上面兩個步驟,最後剩下的一個節點即為最小節點,排在整個二叉樹的根節點,至此則生成從小到大的有序堆
具體圖示如下:
(圖中輸出,即表示為調整到陣列最後)
下面貼上具體程式碼(C語言版):
1 #include <stdio.h> 2 #include "CreateData.c" //生成隨機數檔案 3 4 #define MAXSIZE 10 5 6 //對指定的節點構成堆 7 //int m 指定的節點 8 //int n 陣列的元素個數 9 void HeapAdjust(int a[],int m,int n){ 10 int i,j,t; 11 12 while(2*m+1 < n){ //指定的節點有左子樹 13 j = 2*m+1; //m的左子樹 14 15 if(j+1 < n){ 16 //有右子樹,且右子樹大於左子樹 17 if(a[j] < a[j+1]) j++; 18 } 19 20 21 if(a[m] < a[j]){ 22 //如果有個子樹大於根節點,則交換 23 t = a[m]; 24 a[m] = a[j]; 25 a[j] = t; 26 27 m = j; //交換過以後,以j為根的節點再重新生成堆 28 }else 29 break; 30 31 } 32 } 33 34 //堆排序演算法 35 void HeapSort(int a[],int n){ 36 int i,t; 37 38 i = n/2-1; 39 40 for(;i>=0;i--) 41 HeapAdjust(a,i,n); 42 43 for(i = n-1;i>=0;i--){ 44 //將樹根節點(最大)放到最後一個元素 45 t = a[i]; 46 a[i] = a[0]; 47 a[0] = t; 48 49 //破壞了根節點,再對根節點重新生成堆 50 HeapAdjust(a,0,i); 51 } 52 } 53 54 int main(){ 55 int a[MAXSIZE]; 56 int i; 57 58 59 if(!CreateData(a,100,10,MAXSIZE)){ 60 printf("生成隨機數失敗.\n"); 61 return 0; 62 } 63 64 printf("排序前:"); 65 for(i = 0 ; i < MAXSIZE ;i++) 66 printf("%d ",a[i]); 67 68 HeapSort(a,MAXSIZE); 69 70 printf("\n排序後:"); 71 for(i = 0 ; i < MAXSIZE ;i++) 72 printf("%d ",a[i]); 73 printf("\n"); 74 75 return 1; 76 }
此段程式碼在除錯的過程中比較坎坷,在Linux中有時候會發現比較古怪的問題,比如陷入死迴圈但是很難追蹤,不過都是個人粗心,有幾個變數賦值和迴圈條件剛開始的時候寫錯了。不過現在經測試已經執行穩定,並實現從小到大進行資料排序功能。