归并排序是一种十分经典的冲破O(n2)时间复杂度的排序算法,该算法是基于分治的思想,讲解算法原理之前,先看一下下面这张图。
- 图片来源:【算法】排序算法之归并排序
上图就是归并算法的一个简单示例。该算法就是每次将数组递归拆解为两部分,直到所有部分包含元素为1,之后递归的将各个部分进行归并。 - 具体的算法步骤如下:
-
1:递归拆解数组,直到每个区块包含元素数量为1
2:归并数组,直到归并后的数组长度等于原数组长度
3: 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列
4:设定两个指针,最初位置分别为两个已经排序序列的起始位置
5:比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置
5:重复步骤 4直到某一指针达到序列尾
7:将另一序列剩下的所有元素直接复制到合并序列尾
8:重复步骤2-7
从上述可以看出,该算法的实现是基于递归思想,但是所有的递归都可以转换成迭代,因此该算法有两个实现方式,递归和迭代。
- 代码如下,以递归为例:
- 如果单从原理原理上来直接写代码会比较复杂,我们可以将该算法拆解为两个部分,及分割和归并。分割是一个简单的递归,代码如下:
def mergeSort(nums):
"""
将输入数组递归拆分,直到长度为1时返回,对每一步返回的left,right进行顺序归并
>>>mergeSort(3,38, 5, 44, 15, 36)
>>>[3, 5, 15, 36, 38, 44]
"""
if len(nums) < 2:
return nums
mid = len(nums)//2
left, right = nums[:mid], nums[mid:]
return mergeHelp(mergeSort(left), mergeSort(right))
- 其中mergeHelp是mergeSort的一个help方法,用于对拆分后的数组进行顺序归并。
- 以[3,38, 5, 44, 15, 36]举个栗子,说一下递归过程:
-
1:[3, 38, 5] [44, 15, 36]
2:[3] [38, 5]
3:[38] [5]
4:[44] [15, 36]
5:[15] [36] - 接下来我们实现mergeHelp方法,就是给定两个有序数组,将其按顺序合并为一个数组,代码如下:
def mergeHelp(left, right):
"""
归并排序的help方法,用于将拆分的left和right进行顺序归并
>>>mergeHelp([1, 3, 5], [2, 4, 5]):
>>>[1, 2, 3, 4, 5, 5]
"""
res = []
while left and right:
if left[0] <= right[0]:
res.append(left.pop(0))
else:
res.append(right.pop(0))
while left:
res.append(left.pop(0))
while right:
res.append(right.pop(0))
return res
- 当我们实现了这个方法之后,看一下分割+归并的过程
-
[3, 38, 5] [44, 15, 36] 第一次分割
左数组[3, 38, 5]部分:
[3] [38, 5] 第一次分割
[38] [5] 第二次分割(左数组分割完毕,所有部分元素均为1)
[5, 38] 第一次归并
[3, 5, 38]emsp; 第二次归并,完成[3, 38, 5]部分序列的排序
右数组[44, 15, 36]部分:
[44] [15, 36] 第一次分割
[15] [36] 第二次分割
[15, 36] 第一次合并
[15, 36, 44] 第二次合并
原数组的所有子序列完成排序,进行最终的合并
[3, 5, 15, 36, 38, 44] 排序完成 - 算法解析:算法的时间复杂度从两个部分来看,很明显在mergeSort()递归方法里面,迭代公式为mid = len(nums)//2,该方法的时间复杂度是O(log n),而在mergeHelp()归并方法里面,每一步像res里面追加一下元素,最终的的运行次数为len(left)+len(right),很明显时间复杂度为O(n),因此归并排序算法的总时间复杂度为O(n log n)。由于算法运行过程中,会递归生成原数组的切片,切片总长度为n,所以归并排序算法的空间复杂度为O(n)。
- 注意到我们在进行归并操作是,使用的是如下代码
while left and right:
if left[0] <= right[0]:
res.append(left.pop(0))
else:
res.append(right.pop(0))
- 也就是说对于同样大小的元素,我们先插入位于左边的,而后插入右边的,因此归并排序不会改变相同元素的相对位置,因此该算法是稳定的。