前言
归并排序(Merge Sort)较早为通用存储程序计算机设计的算法之一。它由冯·诺依曼(John von Neumann)在 1945 年发表的“101 报告”时提出,后于 1951 年完成的 EDVAC 计算机上应用了这一算法。
目录
基本思想
将一个数组不断地分成两半,分别对这两半进行排序,然后将排序好的两半合并起来。
复杂度
一种稳定的排序算法,即相同元素的相对顺序在排序前后不会改变。
时间复杂度 O(nlogn)
递归方式 空间复杂度 O(n + logn)
非递归方式 空间复杂度 O(n)
一般步骤
- 分解:将待排序的数组不断地“一分为二”,直到每个子数组只包含一个元素。
- 递归排序与合并:在分解过程完成后,递归地对每个子数组进行排序,并将两个相邻的已排序子数组合并成一个有序的新数组。合并时,通过比较两个子数组的元素,按顺序放入新数组中。
- 结束条件:当所有子数组都合并完毕,最终得到的数组就是完全有序的。
应用场景
归并排序由于其稳定、高效的特点。
只要涉及到对大量数据进行排序且对稳定性有要求的场景,归并排序都可能是一个合适的选择。
- 大规模数据排序:当需要对大量数据进行排序时,归并排序的 时间复杂度能够保证较好的性能。
- 外部排序:当数据量太大而无法一次性放入内存时,归并排序常用于外部排序算法中。可以将数据分成多个较小的块,在内存中对每个块进行排序,然后逐步将这些已排序的块合并。
- 分布式系统:在分布式计算环境中,数据可能分布在不同的节点上。归并排序的思想可以用于合并来自不同节点的已排序数据段。
- 数据库操作:数据库在对大型数据表进行排序或合并操作时,可能会使用类似归并排序的策略。
- 排序算法比较和教学:由于归并排序具有清晰的分治思想和相对简单的实现逻辑,常被用于算法教学和不同排序算法的性能比较。
- 多路归并:在处理多个已排序的数据流(如文件)并将它们合并为一个有序数据流的情况下,归并排序的思想可以扩展为多路归并。
代码
递归方式
自上而下的归并排序 通常更直观,容易理解和实现,因为它利用了递归的思想。
通过递归的方式将数组不断地分成两半,直到子数组的大小为 1 ,然后再将这些子数组逐步合并成有序的数组。它从整个数组开始,逐步向下细分,然后再向上合并。
def merge_sort(arr):
if len(arr) <= 1:
return arr
mid = len(arr) // 2
left_half = merge_sort(arr[:mid])
right_half = merge_sort(arr[mid:])
return merge(left_half, right_half)
def merge(left, right):
result = []
left_index = 0
right_index = 0
while left_index < len(left) and right_index < len(right):
if left[left_index] < right[right_index]:
result.append(left[left_index])
left_index += 1
else:
result.append(right[right_index])
right_index += 1
result.extend(left[left_index:])
result.extend(right[right_index:])
return result
非递归方式
自下而上的归并排序 通常在迭代实现中更高效,特别是在一些情况下可以避免递归带来的额外开销(如函数调用的开销、栈空间的使用等)。
def merge(seq, low, mid, high):
""" 合并两个已排序好的子序列 """
global sort_times
left = seq[low:mid]
right = seq[mid:high]
k = 0 # left 的下标
j = 0 # right 的下标
result = [] # 保存合并后的结果
while k < len(left) and j < len(right):
if left[k] <= right[j]:
result.append(left[k])
k += 1
else:
result.append(right[j])
j += 1
result += left[k:]
result += right[j:]
seq[low:high] = result
def merge_sort(seq):
i = 1 # 子数组长度,从1开始
while i < len(seq):
low = 0
# print(f'------ start {i} ----')
while low < len(seq):
mid = low + i
high = min(low + 2 * i, len(seq)) # 防止超出数组长度
if mid < high:
merge(seq, low, mid, high)
low += 2 * i # 移动到下一组要合并的子数组
# print(f'------ end {i} ----')
i *= 2 # 子数组长度翻倍
运行样例
array_A = [5, 2, 4, 7, 1, 3, 4, 6]
array_B = merge_sort(array_A)
print(array_B)
# [1, 2, 3, 4, 4, 5, 6, 7]
真题
用归并算法对数组<3, 1, 4, 1, 5, 9, 6, 5>进行从小到大排序,则需要进行多少次数组元素之间的比较?(2011年上半年)
答案:14次