1. 程式人生 > >排序【4.1】歸併排序(MergeSort)

排序【4.1】歸併排序(MergeSort)

歸併排序(Merge Sort)

1、基本思想:

將兩個的有序數列合併成一個有序數列,我們稱之為"歸併"。 歸併排序(Merge Sort)就是利用歸併思想對數列進行排序。根據具體的實現,歸併排序包括"從上往下"和"從下往上"2種方式。

(1)從下往上的歸併排序:將待排序的數列分成若干個長度為1的子數列,然後將這些數列兩兩合併;得到若干個長度為2的有序數列,再將這些數列兩兩合併;得到若干個長度為4的有序數列,再將它們兩兩合併;直接合併成一個數列為止。這樣就得到了我們想要的排序結果。(參考下面的圖片) (2)從上往下的歸併排序:它與"從下往上"在排序上是反方向的。它基本包括3步: ① 分解 – 將當前區間一分為二,即求分裂點 mid = (low + high)/2; ② 求解 – 遞迴地對兩個子區間a[low…mid] 和 a[mid+1…high]進行歸併排序。遞迴的終結條件是子區間長度為1。 ③ 合併 – 將已排序的兩個子區間a[low…mid]和 a[mid+1…high]歸併為一個有序的區間a[low…high]。

下面的圖片很清晰的反映了"從下往上"和"從上往下"的歸併排序的區別。 在這裡插入圖片描述

2、排序過程:

從上往下的歸併排序採用了遞迴的方式實現。它的原理非常簡單,如下圖: 在這裡插入圖片描述 通過"從上往下的歸併排序"來對陣列{80,30,60,40,20,10,50,70}進行排序時:

  1. 將陣列{80,30,60,40,20,10,50,70}看作由兩個有序的子陣列{80,30,60,40}和{20,10,50,70}組成。對兩個有序子樹組進行排序即可。
  2. 將子陣列{80,30,60,40}看作由兩個有序的子陣列{80,30}和{60,40}組成。 將子陣列{20,10,50,70}看作由兩個有序的子陣列{20,10}和{50,70}組成。
  3. 將子陣列{80,30}看作由兩個有序的子陣列{80}和{30}組成。 將子陣列{60,40}看作由兩個有序的子陣列{60}和{40}組成。 將子陣列{20,10}看作由兩個有序的子陣列{20}和{10}組成。 將子陣列{50,70}看作由兩個有序的子陣列{50}和{70}組成。

從下往上的歸併排序的思想正好與"從下往上的歸併排序"相反。如下圖: 在這裡插入圖片描述 通過"從下往上的歸併排序"來對陣列{80,30,60,40,20,10,50,70}進行排序時:

  1. 將陣列{80,30,60,40,20,10,50,70}看作由8個有序的子陣列{80},{30},{60},{40},{20},{10},{50}和{70}組成。
  2. 將這8個有序的子數列兩兩合併。得到4個有序的子樹列{30,80},{40,60},{10,20}和{50,70}。
  3. 將這4個有序的子數列兩兩合併。得到2個有序的子樹列{30,40,60,80}和{10,20,50,70}。
  4. 將這2個有序的子數列兩兩合併。得到1個有序的子樹列{10,20,30,40,50,60,70,80}。
3、時間複雜度:

歸併排序的時間複雜度是O

(NlgN)O(N*lgN)。 假設被排序的數列中有N個數。遍歷一趟的時間複雜度是O(N),需要遍歷多少次呢? 歸併排序的形式就是一棵二叉樹,它需要遍歷的次數就是二叉樹的深度,而根據完全二叉樹可以得出它的時間複雜度是O(NlgN)O(N*lgN)

4、穩定性:

歸併排序是穩定的演算法,它滿足穩定演算法的定義。

5、應用:

歸併排序的兩個應用: 1、資料庫:基於排序的關係操作中,利用歸併排序將表資料劃分為n個有序子表,然後將資料逐片讀入記憶體中,執行具體關係操作後輸出。資料庫中物理計劃的選擇方法可選用動態規劃、分支定界、… 2、MapReduce:在Hadoop MapReduce的shuffle階段,Map後的資料首先對key hash後再以reduce task資料取模(即Partition),key/value對以及Partition的結果都會被寫入記憶體緩衝區,記憶體緩衝區將滿會將資料溢寫到磁碟,溢寫時會對key做排序,發生多次溢寫,最後將多個溢寫檔案歸併到一起,經網路傳輸進入相應的TaskTracker,Reduce端執行合併操作。

6、演算法實現(python3)
"""
歸併排序:

基本思想:
將兩個的有序數列合併成一個有序數列,我們稱之為"歸併"。
① 分解 -- 將當前區間一分為二,即求分裂點 mid = (low + high)/2;
② 求解 -- 遞迴地對兩個子區間a[low...mid] 和 a[mid+1...high]進行歸併排序。遞迴的終結條件是子區間長度為1。
③ 合併 -- 將已排序的兩個子區間a[low...mid]和 a[mid+1...high]歸併為一個有序的區間a[low...high]。

時間複雜度:O(nlog(n))

引數說明:
data -- 包含兩個有序區間的陣列
start -- 第1個有序區間的起始地址。
mid   -- 第1個有序區間的結束地址。也是第2個有序區間的起始地址。
end   -- 第2個有序區間的結束地址。

"""
def merge(data, start, mid, end):
    tmp = []
    i = start
    j = mid + 1
    while i <= mid and j <= end:      # O(n)
        if data[i] <= data[j]:
            tmp.append(data[i])
            i += 1
        else:
            tmp.append(data[j])
            j += 1

    for ii in range (i, mid + 1):
        tmp.append(data[ii])

    for jj in range(j, end + 1):
        tmp.append(data[jj])

    for k in range(len(tmp)):
        data[start + k] = tmp[k]


def mergeSort(data, start, end):
    if data == None or start >= end:
        return
    mid = (end + start) // 2      # O(log(n))
    mergeSort(data, start, mid)
    mergeSort(data, mid + 1, end)
    merge(data, start, mid, end)


if __name__ == '__main__':
    data = [20, 40, 30, 10, 60, 50]
    data_len = len(data)
    print("before sort:", data)

    mergeSort(data, 0, data_len-1)

    print("after sort:", data)

執行結果:

before sort: [20, 40, 30, 10, 60, 50]
after sort: [10, 20, 30, 40, 50, 60]