歸併排序及其優化

簡單實現

若是有兩個數組已經有序,那麼能夠把這兩個數組歸併爲更大的一個有序數組。歸併排序即是創建在這一基礎上。要將一個數組排序,能夠將它劃分爲兩個子數組分別排序,而後將結果歸併,使得總體有序。子數組的排序一樣採用這樣的方法排序,這個過程是遞歸的。數組

下面舉例說明,假如要對數組a={2,1,3,5,2,3}進行排序,那麼把數組劃分爲{2,1,3}{5,2,3}兩個子數組,這兩個子數組排序後變爲{1,2,3}{2,3,5},而後對這兩個數組進行歸併操做便獲得最終的有序數組。代碼實現以下:優化

void sort(int[] a) {
    int[] aux = new int[a.length];    //輔助數組
    mergeSort(a, 0, a.length - 1, aux);
}

void mergeSort(int[] a, int lo, int hi, int[] aux) {
    if (hi <= lo)
        return;
    int mid = lo + (hi - lo) / 2;
    mergeSort(a, lo, mid, aux);
    mergeSort(a, mid + 1, hi, aux);
    merge(a, lo, mid, hi, aux);

}

void merge(int[] a, int lo, int mid, int hi, int[] aux) {
    int i = lo, j = mid + 1;
    for (int k = lo; k <= hi; k++) {
        aux[k] = a[k];
    }
    for (int k = lo; k <= hi; k++) {
        if (i > mid)
            a[k] = aux[j++];
        else if (j > hi)
            a[k] = aux[i++];
        else if (aux[i] <= aux[j])
            a[k] = aux[i++];
        else
            a[k] = aux[j++];

    }
}

對於歸併排序有幾點說明:code

  1. 歸併排序的時間複雜度是O(NLogN),空間複雜度是O(N)
  2. 輔助數組是一個共用的數組。若是在每一個歸併的過程當中都申請一個臨時數組會形成比較大的時間開銷。
  3. 歸併的過程須要將元素複製到輔助數組,再從輔助數組排序複製回原數組,會拖慢排序速度。

優化

歸併排序有如下幾點優化方法:排序

  1. 和快速排序同樣,對於小數組可使用插入排序或者選擇排序,避免遞歸調用。
  2. merge()調用以前,能夠判斷一下a[mid]是否小於等於a[mid+1]。若是是的話那麼就不用歸併了,數組已是有序的。緣由很簡單,既然兩個子數組已經有序了,那麼a[mid]是第一個子數組的最大值,a[mid+1]是第二個子數組的最小值。當a[mid]<=a[mid+1]時,數組總體有序。
  3. 爲了節省將元素複製到輔助數組做用的時間,能夠在遞歸調用的每一個層次交換原始數組與輔助數組的角色。
  4. merge()方法中的歸併過程須要判斷ij是否已經越界,即某半邊已經用盡。能夠用另外一種方式,去掉檢測是否某半邊已經用盡的代碼。具體步驟是將數組a[]的後半部分以降序的方式複製到aux[],而後從兩端歸併。對於數組{1,2,3}{2,3,5},第一個子數組照常複製,第二個則從後往前複製,最終aux[]中的元素爲{1,2,3,5,3,2}。這種方法的缺點是使得歸併排序變爲不穩定排序。代碼實現以下:遞歸

    void merge(int[] a, int lo, int mid, int hi, int[] aux) {
    for (int k = lo; k <= mid; k++) {
        aux[k] = a[k];
    }
    for (int k = mid + 1;k <= hi; k++) {
        aux[k] = a[hi - k + mid + 1];
    }
    int i = lo, j = hi;      //從兩端往中間
    for (int k = lo; k <= hi; k++)
        if (aux[i] <= aux[j]) a[k] = aux[i++];
        else a[k] = aux[j--];
    }

另外一種實現:自底向上的歸併排序

在上面的實現中,至關於將一個大問題分割成小問題分別解決,而後用全部小問題的答案來解決整個大問題。將一個大的數組的排序劃分爲小數組的排序是自頂向下的排序。還有一種實現是自底向上的排序,即先兩兩歸併,而後四四歸併......代碼實現以下:基礎

void sort(int[] a) {
    int N = a.length;
    int[] aux = new int[N];
    for (int sz = 1; sz < N; sz += sz) {
        for (int lo = 0; lo < N - sz; lo += sz + sz) {
            //在每輪歸併中,最後一次歸併的第二個子數組可能比第一個子數組要小
            merge(a, lo, lo + sz - 1, Math.min(lo + sz + sz - 1, N - 1), aux);
        }
    }
}
相關文章
相關標籤/搜索