排序系列【比較排序系列之】堆排序

堆排序,顧名思義是通過直接選擇排序衍生而來的。直接選擇排序是直接從剩餘記錄中線性的查找最大記錄的方法,並沒有巧妙的利用前一輪查找所得到的信息,而堆排序,利用堆數據結構來保存剩餘記錄相對大小的信息,因而是更有效的選擇排序。
堆分爲最大堆和最小堆,本篇我們通過最大堆來實現我們的功能。
最大堆需要滿足的條件: 堆中每個父節點中的數據項都要大於或等於其子節點中的數據項。
堆排序主要有兩個步驟

  1. 對所有記錄建立一個最大堆。
  2. 取出堆頂的最大記錄與數組末端進行交換,最大記錄放在下標n-1的位置;
  3. 對剩餘堆記錄進行調整,再次形成一個最大堆;
  4. 再次取出對頂的最大記錄與數組末端進行交換,最大記錄放在下標n-2的位置;
  5. 不斷重複,直到堆爲空,也就是排序完成。

示例數組如下:【49,38,65,97,76,13,27,49】
通過篩選法建最大堆的前提條件

  1. 堆的初始位置從0開始,依次遞增;
  2. 若父結點的位置爲i;則左孩子結點位置爲2i+1;右孩子結點位置爲2i+2;
  3. 篩選位置從最後一個非結點編號開始,也就是n/2-1向下取整。

    初始堆如下:
    初始堆

篩選位置從最後一個非結點編號開始,n=8,所以初始篩選位置爲i=3,也就是i=97;
因爲97>49,所以位置不變;然後繼續比較i,i–;
i=2時,因爲13<27,且65>27,所以位置依舊不變。
i=1時,因爲97>76,所以比較38和97,因爲38<97所以交換位置;又因爲38<49所以繼續交換位置,最後堆位置如下:
i=1時
i=0時,因爲97>65,所以比較49和97,因爲49<97,交換位置;又因爲49<76,繼續交換位置,最後堆位置如下:
i=0時
到此位置,排序完成,堆變成了一個最大堆。
接下來則進行交換流程,將n-1位置的值與堆頂位置的值進行交換;

1、 i=7;交換位置7上的值和堆頂的值
交換1
交換完畢,再次調整除了i=7之外的堆元素,再次轉換成一個最大堆。
交換2

2、當i=6;交換位置6和堆頂的值,然後調整屬於的元素;
3、當i=5;交換位置5和堆頂的值,然後調整屬於的元素;

當i=1時;交換位置1和堆頂的值,交換流程到此結束,最後的堆如下:
end

以上是針對堆排序的分析流程,如下則是代碼:

public static void main(String[] args) {
        int[] array = {49,38,65,97,76,13,27,49};
        heapSort(array,array.length);
    }

    /**
     * 堆排序的主過程
     * @param array
     * @param n
     */
    static void heapSort(int[] array, int n) {

        int i;
        int temp;
        for (i = n / 2 - 1; i >= 0; --i) {
            sift(array, i, n - 1);
        }
        for (i = n - 1; i > 0; --i) {
            temp = array[0];
            array[0] = array[i];
            array[i] = temp;
            sift(array, 0, i - 1);
        }

    }

    /**
     * 調整函數
     * @param arr
     * @param low
     * @param high
     */
    static void sift(int arr[], int low, int high) {
        int i = low;
        int j = 2 *i  + 1;
        int temp = arr[i];
        while (j <= high) {
            //判定左孩子結點和右孩子結點的大小,進而決定跟結點到底與誰作比較
            if (j < high && arr[j] < arr[j + 1]) {
                ++j;
            }
            if (temp < arr[j]) {
                arr[i] = arr[j];
                i = j;
                j = 2 * i + 1;
            } else {
                break;
            }
        }
        arr[i] = temp;
    }

而時間複雜度,我們通過以上分析和代碼都可以發現,其堆排序的過程其實就是建最大堆和交換最大值之後重建堆的過程。
對於有N個結點的堆 ,其層數爲logN.設置i表示二叉樹的層編號,則第i層的結點數最多有2^i個。
就拿n=8舉例:i=0,1,2,3
當i=3時,該層含有的最多結點個數爲2^3=8個,因爲是最底層,所以不需要比較,比較個數爲logN-i=0;
當i=2時,該層含有的最多結點個數爲2^2=4個,需要和其孩子結點(比較到最後一層)進行比較,logN-2=1;
當i=1時,該層含有的最多結點個數爲2^1=2個,需要和其孩子結點進行比較(比較到最後一層)進行比較,logN-1=2次,所以當n=8時,logN=3,需要的比較次數爲:2^3(3-3)+2^2(3-2)+2^1(3-1);
所以當結點數爲N時,堆排序需要的比較次數爲:∑求和,logN,i=0開始,2^i(logN-i).

而對於重建堆的過程,待定。

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