數據結構與演算法:堆排序
堆
大頂堆:子節點的鍵值或索引總是小於(或等於)它的父節點
小頂堆:子節點的鍵值或索引總是大於(或等於)它父節點
堆排序
堆排序(英語:Heapsort)是指利用堆這種數據結構所設計的一種排序演算法,是選擇排序的擴展,它的最好和最壞的平均複雜度都為O(nlogn),是不穩定排序演算法。
堆排序步驟
案例:使用大頂堆模式對數組[5, 15, 10, 20, 13]進行升序排序
步驟一:對給定數組[5, 15, 10, 20, 13]進行調整,轉換成大頂堆數組(一般升序使用大頂堆,降序使用小頂堆)
(1). 創建一個指針指向調整起點(第一次調整時指向給定數組的二叉樹結構的最後一個非葉子節點 )
(1.1). 將指針指向的節點的值與它子節點的最大值進行比較,當指針指向的節點的值小於子節點的最大值時,進行調整交換
(1.2). 當發生交換時將指針指向與它發生交換的子節點位置循環步驟(1),如果沒有發生交換或指針指向葉子節點則結束步驟(1)
(2). 再從最後一個非葉子節點開始,往上遍歷節點,以遍歷節點為調整起點循環步驟(1),直到遍歷完根節點
步驟二:將堆頂元素(即數組0下標位置)與末尾元素進行交換,使得尾部元素最大。
(1). 因為經過步驟一後,數組已經變成了大頂堆結構,這時數組的第一個元素就是數組中最大的元素,而排序要求是升序,所以要把第一個元素與最後一個元素 (排除掉切割出的長度) 進行交換,使得最大元素在操作數組的最後。交換完後需把操作數組中的末尾元素切割出去(實際操作通過調整長度來實現切割,而不是創建一個新的數組)。
(2). 因為經過步驟(1)後,數組的大頂堆結構已經被破壞了,所以需重新調整,即跳到步驟一,又因為根節點元素髮生了變化,所以此時調整起點為根節點。
步驟三:循環執行步驟一和步驟二,當操作數組的元素都被切割出去時(調整長度為0),則表明數組已經按升序順序排序完成
程式碼實現
public class HeapSort { public static void main(String[] args) { int array[] = {5, 15, 10, 20, 13}; heapSort(array); System.out.println(Arrays.toString(array)); } //堆排序(從小到大排序,大頂堆模式實現) public static void heapSort(int array[]){ int temp = 0; /* 在二叉樹中由下到上,把二叉樹數組調整成大頂堆數組 array.length/2-1 :指向最後一個非葉子節點 */ for (int i = array.length/2-1; i>=0; i--){ adjustHeap(array, i, array.length); } /* 在經過調整後,數組的第一個索引0的值必定是數組調整長度內最大的值, 因為是從小到大進行排序,所以要把索引0的值放到調整長度內最後一個索引的值, 即放到調整長度-1的索引位置 */ for (int j = array.length-1; j>0; j--){ //將調整長度內最大值array[0]和調整長度內最後位置的值array[j]進行交換 temp = array[j]; array[j] = array[0]; array[0] = temp; /* 又因為經過交換後,大頂堆的結構會被破壞,所以要重新進行調整, 調整起點應為根節點(因為根節點發生了變化),即數組0下標位置, 而array[j]已經是不需要調整的了,所以調整長度為j(因為在調整方法里j參數是被當作長度處理的,所以無需-1) */ adjustHeap(array,0, j); } } /** * 將數組調整成大頂堆數組 * @param array 需調整的數組 * @param i 索引指針,開始指向傳入的非葉子節點,後面指向最後進行交換(插入)的節點 * @param length 需調整的長度,即調整的元素個數,從0下標索引開始 */ public static void adjustHeap(int array[], int i, int length){ //輔助變數,存儲傳入的非葉子節點的值 int temp = array[i]; /* 遍歷傳入的非葉子節點下的子節點,i*2+1為左節點,i*2+2為右節點 如果該節點不是非葉子節點則退出循環 */ for (int k = i*2+1; k<length; k = k*2+1){ /* 判斷該節點是否有右節點,即k+1是否超過數組範圍索引 如果有,則比較大小,拿出最大值的節點 */ if (k+1 < length && array[k] < array[k+1]){ //如果右節點大,把索引k+1即可使k指向右節點 k++; } //判斷傳入的非葉子節點的值是否小於它子樹節點的最大值 if (temp < array[k]){ //如果小於則把大於它的子節點的值賦給該節點 array[i] = array[k]; //再把非葉子節點的指針指向該子節點的位置,繼續循環 i = k; }else { /* 因為在調整二叉樹數組成大頂堆數組時,是從下到上進行遍歷調整的, 而循環條件是從上到下遞減的,所以當上訴條件不成立時直接退出即可 */ break; } } /* 因為在上面節點和節點的交換中使用的是插入演算法,所以在退出循環時, 需要將初始傳入的非葉子節點的值temp賦給最後一個實現交換(插入)的節點,即i指向的節點 */ array[i] = temp; } }