排序算法(7):堆排序

基本思想

  堆排序是基於這種數據結構的一種排序方法。首先將待排序的數組(或序列)構造成完全二叉樹,然後利用完全二叉樹中父節點和孩子節點之間的關係,每次從當前二叉樹中找出最大節點並將其移出未排序部分,達到排序的目的。首先介紹一下一些相關概念:

  • 完全二叉樹:對於一棵深度爲 hh 的二叉樹,如果除了最後一層外,其他每層的節點數都達到最大,且第 hh 層的節點都連續集中在最左邊,那麼這就是一棵完全二叉樹,如下圖所示:

  • :這裏完整的叫法應當是二叉堆,它是一棵堆有序的完全二叉樹。堆分爲最大堆和最小堆,對於最大堆來說所有的父節點均大於等於兩個孩子節點的值,因此根節點應當是最大值節點;相反,對於最小堆,所有的父節點均小於等於子節點的值,因此最小堆的根節點是最小值節點。

  • 堆的順序存儲結構:堆是一棵完全二叉數,因此用數組這種順序存儲結構就可以表示:

    ①:按層序遍歷的順序在數組中存放堆的元素,下標0表示的元素是根節點,其子節點分別爲下標1和下標2……依次類推;

    ②:節點 ii 如果存在左孩子,左孩子的下標爲 2i+12i+1;如果存在右孩子,右孩子的下標爲 2i+22i+2

算法流程

這裏以一個長度爲 nn 的數列arr爲例

  1. 首先將待排序數組構建成一個堆,此時根節點(arr[0])應當爲最大值,整個數組都處於無序區
  2. 將堆中最大的元素移出。具體做法就是交換數組第一個元素 arr[0] 和 最後一個元素arr[n-1],交換之後 arr[n-1] 位於有序區,不再參與後面的排序,此時無序區由 {arr[0]~arr[n-2]} 組成;
  3. 第二步之後,無序區的元素排列是違反堆的規則的,因此要重新對無序區進行調整得到一個包含 {arr[0]~arr[n-2]} 的新堆,然後交換 arr[0] 和 最後一個元素arr[n-2], 並且將arr[n-2] 移到有序區,接下來對剩餘元素重複相同的操作直至排序完成。

演示

代碼實現

堆排序中,關鍵的一個操作就是每次調整無序區的元素使其滿足堆的規則,這個操作通過adjust()函數實現:

private static void adjust(int[] arr, int i, int N){
    while(2*i+1 < N){
        int j = 2 * i + 1; // 當前節點左孩子結點的索引
        if(j+1 < N && (arr[j] < arr[j+1]))   j++;   // 找到最大的孩子節點
        if(arr[i] >= arr[j])   break;   // 表明根節點大於等於所有的孩子節點,不用交換
        int temp = arr[i];  // 交換最大孩子節點和根節點
        arr[i] = arr[j];
        arr[j] = temp;
        i = j;   
    }
}

排序函數如下:

public static void heap_sort(int[] arr){
        // 數組爲空或者長度爲1不需要排序
        if(arr == null || arr.length < 2){
            return;
        }
        
        int N = arr.length;
        for(int i = N/2; i >= 0; i--){
            adjust(arr, i, N);  // 通過調整使得初始堆有序
        }
        
        // 交換根節點(保存着最大元素)和最後一個孩子節點,並從堆中刪除
        while(N-- > 0){
            int temp = arr[N];
            arr[N] = arr[0];
            arr[0] = temp;
            adjust(arr, 0, N);  // 重新調整數組使得堆有序
        }
    }

說明:首先對於原數組進行調整使得初始堆有序,然後通過while循環裏的語句,首先交換當前堆中第一個元素和最後一個元素,並將最後一個元素移出堆(通過N–實現),再重新調整堆使其有序。

分析

  • 時間複雜度

    堆排序遍歷的次數就是由初始元素構成的完全二叉樹的深度,其時間複雜度爲 O(n)O(n),最好和最差情況下也都爲 O(n)O(n)

  • 空間複雜度

    堆排序使用的額外空間跟數組長度無關,因此空間複雜度是 O(1)O(1)

  • 穩定性

    如果存在重複元素,堆排序在交換堆頂元素和最後一個元素的時候,可能將原本靠後的元素前移,造成相同元素相對順序的改變,因此堆排序是不穩定的。

堆排序是目前唯一能夠同時最優地利用時間和空間的排序方法。

參考資料

  1. 一文搞定十大經典排序算法
  2. 《算法(第四版)》
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章