資料結構與演算法:堆排序

Java正道的光發表於2020-10-07

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

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

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

 

堆排序

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

 

相關文章