介绍
归并排序是非常经典的排序算法之一,相比于冒泡排序、插入排序、选择排序等方法,在时间复杂度上具有很大的优势,适用于大规模的数据排序。归并排序算法很好的利用了分治策略的思想,将一个问题分为若干个子问题,再把子问题看做一个问题继续划分,直至问题的规模足够小,此时解决该子问题的时间复杂度为常数。分治策略中,我们递归的求解一个问题,每次递归的的步骤为:
- 分解:将问题划分为若干子问题,子问题的形式与原问题相同,但规模更小。
- 求解:递归的求解子问题。若子问题规模足够小,则直接求解。
- 合并:将每个子问题的结果合并为整个问题的解。
对于归并排序算法,完全适用于以上的求解步骤。对于规模为n的数组,每次递归地分为原来的1/2,直至划分的子数组规模为1,则直接返回子数组本身,再将求解出的子问题合并,即将两个有序数组合并成一个有序数组。
代码
def MergeSort(arr: List[int], p: int, r: int):
# 将一个数组的左右两个有序子数组合并成一个有序数组
def Merge(arr: List[int], p: int, q: int, r: int):
arr1 = arr[p:q + 1]
arr2 = arr[q + 1:r + 1]
p1, p2 = 0, 0
for i in range(p, r + 1):
if arr1[p1] < arr2[p2]:
arr[i] = arr1[p1]
p1 += 1
else:
arr[i] = arr2[p2]
p2 += 1
# 判断是否其中一个子数组被完全复制到了原数组中,若是,则将另一个子数组按顺序直接复制到原数组中
if p1 == q - p + 1:
arr[i + 1:r + 1] = arr2[p2:]
break
elif p2 == r - q:
arr[i + 1:r + 1] = arr1[p1:]
break
# 当p>=r时,子数组中仅有一个元素,不进行操作,即子问题划分得足够小,直接求解。
if p < r:
q = (p + r) // 2
Merge_sort(arr, p, q)
Merge_sort(arr, q + 1, r)
Merge(arr, p, q, r)
对于规模为n的数组,则初始得调用代码为:
MergeSort(arr,0,n-1)
时间复杂度
从MergeSort函数可以看出,里面递归调用了两次MergeSort,传入数组的规模是原来的一半,即左右两个部分,进行了分解和求解两个步骤,然后调用了Merge函数,在最坏的情况下,Merge函数的时间复杂度为O(n),则在最坏的情况下,整体算法的时间复杂度可以用递归式表示为:
当数组规模为1时,复杂度为常数,当规模大于1,则表示为分解的两个子问题的时间复杂度和合并所用时间复杂度的和。求解该递归公式,最终算法所需要的时间代价为:cnlgn+cn,其中c为常系数。则归并排序的时间复杂度为:
具体的求解步骤请参考书籍 算法导论 第一部分2.3.2节。
该文章部分引用于算法导论一书