1. 程式人生 > >分治法:歸併排序

分治法:歸併排序

歸併排序的思想:

對集合的排序,可以看成他們子集合的分別排序,然後把各個有序的子集合合併成有序的全集,這裡一般把原集合儘可能的均分為2個子集合。

可以看出這裡有兩個工作量,一是把原陣列不斷的對半切,直到子問題足夠小可以直接解出,二是把兩個有序的數組合併成一個有序的陣列。

def MergeSort(arr,N):
    # 遞迴出口,出口需要返回當前的那個數
    if N == 1:
        return arr
    
    # 以下當作某一層的處理
    middle = N //2
    # 獲取待排序的兩個已排序的陣列
    left_list =
MergeSort(arr[:middle],len(arr[:middle])) right_list = MergeSort(arr[middle:],len(arr[middle:])) # 如下是針對兩個已排序的數組合併成一個有序陣列的排列方法,為最基本的雙針模型 i ,j =0,0 result =[] while i < len(left_list) and j < len(right_list): if left_list[i] <= right_list[j]: result.
append(left_list[i]) i +=1 else: result.append(right_list[j]) j += 1 result += left_list[i:] result += right_list[j:] # 返回已排序的結果,用於上一層獲取待排序的有序陣列 return result

我們想一下可以發現,對半切這個操作可以省略掉,對半切是為了切成大小為1的肉丁,但是陣列本來就是已經切好了的,那我們一個直接開始合併的操作,兩兩合併,四四合並,…

def MergeSort_iteration(arr):
    
    # 把一個數組arr[left:mid+1]和arr[mid+1:right+1]排成一個有序的序列,最後結果還是在
    # arr裡
    def sorting_two_sorted_arr_in_place(arr,left,mid,right):
        left_list = arr[left:mid+1]
        right_list = arr[mid+1:right+1]

        i ,j =0,0
        result =[]
        while i < len(left_list) and j < len(right_list):
            if left_list[i] <= right_list[j]:
                result.append(left_list[i])
                i +=1
            else:
                result.append(right_list[j])
                j += 1
        
        result += left_list[i:]
        result += right_list[j:]       
        
        arr[left:right+1] = result[:]
    
    # 雙針模型自然合併排序
    # 最外面的大的指標,1,2,4,8,16,32...,直到大於len(arr)    
    cur_size = 1
    # 終止條件為超過了陣列長度,前半部分為2的i次方,後半部分為2的i次方到end
    while cur_size < len(arr):
        # 內部迴圈,用於更新每一塊的順序,只要一個left指標就可以更新每一個區域性
        left = 0
        # 截至條件同樣是left越界
        while left < len(arr)-1:
            # mid的位置,-1為陣列是從0開始,簡單分析一個例項就明白了
            mid = left + cur_size -1
            # right的位置,一般情況下是mid + cur_size,同樣不能越界,越界時區len(arr)-1
            right = (mid + cur_size,len(arr)-1)[mid + cur_size>len(arr)-1]
            # 把制定區域的數排列有序
            sorting_two_sorted_arr_in_place(arr,left,mid,right)
            
            # 更新下一塊,每一個固定cur_size迴圈裡面的步長為2倍cur_size
            left += 2*cur_size
        # 更新外部迴圈的步長
        cur_size *=2
        
    return arr

執行結果

arr = [7,3,66,33,22,66,99,0,1]
print(arr)
print(MergeSort(arr,len(arr)))
print(MergeSort_iteration(arr))

[7, 3, 66, 33, 22, 66, 99, 0, 1]
[0, 1, 3, 7, 22, 33, 66, 66, 99]
[0, 1, 3, 7, 22, 33, 66, 66, 99]

我們還直到歸併排序的時間複雜度為O(nlogn),而且還是穩定的,排序問題的時間下界就是nlogn,歸併是一個漸近最優演算法,為什麼歸併排序是一個漸近最優演算法?這涉及到排序的本質,排序的本質是消除逆序對,假如逆序對沒有了,整個陣列就已經有序了,歸併排序先消除區域性的逆序對,再消除全域性的逆序對,這樣可以減少不必要的操作。為什麼歸併是一個穩定的演算法,歸併排序先消除區域性的逆序對,再消除全域性的逆序對,這樣不會理論上造成不穩定。