堆排序C++實現
堆排序的具體思路可以查看《算法導論》這本書,一下只提供筆者的C++實現代碼,並且將筆者在編寫程序的過程當中所遇到的一些細節問題拿出來作一番解釋,希望能夠對對堆排序有一個透徹的理解。
1、構造一個維護堆性質(最大堆)的函數
這里需要做一個假設:對於數組中下標為i的節點其左子樹和右子樹都是保持最大堆性質的堆。在假設成立的前提上,經過這一個維護函數維護過的堆才能夠保證是一個最大堆。函數的C++實現代碼如下:
1 //假定對某一個節點i其左,右子樹都是都是最大堆,但是對於節點i和它的左右子節點則可能破壞最大堆的性質,我們來寫一個函數對這 2 //情況下的堆來進行維護使整體的堆滿足最大堆性質 3 void MaxHeapify(int* a,int i,int low,int high)//輸入為要被排序的數組和根節點,數組a當中被維護的那一部分的下標low,high 4 { 5 int l = left(i);//計算下標為i的節點的左子節點 6 int r = right(i);//計算下標為i的節點的右子節點 7 int largest;//保存i,l,r(即i和它的左右子節點)之間的最大數的下標 8 int temp;//交互數組中的數所使用的臨時變量 9 //找到三個數當中最大的那個數,將最大的那個數和i進行互換 10 if (l<=high && a[l]>a[i]) 11 { 12 largest = l; 13 } 14 else{ 15 largest = i; 16 } 17 18 if (r<=high && a[r]>a[largest]) 19 { 20 largest = r; 21 } 22 if (largest != i) 23 { 24 temp = a[i]; 25 a[i] = a[largest]; 26 a[largest] = temp; 27 MaxHeapify(a, largest,low,high);//交換有可能破壞子樹的最大堆性質,所以對所交換的那個子節點進行一次維護,而未交換的那個子節點,根據我們的假設,是保持着最大堆性質的。 28 } 29 }
代碼中還有一個細節需要注意:
1 //定義兩個有參宏來尋找數組中的數之間的關系 2 #define left(x) 2*x+1;//獲得左節點在數組中的下標 3 #define right(x) 2*(x+1);//獲得右節點在數組中的下標
《算法導論》一書當中的數組都是以1作為起始下標的,所以書中計算左右節點的下標的公式分別為:2*i,2*i+1。但是在C++的程序當中數組都是從0開始的所以我們的計算方式也要進行修改。
建堆函數
這個函數負責將數組中元素的位置進行更改保證數組能夠構造成為一個最大堆,實現如下:
//將數組建立為一個最大堆 //調整數組當中數的位置將其處理為一個最大堆 void BuildMaxHeap(int* a,int length) { for (int i = length / 2-1; i >= 0; i--) { MaxHeapify(a, i, 0, length - 1); } }
其中MaxHeapify函數為在上文中創建的函數。有一個細節的位置和《算法導論》上面的不一樣,i=length/2-1,這也是因為數組的起始下標為0所導致的。可以這樣理解此處的減1,相當於在以1為起始的數組當中找到這個點之后,對數組整體進行向后位移一個單位就可以得到現在這個點的下標了。
在本節后面的習題當中有提到這么一個問題:為什么是從length/2-1遞減到0,而不是從0開始遞增到length/2-1呢?從length/2-1遞減到0就意味着,從最后的一個非葉節點開始“從小到大”地進行維護,保證每一個點的子樹都是最大堆,可以確保最大堆的性質,而如果從0遞增到length/2-1則無法保證堆的性質,可以看如下反例:
對數組{14,10,8,16,7}從0遞增到length/2-1的堆創建過程如下圖所示:
可見無法保證最大堆性質。
堆排序函數
實現程序如下:
1 //堆排序函數 2 void HeapSort(int a[],int length) 3 { 4 int temp; 5 BuildMaxHeap(a,length); 6 for (int i = length - 1; i >= 1; i--) 7 { 8 //交換根節點和數組的最后一個節點 9 temp = a[i]; 10 a[i] = a[0]; 11 a[0] = temp; 12 MaxHeapify(a, 0, 0, i-1);//維護從下標為i-1到0的子數組 13 } 14 }
在縮減被排列數組這一思路上面我采用的方法是在函數當中輸入被排序數組的下標范圍。