排序【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}進行排序時:
- 將陣列{80,30,60,40,20,10,50,70}看作由兩個有序的子陣列{80,30,60,40}和{20,10,50,70}組成。對兩個有序子樹組進行排序即可。
- 將子陣列{80,30,60,40}看作由兩個有序的子陣列{80,30}和{60,40}組成。 將子陣列{20,10,50,70}看作由兩個有序的子陣列{20,10}和{50,70}組成。
- 將子陣列{80,30}看作由兩個有序的子陣列{80}和{30}組成。 將子陣列{60,40}看作由兩個有序的子陣列{60}和{40}組成。 將子陣列{20,10}看作由兩個有序的子陣列{20}和{10}組成。 將子陣列{50,70}看作由兩個有序的子陣列{50}和{70}組成。
從下往上的歸併排序的思想正好與"從下往上的歸併排序"相反。如下圖: 通過"從下往上的歸併排序"來對陣列{80,30,60,40,20,10,50,70}進行排序時:
- 將陣列{80,30,60,40,20,10,50,70}看作由8個有序的子陣列{80},{30},{60},{40},{20},{10},{50}和{70}組成。
- 將這8個有序的子數列兩兩合併。得到4個有序的子樹列{30,80},{40,60},{10,20}和{50,70}。
- 將這4個有序的子數列兩兩合併。得到2個有序的子樹列{30,40,60,80}和{10,20,50,70}。
- 將這2個有序的子數列兩兩合併。得到1個有序的子樹列{10,20,30,40,50,60,70,80}。
3、時間複雜度:
歸併排序的時間複雜度是。 假設被排序的數列中有N個數。遍歷一趟的時間複雜度是O(N),需要遍歷多少次呢? 歸併排序的形式就是一棵二叉樹,它需要遍歷的次數就是二叉樹的深度,而根據完全二叉樹可以得出它的時間複雜度是。
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]