1 什么叫归并排序?
什么叫归并排序呢?这也是我们排序算法中目前最后一个,后面可能还会有补充!
- 归并排序是采用分治法的一个非常典型的应用。
- 归并排序的思想就是先递归分解数组,再合并数组。
- 将数组分解最小之后,然后合并两个有序数组。
- 基本思路是比较两个数组的最前面的数,谁小就先取谁,取了后相应的指针就往后移一位。然后再比较,直至一个数组为空,最后把另一个数组的剩余部分复制过来即可。
归并排序就是两个大的步骤:
- 首先进行拆分,直至分到只有1个元素为止。
- 然后进行合并。两两有序合并,直至全部合并ok!
是不是看着相当easy?但讲道理,代码实现起来非常的difficult!所以今天小编给大家带来的代码实现部分还加入了新的环节,就是代码的拆解,看看究竟代码是如何一步一步进行的!
2 代码实现
2.1 思路
- 首先将序列进行一分为二 然后再对半分 一直到分成1个元素为止!
- 然后一左一右设置两个游标left right
- 如果比较left和right对应的值 如果left小 将元素拿过来 然后游标右移 继续比较和right 如果right小把right拿过来 直至某一个list里全为空 此时再把剩下一个copy过来
- 注意上述过程是从最小单位开始进行 逐个往上!
- 肯定要用到递归的思路,而且传参进去应该也不止一个参数 还应该有始末位置![不一定]
2.2 代码
alist = [54,26,93,17,77,31,44,55,20]
sorted_alist = mergeSort(alist)
print(sorted_alist)
9 // 2
4
def merge_sort(alist):
'''归并排序'''
# 先拆分 - 算法复杂度为常数
n = len(alist)
if n <= 1: # 只有一个元素 返回自身
return alist
mid = n // 2
left_li = merge_sort(alist[:mid]) # 前面一部分
right_li = merge_sort(alist[mid:]) # 后面一部分
# merge(left_li, right_li) # 将两个子序列合并为一个新的整体
'''
问题:上面两个调用 需要返回值吗?
区别于快排,这是传入了列表,而不是给定位置了
left_li:采用归并排序后新的list
right_li:采用归并排序后新的list
然后合并两部分
'''
# 然后排序 - 算法复杂度为:n × 对数
left_pointer, right_pointer = 0, 0
result = []
n1 = len(left_li)-1
n2 = len(right_li)-1
while left_pointer <= n1 and right_pointer <= n2:
if left_li[left_pointer] <= right_li[right_pointer]: # 加上等号保证稳定
result.append(left_li[left_pointer])
left_pointer += 1
else:
result.append(right_li[right_pointer])
right_pointer += 1
# 有一种情况是 如果任一为0了 直接把剩下的加到result
# if left_pointer > n1:
# result += right_li
# elif right_pointer > n2:
# result += left_li
# 上面这种情况复杂了 不管三七二十一 直接加上去就好!
result += left_li[left_pointer:]
result += right_li[right_pointer:]
return result
alist = [54,26,93,17,77,31,44,55,20]
print(alist)
sorted_alist = merge_sort(alist)
print(alist) # 原先列表不会改变 返回有序的列表
print(sorted_alist)
[54, 26, 93, 17, 77, 31, 44, 55, 20]
[54, 26, 93, 17, 77, 31, 44, 55, 20]
[17, 20, 26, 31, 44, 54, 55, 77, 93]
2.3 代码拆解
上面递归看着还是比较生涩的,下面通过一步一步的拆解,来看上述过程是如何进行的!
right_li = merge_sort([77,31,44,55,20])
left_li = merge_sort([77,31])
left_li = merge_sort([77])
return [77]
right_li = merge_sort([77,31,44,55,20])
left_li = merge_sort([77,31])
left_li = [77]
right_li = merge_sort([31])
return [31]
right_li = merge_sort([77,31,44,55,20])
left_li = merge_sort([77,31])
left_li = [77]
right_li = [31]
right_li = merge_sort([77,31,44,55,20])
left_li = merge_sort([77,31])
left_li = [77]
right_li = [31]
return result = [31, 77]
right_li = merge_sort([77,31,44,55,20])
left_li = [31, 77]
right_li = merge_sort([44,55,20])
left_li = merge_sort([44])
return [44]
right_li = merge_sort([77,31,44,55,20])
left_li = [31, 77]
right_li = merge_sort([44,55,20])
left_li = [44]
right_li = merge_sort([55,20])
left_li = merge_sort([55])
return [55]
right_li = merge_sort([20])
return [20]
return result = [20,55]
right_li = merge_sort([77,31,44,55,20])
left_li = [31, 77]
right_li = merge_sort([44,55,20])
left_li = [44]
right_li = [20,55]
return result = [20,44,55]
right_li = merge_sort([77,31,44,55,20])
left_li = [31, 77]
right_li = [20,44,55]
right_li = merge_sort([77,31,44,55,20])
left_li = [31, 77]
right_li = [20,44,55]
return result = [20,31,44,55,77]
right_li = [20,31,44,55,77] # 大功告成!
上面就是右边序列一步一步拆解的过程!结束!
3 算法时间复杂度
- 最优/最差算法复杂度为: O ( n l o g n ) O(nlogn) O(nlogn) 先两两比较 线性复杂度 然后一分为二 对数 故两者相乘!最优和最差都是这个,因为均是一分为二,特不特殊情况无所谓的。
- 空间复杂度是比其余大的,因为新生成了一个list,其余的就是在原来上面修改的!所以需要占用更多的空间!
4 算法稳定性
稳定!比如下图:
由于代码:
if left_li[left_pointer] <= right_li[right_pointer]: # 加上等号保证稳定
所以左边的54首先插入result,然后再是右边的54,所以算法是稳定的!