上一篇文章講過了快速排序,這篇文章主要講講堆排序 2.5、堆排序演算法
1、什麼是堆
堆是一種樹結構,更準確的說是完全二叉樹。並且每個非分葉節點的資料大於或等於(小於或等於)其左、右子結點的資料,這樣堆結構的根結點為最大值(最小值),堆稱為最大堆(最小堆)。所以可以歸納為:堆是一個完全二叉樹,非葉結點與左右子結點的資料有一定的大小關係。
2、什麼是堆排序
完整的堆排序需要經過反覆的兩個步驟:構造堆結構和堆排序輸出。
用通俗的話來說就是:
(1)將一組資料放到一個完全二叉樹中去,再通過資料交換,使完全二叉樹變成堆。
(2)根據堆的定義我們知道,堆的根結點是最大值(最小值),這樣我們將跟結點的值輸出,就得到了這一組資料的最大值(最小值)。
(3)使剩餘的資料再次構成一個堆,然後輸出跟結點。
(4)不斷重複上述步驟就可以實現原資料按順序輸出。
那現在的問題就是如何構造堆以及如何堆排序輸出。
3、如何構造堆
構造堆結構就是把原始的無序資料按照前面堆結構的定義進行調整。
(1)將原始的無序資料放到一個完全二叉樹的各個結點中去。
(2)然後由完全二叉樹的最下層的非葉結點開始逐層向上進行調整(因為葉結點沒有左右子樹,所以一定是合格),直到所有結點(在調整過程中某些非葉結點可能需要經過多次調整)最後都滿足堆結構的條件為止。整個過程其實就是將最大值(最小值)不斷向上移動,直到移到跟結點為止。
(3)在調整過程中,假設父結點與左結點的大小關係不合規則,需要進行值得交換。交換之後父結點與左右結點的大小關係符合要求了,但是此時如果左結點是一個非葉結點(也有子結點),那麼左結點與該左結點的子結點之間就有可能不符合規則,即還需要對子結點進行調整,直到葉結點。
下面給出一個實際的例子來分析構造堆的過程。
(1)將未經處理資料構成一個完全二叉樹。
(2)最後一個非葉結點為結點4,從該結點進行堆化操作。該結點只有左子樹且49>38。按照最大堆的定義,是使4和8這兩個結點互換位置
(3)上述完全二叉樹中倒數第2個非葉結點是結點3,顯然結點3及其子結點已經構成堆結構,無需對該結點進行堆化操作。
(4)上述完全二叉樹中倒數第3個非葉結點是結點2,對該結點進行堆化操作。由於結點2的兩個子結點中結點5的值較大,因此使結點2與結點5進行比較。因為65<97,此時需要將結點2與結點5互換資料。這樣結點2及其子及結點構成堆
(5)上述二叉樹中倒數第4個非葉結點是結點1,對該結點進行堆化操作,由於結點1的兩個子節點中結點2的值較大。所以使結點1與結點2進行比較。67小於97,因此將結點1與結點2互換資料。這時結點2的值已經改變,需要重新對結點2及其子結點進行堆化操作,因為結點1與結點2互換資料可能破壞了結點2和其子結點構成的堆。 這樣結點1及其子結點構成堆結構。
4、堆排序輸出
(1)根據堆結構的特點,堆結構的根結點也就是最大值。這裡按照從小到大排序,因此將其放到數組的最後,將結點8與結點1互換。
(2)結點8的值38換到跟結點之後,對除最後一個結點外的其他結點結點重新執行前面介紹的構造堆的過程,重新得到堆。
(3)重複上述過程,取此時堆結構的根結點(最大值)進行互換,放在數組的後面。此時結點1與結點7互換。
(4)然後重複將剩餘的資料構造堆結構,取根結點與最後一個結點交換。結果如下圖。
(5)再進一步執行堆排序輸出,結點1與結點3互換,結果如下圖
(6)堆最後的兩個資料進行處理,得到最終的結果
5、具體的代碼實現
具體代碼在這就不解釋了,代碼中的注釋寫的非常詳細。
/** * @author Mr0 * */public class P4_6_Select_HeapSort { public static void heapSort(int a[]){ buildHeap(a); int n = a.length; for(int i=n-1;i>=1;i--){ /*將最大值與堆的最後一個數(在記憶體中表現為數組中下標最大的值)互相交換位置*/ swap(a,0,i); /*將完全二叉樹的最後一個分葉節點(即上行代碼執行交換操作中的最大值)去掉,剩下的完全二叉樹 跟節點不再是最大值,調用Heapify方法,將完全二叉樹堆化,注意這裡的堆化與調用buildHeap進行第一次的堆化是不同的,這裡的完全二叉樹只有跟節點與左右子節點是不合要求的,其他節點都是符合要求的,所以直接調用heapify方法即可完成堆化*/ Heapify(a,0,i); } } /** * @param a * 將一個完全二叉樹堆化(最大堆) * 核心思想:找到二叉樹中最後一個分葉節點的父節點(第一個非分葉節點),比較父節點與左右節點(左右節點至少存在一個)的值, * 這時會出現兩種情況:1、左右節點的某一個值比父節點大,此時將較大的值與父節點互換,記錄下進行了互換操作的左右節點的id, * 再調用Heapify對以左(右)節點為跟節點的子二叉樹進行堆化 2、左右節點都比父節點小,此時父節點與子節點之間沒有值得交換 * 不需要對以左(右)節點為父節點的子二叉樹進行堆化,迴圈到下一個非分葉節點。再迴圈1、2的判斷過程 * 最終就完成了完全二叉樹的堆化。 */ public static void buildHeap(int a[]){ int n= a.length; for(int i=(n/2-1);i>=0;i--){ //n/2-1一定是完全二叉樹的最後一個非分葉節點,通過for迴圈一直到跟節點 Heapify(a,i,n); } } /** * @param a * @param id * @param max * 將以id為下標的節點所對應的子樹堆化 */ public static void Heapify(int a[],int id,int max){ int left = id*2+1; int right = id*2+2; int largest = 0; if(left<max && a[left]>a[id]){ largest = left; }else{ largest = id; } if(right<max && a[right]>a[largest]){ largest = right; } if(largest != id){ //如果根節點與左右子節點進行了值得交換,則需要對交換值後的子節點再次進行堆化操作 swap(a,largest,id); //如果沒有交換值,怎不需要對子節點再次進行堆化操作(因為已經執行過了) for(int i=0;i<a.length;i++){ System.out.print(a[i]+" "); } System.out.print("largest="+largest+" "); System.out.print("i="+id); System.out.println(""); Heapify(a,largest,max); //對交換值後的子節點進行堆化操作。 } } /** * @param a * @param b * @param c * 交換a數組中bc下標元素的值。 */ public static void swap(int a[],int b,int c){ int temp = a[b]; a[b] = a[c]; a[c] = temp; } public static void main(String[] args){ int[] array= {1,2,3,4,5,6,7,16,9,10,11,12,13,14,15,8}; System.out.println("排序前數組順序為:"); for(int i=0;i<array.length;i++){ System.out.print(array[i]+" "); } System.out.println(""); heapSort(array); System.out.println(""); System.out.println("排序後的數組順序為:"); for(int i=0;i<array.length;i++){ System.out.print(array[i]+" "); } }}