1. 程式人生 > >歸併排序詳解(附python實現)

歸併排序詳解(附python實現)

一、介紹

歸併排序(Merge Sort)指的是利用分治和遞迴的思想,對一個亂序的數列進行排序。

  • 所謂“分”,指的是將一個亂序數列不斷進行二分,得到許多短的序列。
  • 所謂“治”,指的是將這些短序列進行兩兩合併,然後將合併的結果作為新的序列,再與其他序列進行合併,最終得到一個新的序列。

因此,歸併排序具體包括兩個步驟:分散、和並。

二、具體步驟

分散和合並的示例參見下圖: 在這裡插入圖片描述

1、分散(從整到零)

將原始序列一刀切開,劃分成兩個序列。然後每個序列繼續切開,又劃分成兩個更小的序列。

這樣一層層地劃分,可以得到一顆劃分的樹,樹的葉子節點就是原始序列的每個元素了。

2、合併(從零到整)

從樹的葉子節點開始沿著枝幹往上合併,序列數越來越少,每個序列的長度越來越大,最終剩下一個序列,這個序列即為所求。

這時候注意,每次合併時,輸入的是兩個有序數列,輸出的是一個合併之後的有序數列。

為什麼可以保證輸入的是有序的呢?

可以使用數學歸納法來證明。在合併的起始階段,每個序列就是一個元素,自然是有序的。然後進行正確的合併操作之後,輸出的也是有序的。接下來以這個輸出的序列為輸入繼續合併,自然輸出也是有序的。因此,最終得到的數列,也是有序的。

那麼,關鍵就在於正確的合併操作是怎麼操作了。比如有兩個有序的數列 l1l1l2l2 ,想要對其進行合併,可以:

1、定義兩個指標,分別指向兩個數列的第一個元素;定義新的空數列,作為結果。 2、依次取出指標值,比較大小,將較小值追加到新數列,同時將較小值的指標往後移動一位。 3、如果其中一個指標到頭了,那麼將另一個指標剩下的數列直接追加到結果數列即可。 4、直至兩個指標都指到了最後一位。

這樣,得到的結果數列便是有序數列。

三、複雜度分析

時間複雜度

由“分散”和“合併”兩部分組成。

“分散”:最後分成一個個的單一元素,因此複雜度為 O(n)O(n)

“合併”:由於每次進行兩兩合併,因此合併的次數為 O(log2n)O(log_2n),而每次合併的複雜度為 O(n)O(n),因此整體合併的複雜度為 O(nlog2n)O(nlog_2n)

因此,整個演算法的時間複雜度為 O(nlog2n)O(nlog_2n)

空間複雜度

歸併的空間複雜度就是那個臨時的陣列和遞迴時壓入棧的資料佔用的空間:n+logn

n + logn;所以空間複雜度為 O(n)O(n)

python實現

def merge_sort(lists):
    '''
    遞迴進行歸併排序。
    '''
    # 遞迴結束條件
    if len(lists) <= 1:
        return lists
    
    # 分治進行遞迴
    middle = len(lists)//2
    left = merge_sort(lists[:middle])
    right = merge_sort(lists[middle:])
    
    # 將兩個有序陣列進行合併
    result = []
    i = j = 0
    while i < len(left) and j < len(right):
        # 將較小值放入到result中
        if left[i] < right[j]:
            result.append(left[i])
            i += 1
        else:
            result.append(right[j])
            j += 1
    
    # 將未被掃描到的直接追加到result後面
    if i == len(left):
        result.extend(right[j:])
    else:
        result.extend(left[i:])
    
    return result
    
if __name__ == '__main__':
    a = [2, 6, 10, 3, 5, 8, 4]
    print(merge_sort(a))