1.归并排序是什么
归并排序,英文叫merge-sort,从英文名可以容易地看出它的原理:通过merge若干组有序子序列来实现整体的排序
归并排序是分治思想的典型体现。通过将待排序列划分成多组子序列,这些子序列又继续往下各自划分成多组,直到序列长度为1,视为序列有序,可以开始merge;这时不断自下而上地merge这些子序列,直到待排序列的排序完成。若将两个有序序列合并成一个有序序列,称为二路归并
2. 归并核心 --- 序子 + 后并
使用归并排序一个无序序列,说穿了就是2步
序子后并
-
序子:让无序序列的子序列有序。如果子序列无序,继续利用
归并排序这个子序列(递归),直到子序列有序(递归出口:子序列长度为1即认为有序) - 并:merge有序子序列
归并的递归思路
先看一下递归实现的自顶向下形式。如图,不断地二分产生子序列,直到子序列长度为1,即到达“80”“30”等这些结点,再不断向上merge子序列
归并排序递归形式图示
归并的递推思路(自底向上)
那么同时,我们也可以倒过来,自底向上地归并。即叶子结点起,不断merge叶子结点,将多个叶子结点merge形成一新的叶子结点,一直到只剩一个结点为止。如图所示:
自底向上归并图示
如果在自底向上merge的过程中,在最后一组出现子序列不能配对成组的情况,那么先留着这个子序列不做任何处理,因为在后续的merge过程中,总能用到它的。我把这种处理方式叫做【不成组子序列的滞后merge】。如图:
3.归并排序性能分析
3.1 时间复杂度分析
通过递归实现归并排序的过程构成了一棵归并树(例如上面的归并排序递归形式图示),这一点类似快速排序的递归过程。因为在树的每一层总是要近似地进行O(n)次操作,而树的层数为 1 + log n,因此归并排序的时间复杂度为O(nlogn),最好最坏都是O(nlogn)
3.2 空间复杂度分析
自顶向下的递归实现和自底向上的实现所需要的空间复杂度不同,需要具体分析
例如,使用递归形式归并排序一个无序数组,每次递归都要拆分数组,拆分后的数组需要新的空间;另外每次合并数组也需要开辟新的空间,空间复杂度是比较大的
3.3 排序稳定性分析
归并排序是稳定的。因为在merge的时候,我们可以保证如果两个当前元素相等,就把处在前面的序列的元素保存在结果序列的前面,从而保证稳定性
4. 归并排序的实现
4.1 递归形式
递归代码非常好写,找到分段函数就行
class solution:
def mergeSort(self, l: list):
length = len(l)
# 写分段函数
if length == 1:
return l
else:
# 核心代码
mid = length / 2
# 序子
l1 = self.mergeSort(l[:mid])
l2 = self.mergeSort(l[mid:])
# 并
return self.merge(l1, l2)
def merge(self, l1, l2):
# 新开辟一个list,因此是非原地修改,会增加空间消耗
# 当然也可以选择往l1或者l2插入元素来实现原地修改的merge
result = list()
# 指针分别指向两个序列的首个元素
i = j = 0
length_l1 = len(l1)
length_l2 = len(l2)
# 当两个指针都在有效范围内的时候才merge,否则直接拼接
while i < length_l1 and j < length_l2:
if l1[i] <= l2[j]:
result.append(l1[i])
i += 1
else:
result.append(l2[j])
j += 1
if i == length_l1:
result += l2[j:]
if j == length_l2:
result += l1[i:]
return result
4.2 自底向上的归并实现
这篇博客写得很清楚了,不再赘述博客地址
相对递归写法而言,非递归写法要求对整个过程的各个步骤细节有清楚的认识,通常都是更难想更难写的
网友评论