一、分治模式
许多有用的算法在结构上是递归的:为了解决一个给定的问题,算法一次或多次递归地调用其自身以解决紧密相关的若干子问题。这些算法典型地遵循分治法的思想:将原问题分解为几个规模较小但类似于原问题的子问题。递归的求解这些问题,然后再合并这些子问题的解来建立原问题的解。
分治模式在每层递归都有三个步骤:
分解原问题为若干子问题,这些子问题是原问题的规模较小的实例。
解决这些子问题,递归地求解各子问题。然而,子问题的规模足够小,则直接求解。
合并这些子问题的解成原问题的解。
归并排序算法完全遵循分治模式。
分解:分解待排序的n各元素的序列各成n/2个元素的两个子列。
解决:使用归并排序递归地排序两个子序列。
合并:合并两个已排序的子列以产生已排序的答案。
归并算法中合并过程的伪代码如下:
MERGE(A,p,q,r)
1 n1 = q - p + 1
2 n2 = r - q
3 let L[1..n1 + 1] and R[1..n2 + 1] be new arrays
4 for i = 1 to n1
5 L[i] = A[p + i -1]
6 for j = 1 to n2
7 R[j] = A[q + j]
8 L[n1 + 1] = ∞
9 R[n2 + 1] = ∞
10 i = 1
11 j = 1
12 for k = p to r
13 if L[i] <= R[j]
14 A[k] = L[i]
15 i = i + 1
16 else
17 A[k] = R[j]
18 j = j + 1
第1行计算子数组A[p..q]的长度你n1。 第2行计算子数组A[q+1..r]的长度n2。 在第3行,我们创建长度分别为n1+1,n2+1的数组L和R,每个数组中额外的位置保存哨兵。 第4~5行的for循环将子数组A[p..q]复制到L[1..n1]。 第6~7行的for循环将子数组A[q+1..r]复制到R[1..n2]。 第8~9行将哨兵放在数组L和R的末尾。 第10~18行在下图中,通过维持以下循环不变式,执行r-p+1个基本操作。
在开始第12~17行for循环的每次迭代时,子数组A[p..k-1]按从小到大的顺序包含L[1..n1+1]和R[1..n2+1]中的k-p个最小元素。进而L[i]和R[j]是各自所在数组中未被复制回数组A的最小元素。
归并排序证明循环不变式成立
初始化:循环第一次迭代之前,有k=p,所以子数组A[p..k-1]为空。这个空的子数组包含L和R的k-p = 0个最小元素。又因为i=j=1,所以L[i]和R[j]都是各自所在数组中未被复制回数组A的最小元素。
保持:为了理解每次迭代都维持循环不变式,首先假设L[i]<=R[j]。这时,L[i]是未被复制回数组A的最小元素,子数组A[p..k]将包含k-p+1个最小元素。增加k的值和i的值后,为下次迭代重新建立了该循环不变式。反之,若L[i]>=R[j],则第17~18行执行适当的操作来维持该循环不变式。
终止:终止时k = r +1。根据循环不变式,子数组A[p..k-1]就是A[p..r]且按从小到大的顺序包含L[1...n1+1]和R[1..n2+1]中的k-p = r - p +1个最小元素。数组L和R一起包含n1+n2+2 = r-p+3个元素。除两个最大的元素以外,其他所有元素都已被复制回数组A,这两个最大的元素就是哨兵。
归并排序伪代码
1 MERGE-SROT(A,p,r)
2 if p < r
3 q = (p + r)/2
4 MERGE-SORT(A,p,q)
5 MERGE-SORT(A,q + 1,r)
6 MERGE(A,p,q,r)
归并排序实例图
对示例A = <5,2,4,7,1,3,2,6>进行归并排序
归并排序的时间复杂度为O(nlgn)。
因为递归树是lgn + 1层,所以总的代价为cnlgn + cn。
二、归并排序的具体实现过程
#include <stdio.h>
#include <stdlib.h>
void merge(int intArray[], int begin, int mid, int end)
{
int n1 = mid - begin + 1;
int n2 = end - mid;
int i, j, k;
int *L = (int *)malloc(sizeof(int) * n1);
int *R = (int *)malloc(sizeof(int) * n2);
/*利用goto语句实现异常处理
当L或R未能正常分配时
将直接跳转至程序末尾,后续程序不再执行
*/
if (L == NULL || R == NULL)
{
goto error;
}
for (i = 0; i < n1; i++)
L[i] = intArray[begin + i];
for (j = 0; j < n2; j++)
R[j] = intArray[mid + 1 + j];
i = j = 0;
k = begin;
while (i < n1 && j < n2)
{
if (L[i] < R[j])
{
intArray[k++] = L[i++];
}
else
{
intArray[k++] = R[j++];
}
}
while (i < n1)
{
intArray[k++] = L[i++];
}
while (j < n2)
{
intArray[k++] = R[j++];
}
/*
程序执行完毕后,在done处,进行资源释放
*/
goto done;
error:
printf("malloc has benn failed!\n");
done:
if (L != NULL && R != NULL)
{
free(L);
free(R);
}
}
void merge_sort(int intArray[], int head, int tail)
{
int mid;
if (head < tail)
{
mid = (head + tail) / 2;
merge_sort(intArray, head, mid);
merge_sort(intArray, mid + 1, tail);
merge(intArray, head, mid, tail);
}
}
int main(void)
{
int a[8] = { 5, 2, 4, 7, 1, 3, 8, 6 };
int i = 0;
merge_sort(a, 0, 7);
for (i = 0; i < 8; i++)
{
printf("%d ", a[i]);
}
while (1);
}