數據結構常見的八大排序算法之堆排序

                                   數據結構常見的八大排序算法之堆排序

一、簡介 

      1991年的計算機先驅獎獲得者、斯坦福大學計算機科學系教授羅伯特·弗洛伊德(Robert W.Floyd) 和威廉姆斯(J.Williams) 在1964年共同發明了著名的堆排序算法(Heap Sort)。 

堆的定義如下:nn個元素的序列{k1,k2,..,kn},當且僅當滿足下關係時,稱之爲堆。

把此序列對應的二維數組看成一個完全二叉樹。那麼堆的含義就是:完全二叉樹中任何一個非葉子節點的值均不大於(或不小於)其左,右孩子節點的值。 由上述性質可知大頂堆的堆頂的關鍵字肯定是所有關鍵字中最大的,小頂堆的堆頂的關鍵字是所有關鍵字中最小的。因此我們可使用大頂堆進行升序排序, 使用小頂堆進行降序排序。

堆排序(Heap Sort)是指利用堆這種數據結構所設計的一種排序算法。堆是一個近似完全二叉樹的結構,並同時滿足堆積的性質:即子結點的鍵值或索引總是小於(或者大於)它的父節點

二、基本思想

此處以大頂堆爲例,堆排序的過程就是將待排序的序列構造成一個堆,選出堆中最大的移走,再把剩餘的元素調整成堆,找出最大的再移走,重複直至有序。

三、算法思路

  1. 先將初始序列K[1..n]K[1..n]建成一個大頂堆, 那麼此時第一個元素K1K1最大, 此堆爲初始的無序區.
  2. 再將關鍵字最大的記錄K1K1 (即堆頂, 第一個元素)和無序區的最後一個記錄 KnKn 交換, 由此得到新的無序區K[1..n−1]K[1..n−1]和有序區K[n]K[n], 且滿足K[1..n−1].keys⩽K[n].keyK[1..n−1].keys⩽K[n].key
  3. 交換K1K1 和 KnKn 後, 堆頂可能違反堆性質, 因此需將K[1..n−1]K[1..n−1]調整爲堆. 然後重複步驟2, 直到無序區只有一個元素時停止。

動畫效果: 

堆排序過程

四、時空複雜度

  1. 建立堆的過程, 從length/2 一直處理到0, 時間複雜度爲O(n);
  2. 調整堆的過程是沿着堆的父子節點進行調整, 執行次數爲堆的深度, 時間複雜度爲O(lgn);
  3. 堆排序的過程由n次第2步完成, 時間複雜度爲O(nlgn).

由於堆排序中初始化堆的過程比較次數較多, 因此它不太適用於小序列。 同時由於多次任意下標相互交換位置, 相同元素之間原本相對的順序被破壞了, 因此, 它是不穩定的排序。

五、代碼實現 

從算法描述來看,堆排序需要兩個過程,一是建立堆,二是堆頂與堆的最後一個元素交換位置。所以堆排序有兩個函數組成。一是建堆函數,二是反覆調用建堆函數以選擇出剩餘未排元素中最大的數來實現排序的函數。

總結起來就是定義了以下幾種操作:

  • 最大堆調整(Max_Heapify):將堆的末端子節點作調整,使得子節點永遠小於父節點
  • 創建最大堆(Build_Max_Heap):將堆所有數據重新排序
  • 堆排序(HeapSort):移除位在第一個數據的根節點,並做最大堆調整的遞歸運算

對於堆節點的訪問:

  • 父節點i的左子節點在位置:(2*i+1);
  • 父節點i的右子節點在位置:(2*i+2);
  • 子節點i的父節點在位置:floor((i-1)/2);
public class HeapSort {

    public static void main(String[] args) {
        int[] a = {1, 3, 2, 9, 6, 8};
        System.out.println("排序前:"+ Arrays.toString(a));
        sort(a);
        System.out.println("排序後:"+ Arrays.toString(a));
    }

    public static void sort(int[] a) {
        for (int i = a.length - 1; i > 0; i--) {
            max_heapify(a, i);
            //堆頂元素(第一個元素)與Kn交換
            int temp = a[0];
            a[0] = a[i];
            a[i] = temp;
        }
    }

    /***
     *  將數組堆化
     *  i = 第一個非葉子節點。
     *  從第一個非葉子節點開始即可。無需從最後一個葉子節點開始。
     *  葉子節點可以看作已符合堆要求的節點,根節點就是它自己且自己以下值爲最大。
     */
    public static void max_heapify(int[] a, int n) {
        int child;
        for (int i = (n - 1) / 2; i >= 0; i--) {
            //左子節點位置
            child = 2 * i + 1;
            //右子節點存在且大於左子節點,child變成右子節點
            if (child != n && a[child] < a[child + 1]) {
                child++;
            }
            //交換父節點與左右子節點中的最大值
            if (a[i] < a[child]) {
                int temp = a[i];
                a[i] = a[child];
                a[child] = temp;
            }
        }
    }
}

 

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