說說你對歸併排序的理解?如何實現?應用場景?

林恒發表於2024-04-24

一、是什麼

歸併排序(Merge Sort)是建立歸併操作上的一種有效,穩定的排序演算法,該演算法是採用分治法的一個非常典型的應用

將已有序的子序列合併,得到完全有序的序列,即先使每個子序列有序,再使子序列段間有序

例如對於含有 n 個記錄的無序表,首先預設表中每個記錄各為一個有序表(只不過表的長度都為 1)

然後進行兩兩合併,使 n 個有序表變為n/2 個長度為 2 或者 1 的有序表(例如 4 個小有序表合併為 2 個大的有序表)

透過不斷地進行兩兩合併,直到得到一個長度為 n 的有序表為止

例如對無序表{49,38,65,97,76,13,27}進行歸併排序分成了分、合兩部分:

如下圖所示:

歸併合過程中,每次得到的新的子表本身有序,所以最終得到有序表

上述分成兩部分,則稱為二路歸併,如果分成三個部分則稱為三路歸併,以此類推

二、如何實現

關於歸併排序的演算法思路如下:

  • 分:把陣列分成兩半,再遞迴對子陣列進行分操作,直至到一個個單獨數字

  • 合:把兩個數合成有序陣列,再對有序陣列進行合併操作,直到全部子陣列合成一個完整的陣列

    • 合併操作可以新建一個陣列,用於存放排序後的陣列
    • 比較兩個有序陣列的頭部,較小者出隊並且推入到上述新建的陣列中
    • 如果兩個陣列還有值,則重複上述第二步
    • 如果只有一個陣列有值,則將該陣列的值出隊並推入到上述新建的陣列中

用程式碼表示則如下圖所示:

function mergeSort(arr) {  // 採用自上而下的遞迴方法
    const len = arr.length;
    if(len < 2) {
        return arr;
    }
    let middle = Math.floor(len / 2),
        left = arr.slice(0, middle),
        right = arr.slice(middle);
    return merge(mergeSort(left), mergeSort(right));
}

function merge(left, right)
{
    const result = [];

    while (left.length && right.length) {
        if (left[0] <= right[0]) {
            result.push(left.shift());
        } else {
            result.push(right.shift());
        }
    }

    while (left.length)
        result.push(left.shift());

    while (right.length)
        result.push(right.shift());

    return result;
}

上述歸併分成了分、合兩部分,在處理分過程中遞迴呼叫兩個分的操作,所花費的時間為2乘T(n/2),合的操作時間複雜度則為O(n),因此可以得到以下公式:

總的執行時間 = 2 × 輸入長度為n/2sort函式的執行時間 + merge函式的執行時間O(n)

當只有一個元素時,T(1) = O(1)

如果對T(n) = 2 * T(n/2) + O(n)進行左右 / n的操作,得到 T(n) / n = (n / 2) * T(n/2) + O(1)

現在令 S(n) = T(n)/n,則S(1) = O(1),然後利用表示式帶入得到S(n) = S(n/2) + O(1)

所以可以得到:S(n) = S(n/2) + O(1) = S(n/4) + O(2) = S(n/8) + O(3) = S(n/2^k) + O(k) = S(1) + O(logn) = O(logn)

綜上可得,T(n) = n * log(n) = nlogn

關於歸併排序的穩定性,在進行合併過程,在1個或2個元素時,1個元素不會交換,2個元素如果大小相等也不會交換,由此可見歸併排序是穩定的排序演算法

三、應用場景

在外排序中通常使用排序-歸併的策略,外排序是指處理超過記憶體限度的資料的排序演算法,通常將中間結果放在讀寫較慢的外儲存器,如下分成兩個階段:

  • 排序階段:讀入能夠放進記憶體中的資料量,將其排序輸出到臨時檔案,一次進行,將帶排序資料組織為多個有序的臨時檔案
  • 歸併階段:將這些臨時檔案組合為大的有序檔案

例如,使用100m記憶體對900m的資料進行排序,過程如下:

  • 讀入100m資料記憶體,用常規方式排序
  • 將排序後的資料寫入磁碟
  • 重複前兩個步驟,得到9個100m的臨時檔案
  • 將100m的記憶體劃分為10份,將9份為輸入緩衝區,第10份為輸出緩衝區
  • 進行九路歸併排序,將結果輸出到緩衝區
    • 若輸出緩衝區滿,將資料寫到目標檔案,清空緩衝區
    • 若緩衝區空,讀入相應檔案的下一份資料

參考文獻

  • https://baike.baidu.com/item/%E5%BD%92%E5%B9%B6%E6%8E%92%E5%BA%8F/1639015
  • https://chowdera.com/2021/09/20210920201630258d.html#_127
  • https://juejin.cn/post/6844904007899561998

如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。

說說你對歸併排序的理解?如何實現?應用場景?

相關文章