演算法漫遊指北(第十一篇):歸併排序演算法描述、動圖演示、程式碼實現、過程分析、複雜度
一、歸併排序
歸併排序是建立在歸併操作上的一種有效的排序演算法。該演算法是採用分治法(Divide and Conquer)的一個非常典型的應用。將已有序的子序列合併,得到完全有序的序列;即先使每個子序列有序,再使子序列段間有序。若將兩個有序表合併成一個有序表,稱為2-路歸併。
-
所謂“分”,指的是將一個亂序數列不斷進行二分,得到許多短的序列。
-
所謂“治”,指的是將這些短序列進行兩兩合併,然後將合併的結果作為新的序列,再與其他序列進行合併,最終得到一個新的序列。
歸併排序演算法描述
-
把長度為n的輸入序列分成兩個長度為n/2的子序列;
-
對這兩個子序列分別採用歸併排序;
-
將兩個排序好的子序列合併成一個最終的排序序列。
歸併排序動圖演示
動畫演示圖2
歸併排序程式碼實現
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]進行歸併排序
1、拆分陣列
假設陣列一共有 n 個元素,我們遞迴對陣列進行折半拆分即n//2
,直到每組只有一個元素為止。
2、合併陣列
演算法會從最小陣列開始有序合併,這樣合併出來的陣列一直是有序的,所以合併兩個有序陣列是歸併演算法的核心,這裡用兩個簡單陣列示例:
步驟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)。
-
穩定性:穩定
從演算法中從左到右逐一比較,較小的先放入新陣列,所以兩個值相同的元素,排序後依然保持原先後順序。
&n