數據結構與演算法:堆排序

堆是一個近似完全二叉樹完全二叉樹)的結構,並同時滿足堆積的性質:即子節點的鍵值或索引總是小於(或者大於)它的父節點。

大頂堆:子節點的鍵值或索引總是小於(或等於)它的父節點

小頂堆:子節點的鍵值或索引總是大於(或等於)它父節點

 

堆排序

堆排序(英語: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;
    }
}