分治法:归并排序

归并排序的思想:

对集合的排序,可以看成他们子集合的分别排序,然后把各个有序的子集合合并成有序的全集,这里一般把原集合尽可能的均分为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,归并是一个渐近最优算法,为什么归并排序是一个渐近最优算法?这涉及到排序的本质,排序的本质是消除逆序对,假如逆序对没有了,整个数组就已经有序了,归并排序先消除局部的逆序对,再消除全局的逆序对,这样可以减少不必要的操作。为什么归并是一个稳定的算法,归并排序先消除局部的逆序对,再消除全局的逆序对,这样不会理论上造成不稳定。

猜你喜欢

转载自blog.csdn.net/weixin_40759186/article/details/84958025