1. 程式人生 > 實用技巧 >資料結構與演算法——堆排序

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

原文連結:https://jiang-hao.com/articles/2020/algorithms-algorithms-heap-sort.html

目錄

演算法介紹

堆排序(Heapsort)是指利用堆這種資料結構所設計的一種排序演算法。堆積是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點。堆排序可以說是一種利用堆的概念來排序的選擇排序。分為兩種方法:

  1. 大頂堆:每個節點的值都大於或等於其子節點的值,在堆排序演算法中用於升序排列;
  2. 小頂堆:每個節點的值都小於或等於其子節點的值,在堆排序演算法中用於降序排列;

堆排序的平均時間複雜度為 Ο(nlogn)。

通常堆是通過一維陣列來實現的。在陣列起始位置為0的情形中:

在堆的資料結構中,堆中的最大值總是位於根節點(在優先佇列中使用堆的話堆中的最小值位於根節點)。堆中定義以下幾種操作:

  • 最大堆調整(Max Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點
  • 建立最大堆(Build Max Heap):將堆中的所有資料重新排序
  • 堆排序(HeapSort):移除位在第一個資料的根節點,並做最大堆調整的遞迴運算

演算法步驟

首先第一步和第二步,建立堆,這裡我們用最大堆;建立過程中,保證調整堆的特性。

從最後一個分支的節點開始從右往左,從下至上進行調整為最大堆。

現在得到的最大堆的儲存結構如下:

初始堆建立完成。

接著,最後一步,堆排序,進行(n-1)次迴圈。

持續整個過程直至最後一個元素為止。

這個迭代持續直至最後一個元素即完成堆排序步驟。

演算法實現

public class HeapSort {
    private int[] arr;
    public HeapSort(int[] arr) {
        this.arr = arr;
    }

    /**
     * 堆排序的主要入口方法,共兩步。
     */
    public void sort() {
        /*
         *  第一步:將陣列堆化
         *  beginIndex = 第一個非葉子節點。
         *  從第一個非葉子節點開始即可。無需從最後一個葉子節點開始。
         *  葉子節點可以看作已符合堆要求的節點,根節點就是它自己且自己以下值為最大。
         */
        int len = arr.length - 1;
        int beginIndex = (arr.length >> 1)- 1;
        for (int i = beginIndex; i >= 0; i--)
            maxHeapify(i, len);
        /*
         * 第二步:對堆化資料排序
         * 每次都是移出最頂層的根節點A[0],與最尾部節點位置調換,同時遍歷長度 - 1。
         * 然後從新整理被換到根節點的末尾元素,使其符合堆的特性。
         * 直至未排序的堆長度為 0。
         */
        for (int i = len; i > 0; i--) {
            swap(0, i);
            maxHeapify(0, i - 1);
        }
    }

    private void swap(int i, int j) {
        int temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    /**
     * 調整索引為 index 處的資料,使其符合堆的特性。
     *
     * @param index 需要堆化處理的資料的索引
     * @param len 未排序的堆(陣列)的長度
     */
    private void maxHeapify(int index, int len) {
        int li = (index << 1) + 1; // 左子節點索引
        int ri = li + 1;           // 右子節點索引
        int cMax = li;             // 子節點值最大索引,預設左子節點。
        if (li > len) return;      // 左子節點索引超出計算範圍,直接返回。
        if (ri <= len && arr[ri] > arr[li]) // 先判斷左右子節點,哪個較大。
            cMax = ri;
        if (arr[cMax] > arr[index]) {
            swap(cMax, index);      // 如果父節點被子節點調換,
            maxHeapify(cMax, len);  // 則需要繼續判斷換下後的父節點是否符合堆的特性。
        }
    }

    /**
     * 測試用例
     *
     * 輸出:
     * [0, 0, 0, 1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8, 8, 9, 9, 9]
     */
    public static void main(String[] args) {
        int[] arr = new int[] {3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6};
        new HeapSort(arr).sort();
        System.out.println(Arrays.toString(arr));
    }
}