算法
所謂「分」,指的是將一個亂序數列不斷進行二分,獲得許多短的序列。數組
所謂「治」,指的是將這些短序列進行兩兩合併,而後將合併的結果做爲新的序列,再與其餘序列進行合併,最終獲得一個新的序列。app
把長度爲n的輸入序列分紅兩個長度爲n/2的子序列;ide
對這兩個子序列分別採用歸併排序;動畫
將兩個排序好的子序列合併成一個最終的排序序列。spa
動畫演示圖23d
def merge_sort(alist): """歸併排序""" n = len(alist) #遞歸結束條件 # 剩一個或沒有直接返回,不用排序 if n <= 1: return alist # 拆分 mid = n//2 # left 採用歸併排序後造成的有序的新的列表 left_li = merge_sort(alist[:mid]) # right 採用歸併排序後造成的有序的新的列表 right_li = merge_sort(alist[mid:]) # 將兩個有序的子序列合併爲一個新的總體 # merge(left, right) left_pointer, right_pointer = 0, 0 result = [] while left_pointer < len(left_li) and right_pointer < len(right_li): if left_li[left_pointer] <= right_li[right_pointer]: result.append(left_li[left_pointer]) left_pointer += 1 else: result.append(right_li[right_pointer]) right_pointer += 1 #將兩個列表按順序融合爲一個列表result result += left_li[left_pointer:] result += right_li[right_pointer:] return result
示例 針對 arrli = [6,5,3,1,8,7,2,4]進行歸併排序指針
假設數組一共有 n 個元素,咱們遞歸對數組進行折半拆分即n//2
,直到每組只有一個元素爲止。code
算法會從最小數組開始有序合併,這樣合併出來的數組一直是有序的,因此合併兩個有序數組是歸併算法的核心,這裏用兩個簡單數組示例:blog
步驟1:新建一個空數組存放合併結果,用left_pointer
和right_pointer
兩個輔助指針記錄兩個數組當前操做位置;
步驟2:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到left_pointer
和right_pointer
指針超出尾部;
步驟3:新建一個空數組存放合併結果,用l
和r
兩個輔助指針記錄兩個數組當前操做位置;
步驟4:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到l
或r
指針超出尾部;
繼續比較寫入較小的元素到新數組
繼續比較寫入較小的元素到新數組
指針還沒有移到尾部的數組,說明還有剩餘元素,將剩餘元素合併到新數組尾部。
步驟5:新建一個空數組存放合併結果,用l
和r
兩個輔助指針記錄兩個數組當前操做位置;
步驟6:從左到右逐一比較兩個小數組中的元素,較小的元素先放入新數組,指針移位,直到l
或r
指針超出尾部;
將較小的元素寫入到新數組
繼續比較寫入較小的元素到新數組
繼續比較寫入較小的元素到新數組
繼續比較寫入較小的元素到新數組
步驟7:右邊的指針還沒有移到尾部的數組,說明還有剩餘元素,將剩餘元素合併到新數組尾部。
完成歸併排序,返回排好序的新數組
時間複雜度:O(nlogn)
歸併排序把數組一層層折半分組,長度爲 n 的數組,折半層數就是 logn,每一層進行操做的運算量是 n,得出時間複雜度 O(nlogn)。
空間複雜度:O(n)
每次歸併操做須要建立額外的新數組,佔用空間爲 n,但這部分額外空間會隨着方法的結束而釋放,因此只須要算單次歸併操做開闢的空間便可,得出空間複雜度 O(n)。
穩定性:穩定
從算法中從左到右逐一比較,較小的先放入新數組,因此兩個值相同的元素,排序後依然保持原前後順序。