圖文並茂!一文教你掌握十大排序算法之堆排序

1. 堆排序算法原理及思想

堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。它的最好、最壞和平均時間複雜度都爲O(N*logN),它是一種不穩定排序,下面瞭解堆這種數據結構。

堆是一種特殊的完全二叉樹,分爲大頂堆和小頂堆;在原有的二叉樹的性質上,大頂堆還需要嚴格保證父節點的值大於等於孩子節點的值;小頂堆則嚴格保證父節點的值小於等於孩子節點的值。

堆只是一個邏輯結構,本質上還是數組。如果用數組表示堆這個結構,大頂堆和小頂堆必須滿足的條件用數組表示爲:

大頂堆:arr[i] ≥ arr[2i+1] && arr[i] ≥ arr[2i+2]

小頂堆:arr[i] ≤ arr[2i+1] && arr[i] ≤ arr[2i+2]

如果要將數組升序排序則構造大頂堆,如果需要降序則構造小頂堆。

構造大頂堆的目的是爲了每一輪排序確定一個最大值並將其放到堆的尾部(數組的尾部)

構造小頂堆的目的是爲了每一輪排序確定一個最小值並將其放到堆的尾部(數組的尾部)

2. 堆排序算法步驟

以升序爲例,第一步是把數組構造成符合大頂堆的狀態:從下往上、從右往左開始判斷非葉子節點是否符合大頂堆的結構,這裏運用到完全二叉樹的一個性質:第一個非葉子節點的數組下標爲:index = (arr.length / 2) - 1

2.1 構造大頂堆

【下圖左上】以數組[33, 35, 41, 26, 78]爲例,第一個非葉子節點的下標index = (5 / 2) - 1 = 1,所以35是第一個非葉子節點,判斷以35爲根節點的完全二叉樹是否滿足大頂堆的條件,發現35 < 78,所以交換位置,數組更新爲[33, 78, 41, 26, 35],由於35是第一個非葉子節點,所以它的孩子節點必定是葉子節點,無需繼續判斷是否滿足大頂堆的條件。

【下圖右上】然後index--,index = 0,判斷33爲根節點的完全二叉樹是否滿足大頂堆的條件,發現33 < 78,所以交換位置,數組更新爲[78, 33, 41, 26, 35]。

【下圖右下】由於33與78交換了位置,破壞了78在index = 1時作爲根節點的大頂堆,需要再判斷33爲根節點的完全二叉樹是否符合大頂堆條件。33 < 35,所以交換位置,數組更新爲[78, 35, 41, 26, 33],此時已經到了第一個非葉子節點,不需要繼續判斷。

【下圖左下】大頂堆構建完成。

2.2 首尾元素交換並重建大頂堆

下圖使第一輪的堆排序,將堆頂元素與堆尾元素交換,使堆尾元素最大,然後重新調整堆結構,再將堆頂元素與堆尾元素互換,得出第二大的元素,然後反覆的交換、調整、交換、調整。

注意每次需要重建的堆元素不應該包括剛剛交換的堆尾元素,因爲最大的元素已經沉到堆的底部了。【下圖綠色元素78不參與重建大頂堆】

3. 堆排序思路總結

1)根據數組需要升序/降序排列構造大頂堆/小頂堆。

2)將堆首元素和堆尾元素互換,使得最大/最小值沉到堆的底部(數組末端)。

3)重新調整堆的結構

4)重複第2、3步,直至整個數組有序。

4. 代碼實現

/**
 * @author Zeng
 * @date 2020/3/3 22:28
 */
public class 堆排序1 {

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

    public static void heapSort(int[] arr){
        //構造大頂堆
        for (int i = arr.length / 2 - 1; i >= 0; i--){
            adjustHeap(arr, i, arr.length);
        }
        //堆頂元素與堆尾元素作交換
        for (int j = arr.length - 1; j >= 0; j--){
            swap(arr, 0, j);
            adjustHeap(arr, 0, j);
        }
    }

    public static void adjustHeap(int[] arr, int begin, int end){
        for (int i = 2 * begin + 1; i < end; i=2*begin+1){
            //如果右孩子比左孩子大就將指針指向右孩子
            if(i + 1 < end && arr[i] < arr[i + 1]){
                i++;
            }
            //如果父親節點比孩子節點小則交換它們的位置,並且將指針指向孩子節點,下一步繼續構造以孩子節點爲根節點的大頂堆
            if(arr[begin] < arr[i]){
                swap(arr, begin, i);
                begin = i;
            }else{
                //父節點比左右孩子節點都大,則說明已經符合大頂堆的要求
                break;
            }
        }
    }

    public static void main(String[] args) {
        int[] arr = new int[]{33, 35, 41, 26, 78};
        heapSort(arr);
        System.out.println(Arrays.toString(arr));
    }

}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章