分治法的思想
分治法的思想就是将原有的问题分解成几个规模较小的类似于原问题的子问题,通过递归的求解子问题,然后合并子问题的解来求取原问题的解。
分治法主要包含三个步骤:
- 分解:将原问题分解为若干个子问题,这些子问题的规模小于原问题。
- 解决:递归的求解子问题。若子问题的规模足够小时,则直接求解。
- 合并:合并子问题的解,求解原问题。
归并排序
归并排序完全遵循分治法的思想。具体体现如下:
- 分解:分解待排序的n个元素为n/2个元素的两个子序列。
- 解决:使用归并排序递归的对两个子序列排序。当子序列元素个数为1时,终止递归。
- 合并:合并两个已排序的子序列,完成排序过程。
归并排序的分析
为了便于分析,将归并排序的输入规模设为2的幂。归并排序一个元素时需要常量时间。当n>1时,可以按照分治法的三个步骤分解运行时间。
- 分解:在这个步骤中只用计算数组的中间位置,需要常量时间。
- 解决:该步骤中需要递归的求解两个子问题,运行时间为 2T(n/2) 。
- 合并:合并一个具有n个元素的子数组需要 Θ(n) 的运行时间。
因此有:
T(n)=Θ(1),n=1
T(n)=2T(n/2)+Θ(n),n>1
对于n>1时,对 T(n) 采用树形结构对其分解如下图所示:
总时间为: T(n)=cnlgn+Θ(n)
归并排序的实现
#include <stdio.h>
#include <stdlib.h>
void merge(int *A, int l, int m, int r)
{
int *tmp, i, j, k, len;
len = r - l + 1;
tmp = (int *)malloc(sizeof(int) * len);
if (tmp == NULL) {
fprintf(stderr, "out of memory\n");
exit(1);
}
i = l, j = m + 1;
k = 0;
while (i <= m && j <= r) {
if (A[i] <= A[j]) {
tmp[k++] = A[i++];
} else {
tmp[k++] = A[j++];
}
}
while (i <= m) {
tmp[k++] = A[i++];
}
while (j <= r) {
tmp[k++] = A[j++];
}
for (i = 0; i < len; i++) {
A[l + i] = tmp[i];
}
free(tmp);
}
void msort(int *A, int l, int r)
{
int m;
if (l < r) {
m = (l + r) / 2;
msort(A, l, m);
msort(A, m + 1, r);
merge(A, l, m, r);
}
}