圖解排序算法(04) -- 堆排序

1、初識堆排序

二叉堆的特性:

  • 最大堆的堆頂是整個堆中的最大元素
  • 最小堆的堆頂是整個堆中的最小元素

以最大堆爲例,如果刪除一個最大堆的堆頂(並不是完全刪除,而是跟末尾的點交換位置),經過自我調整,第2大的元素就會被交換上來,成爲最大堆的新堆頂,如下圖:
在這裏插入圖片描述
如上圖所示,在刪除值爲10的堆頂節點後,經過調整,值爲9的新節點就會頂替上來;在刪除值爲9的堆頂節點後,經過調整,值爲8的新節點就會頂替上來……
由於二叉堆的這個特性,每一次刪除舊堆頂,調整後的新堆頂都是大小僅次於舊堆頂的節點。那麼只要反覆刪除堆頂,反覆調整二叉堆,所得到的集合就會成爲 一個有序集合,過程如下:

  • 刪除節點9,節點8成爲新堆頂
    在這裏插入圖片描述
  • 刪除節點8,節點7成爲新堆頂
    在這裏插入圖片描述
  • 刪除節點7,節點6成爲新堆頂
    在這裏插入圖片描述
  • 刪除節點6,節點5成爲新堆頂
    在這裏插入圖片描述
  • 刪除節點5,節點4成爲新堆頂
    在這裏插入圖片描述
  • 刪除節點4,節點3成爲新堆頂
    在這裏插入圖片描述
  • 刪除節點3,節點2成爲新堆頂
    在這裏插入圖片描述
    經過上述步驟,原本的最大二叉堆已經變成了一個從小到大的有序集合
    二叉堆實際存儲在數組中,數組中的元素排列如下:
    在這裏插入圖片描述
    由此,歸納出堆排序算法的步驟:
  1. 把無序數組構建成二叉堆。需要從小到大排序,則構建成最大堆;需要從大到小排序,則構建成最小堆。
  2. 循環刪除堆頂元素,替換到二叉堆的末尾,調整堆產生新的堆頂。

2、堆排序代碼實現

Code:

import java.util.Arrays;

public class TreeNode5 {
    /**
     * “下沉”調整
     * @param array  待調整的堆
     * @param parentIndex      要“下沉”的父節點
     *  @param length       堆的有效大小
     */
    public static void downAdjust(int[] array, int parentIndex, int length) {
        // temp 保存父節點值,用於最後的賦值
        int temp = array[parentIndex];
        int childIndex = 2 * parentIndex + 1;
        while (childIndex < length) {
            // 如果有右孩子,且右孩子大於左孩子的值,則定位到右孩子
            if (childIndex + 1 < length && array[childIndex + 1] > array[childIndex]) {
                childIndex++;
            }
            // 如果父節點大於任何一個孩子的值,則直接跳出
            if (temp >= array[childIndex])
                break;
                //無須真正交換,單向賦值即可
                array[parentIndex] = array[childIndex];
                parentIndex = childIndex;
                childIndex = 2 * childIndex + 1;
            }
        array[parentIndex] = temp;
    }

    /**
     * 堆排序(升序)
     * @param array  待調整的堆
     */
    public static void heapSort(int[] array){
        //1、把無序數組構建成最大堆
        for(int i = (array.length-2)/2;i >= 0;i--){
            downAdjust(array,i,array.length);
        }
        System.out.println("無序數組構建成的最大堆:" + Arrays.toString(array));
        //2、循環刪除堆頂元素,移到集合尾部,調整堆產生新的堆頂
        for(int i = array.length - 1;i > 0;i--){
            //最後1個元素和第一個元素進行交換
            int temp = array[i];
            array[i] = array[0];
            array[0] = temp;
            //“下沉”調整最大堆
            downAdjust(array,0,i);
        }
    }
    
    public static void main(String[] args) {
       int[] array = new int[] {1,3,2,6,5,7,8,9,10,0};
       System.out.println("排序前:"+ Arrays.toString(array));
       heapSort(array);
       System.out.println("排序後:"+ Arrays.toString(array));
       }
}

編譯輸出:
在這裏插入圖片描述

3、堆排序總結

排序算法的步驟:

  • 把無序數組構建成二叉堆;
  • 循環刪除堆頂元素,並將該元素移到集合尾部,調整堆產生新的堆頂:
    首先把無序數組構建成二叉堆,這一步的時間複雜度是O(n);
    然後進行n-1次循環;每次循環調用一次downAdjust方法,計算規模是 (n-1)×logn ,時間複雜度爲O(nlogn)。
    兩個步驟是並列關係,所以整體的時間複雜度是O(nlogn)

堆排序和快速排序的區別和聯繫:
相同點:堆排序和快速排序的平均時間複雜度都是O(nlogn),並且都是不穩定排序;
不同點:

  • 快速排序的最壞時間複雜度是O(n²),而堆排序的最壞時間複雜度穩定在O(nlogn);
  • 快速排序遞歸和非遞歸方法的平均空間複雜度都是O(logn),而堆排序的空間複雜度是O(1)。

———————————————————————————————————————
內容來源:《漫畫算法》
關注公衆號,回覆 【算法】,獲取高清算法書!
在這裏插入圖片描述

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