归并排序(Merge Sort)

归并排序(Merge Sort)

平均时间复杂度: O ( n l o g n ) O( n log n) O(nlogn)

空间复杂度: O ( n ) O(n) O(n)

稳定性:稳定

基本形式:

归并排序是建立在归并操作上的一种有效的排序算法,将已有序的子序列合并,得到完全有序的序列,是分治算法的典型应用。常用的将两个有序序列合并成一个有序序列的算法称为二路归并。

如何使子序列有序?

要将有序的子序列进行合并,首先要得到有序的子序列。这就是分治算法的“分”的过程。先将完全序列中的每一个数都分为一个子序列,也就是每一个子序列中只有一个数,本身即是有序,就可以开始合并操作了。这是从上到下的过程,就形成了一棵树。

如何合并?

这就是分治算法中的“治”。对于两个有序的子序列,我们将其合并即可,这样就需要额外的存储空间。这个 O ( n ) O(n) O(n)的额外存储空间可以事先申请好,也可以使用的时候用多少申请多少。

为什么归并排序在最好情况和最坏情况下的时间复杂度都是 O ( n l o g n ) O(n log n) O(nlogn)?

因为归并排序,先要“分”,这个过程就构成一棵树,以二路归并为例,n个数的序列要分成n个有序子序列要分成 l o g 2 n log_2n log2n层(树的深度),在”治“的过程中,每一层的平均时间复杂度为 O ( n ) O(n) O(n),故整体的平均时间复杂度为 n l o g ( n ) n log(n) nlog(n)。不管原始序列是否有序,故按照这个步骤进行操作,时间复杂度都是一样的。

实现

递归
    def merge_sort(array):
        if len(array) <= 1: return array
        middle = len(array)//2
        array1 = merge_sort(array[:middle])
        array2 = merge_sort(array[middle:])
        result = []
        i,j = 0,0
        while(i < len(array1) and j < len(array2)):
            #注意这里是小于等于,保证稳定性
            if array1[i] <= array2[j]:
                result.append(array1[i])
                i += 1
            else:
                result.append(array2[j])
                j += 1
        result += array1[i:] + array2[j:]
        return result
迭代

迭代的思想也是分组排序,然后合并, 我们用一个窗口,窗口的长度从2到 ( l e n ( a r r a y ) ) + 1 / / 2 (len(array))+1//2 (len(array))+1//2, 在每个窗口内都需要额外的空间辅助排序。代码总体分为两个部分,merge 函数用来合并有序数组成新的更长的有序数组,主函数用来用窗口扫描数组然后调用merge函数合并数组。

    def merge_sort(array):
        def merge(array, low, middle, high):
            # 用middle 指针将low和high的数组分成两个数组 left array[low: middle] right array[middle: high]
            low_array = array[low: middle]
            high_array = array[middle:high+1]
            low_idx, high_idx, array_idx = 0, 0, low
    
            while(low_idx < len(low_array) and high_idx < len(high_array)):
                if low_array[low_idx] <= high_array[high_idx]:
                    array[array_idx] = low_array[low_idx]
                    low_idx += 1
                else:
                    array[array_idx] = high_array[high_idx]
                    high_idx += 1
                array_idx += 1
            array[array_idx:high+1] =  low_array[low_idx:] + high_array[high_idx:]
    
        step  = 1
        while step < (len(array)):
            low = 0
            while(low < len(array)):
                middle = low + step
                high =  min(low + step * 2-1, len(array)-1)
                if middle <= high:
                    merge(array, low, middle, high)
                low += step*2
            step *= 2
        return array

这里一定要用middle指针,不能用low 和 high除以2,因为在边界的时候可能不能均分成两个数组(比如长度6,分成了左四右二的有序数组)

改进
  1. 如果子数组较小(7左右),则采用速度更快的插入排序。这样做,会带来一定的好处,例如归并排序减少分配、回收临时存储区域的频次,快速排序减少递归层次等。
  2. 检测待归并的两个子数组是否已经有序,如果已经有序则直接复制该子数组;
  3. 特殊情况的处理,比如当前的两个子序列中,左边的最大值小于右边的最小值,可以直接返回;或者嘴边的最小值大于右边的最大值,左右互换即可。

你可能感兴趣的:(算法与数据结构)