1.归并排序原理:将一个序列从中间分成两个子序列,一直分下去,直到子序列中只有一个元素时,停止分裂,开始进行合并。合并时,从只有一个元素的子序列排序后,合并回分裂前的父序列结构,然后再同逻辑合并,直至合并到原序列的结构。其中排序操作需要用到两个下标变量来对比元素,还需要一个新的空序列来接收排序后的序列。原理图如下。
重点是,要先分裂到子序列只有一个元素,才开始合并,所以分裂和合并是分开进行的,分裂为同样的逻辑,且逻辑简单,所以我们可以用递归来进行分裂,用两个变量来接收,然后在下面在进行排序合并操作。
2.分裂步骤:先取出序列长度,再找出序列中间元素的下标(mid),然后用 left(中间左边的子序列) 和 right(中间元素右边的子序列) 来接收递归的返回序列。当子序列元素个数为一时则递归返回。
def merge_sort(list1):
n = len(list1)
'''递归返回条件,当子列表长度等于1,就可以返回,开始比较 排序 合并'''
if n <= 1:
return list1
mid = n // 2
left = merge_sort(list1[:mid]) #递归
right = merge_sort(list1[mid:]) #递归
2.合并步骤: 返回子序列后,就在下面对子序列进行排序和合并。
L_p,R_p = 0,0
'''两个变量用来记录子列表的下标'''
new_list = []
'''新列表用来接收排序后的列表'''
while (R_p <= len(right)-1) & (L_p <= len(left)-1): #错误案例 R_p <= (len(right)-1) & L_p <= (len(left)-1)
'''任意一个下标超出了子列表的长度,就停止循环'''
if right[R_p] < left[L_p]:
new_list.append(right[R_p])
R_p += 1
else:
new_list.append(left[L_p])
L_p += 1
new_list += left[L_p:]
new_list += right[R_p:]
'''递归结束后返回新列表'''
return new_list
L_p和R_p为两个下标变量,分别代表着左边子序列和右边子序列的下标,new_list就为新建序列,来接收排序后的列表。排序的逻辑,比如说我们有两个子序列(L和R,假设各自三个元素),L第一个元素和R第一个元素比较,如果L[L_p]小就把L[L_p]尾插进( append() )新序列,然后让L_p后移一位,否则就把R[R_p]尾插进新序列。就这样依次比较下去,直到有一个子序列的下标变量后移出了子序列的范围,就停止排序的循环,然后把另一个序列剩下的元素直接放到新序列的后面。最后的递归出口即 return new_list。返回排序后的序列
调试代码:
import random
a = [random.randint(1,100) for i in range (10)]
print(a)
b = merge_sort(a)
print(b)
返回值:
F:\测试>python -u "f:\测试\数据结构和算法\归并排序.py"
[29, 7, 70, 42, 90, 3, 87, 8, 30, 5]
[3, 5, 7, 8, 29, 30, 42, 70, 87, 90]
3.时间复杂度: O(n*logn),其中分裂的步骤复杂度为O(1),而排序合并的步骤复杂度为O(logn), 递归返回的复杂度为O(n), 而排序合并O(logn)被包含在递归返回后的代码内,所以两者为相乘关系,则复杂度为O(n*logn)。可见归并排序时间复杂度较低,但是我们创建了一个新序列来接收排序后的序列,所以空间复杂度相较于在原序列上修改的排序方法更大了。