概念
归并排序是属于分治算法。
许多有用的算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决机密相关的若干子
问题。这些算法典型地遵循分治法的思想:将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些子问题,
然后再合并这些子问题的解来建立原问题的解。
分治模式在递归时都要三个步骤:
分解原问题为若子问题,这些子问题是原问题的规模较小的实例。
解决这些子问题,递归地求解各个子问题,然而,若子问题的规模足够小,则直接求解。
合并这些子问题的解成原问题的解。
归并排序算法完全遵循分治模式。直观上其操作如下:
分解:分解待排序的 n 个元素的序列成各具 n/2 元素的两个子序列。
解决:使用归并排序递归地排序两个子序列。
合并:合并两个已排序的子序列以产生已排序的答案。
如我们通过调用一个辅助过程MERGE(A, p, q, r) 来完成合并,其中A是一个数组,p、q 和 r 是数组下标,满足p<= q < r.
该过程假设子数组A[p…q] 和 A[q+1…r]都已经排好序。它合并这两个子数组形成单一的已经排好序的子数组并代替当前的子数组A[p…r]。
MERGE例子:
有两副已经排好序(从上到下,从小到大)的扑克牌,牌面朝上,现在要将这两副牌合并为一副排好序的牌。
两副牌(即两个输入堆)最上面的两张牌比较,拿较小的出来一张另外放牌背朝上,当做第3副牌(即输出堆),那么拿出较小一张的那一副牌就会露出下一张,
重复此步骤,直到输入堆为空。
伪代码:
使用哨兵
MERGE(A, p, q, r)
n1 = q - p + 1
n2 = r - q
let L[1..n1+1] and R[1..n2+1] be new arrays
for i=1 to n1
L[i] = A[p+i-1]
for j=1 to n2
R[j] = A[q+j]
L[n1+1] = ∞
R[n2+1] = ∞
i = 1
j = 1
for k=p to r
if L[i] <= R[j]
A[k] = L[i]
i = i + 1
else
A[k] = R[j]
j = j + 1
不要哨兵
MERGE(A, p, q, r)
n1 = q - p + 1
n2 = r - q
let L[1..n1+1] and R[1..n2+1] be new arrays
for i=1 to n1
L[i] = A[p+i-1]
for j=1 to n2
R[j] = A[q+j]
i = 1
j = 1
for k=p to r
if i<=n1 and j <=n2
if L[i] <= R[j]
A[k] = L[i]
i = i + 1
else
A[k] = R[j]
j = j + 1
else if i>n1 and j<=n2
A[k] = R[j]
j = j + 1
else if i<=n1 and j>n2
A[k] = L[i]
i = i + 1
MERGE过程图:
阴影(即颜色比较重的)的表示已经读取的数据:
MERGE_SORT总过程
MERGE-SORT(A, p, r)
if p < r
q = [(p + r)/2]
MERGE-SORT(A, p, q)
MERGE-SORT(A, q + 1, r)
MERGE(A, p, q, r)
总过程图
算法分析
分析分治算法
当一个算法包含其自身的递归调用时,我们往往可以用递归方程或递归式来描述其
运行时间,该方程根据在较小输入上的运行时间来描述在规模为 n 的问题上的
总运行时间。
假设 T(n) 是规模为 n 的一个问题的运行时间。若问题规模足够小,如对某个
常量 c, n≤c , 则直接求解,需要常量时间,我们将其写作 Θ(1) .
假设把问题分解成 a 个子问题,每个子问题的规模是原问题的 1/b .(对于归并排序,
a 和 b 都为 2,然而,我们将看到在许多分治算法中, a≠b )为了求解一个规模为
n/b 的子问题,需要T(n/b)的时间, 所以需要 aT(n/b) 的时间来求解 a 个子问题。
如果分解问题需要时间D(n), 合并子问题的解成原问题的解需要时间C(n),那么得
递归式: