分治策略
对于所求的问题域,每次将其分为大小相似的两部分,然后再把每一部分当作最初的问题域再次分割,依次递归,直到得到基本问题(递归基),求解。然后,再逐步合并直到完成最初的问题域。
通常用递归的方式分割问题,例如这里的归并排序,每次将数组一分为二,然后分别判断分割动作是否到不可再分割了(数组中仅剩最后一个元素),否则继续对左右两个子数组进行分割:
void merge_sort(int arr[], int lo, int hi) {
if (lo + 1 >= hi) {
return;
}
int mi = (hi + lo) / 2;
merge_sort(arr, lo, mi);
merge_sort(arr, mi, hi);
merge(arr, lo, mi, hi);
}
归并排序的合并操作
每次要合并两个子数组时,同时判断两个数组的开头位置的元素大小,取小的元素,直到有一个数组为空时,把非空数组的所有元素复制过来:
void merge(int arr[], int lo, int mi, int hi) {
if (lo + 1 == hi) {
return;
}
// 原数组 lo 至 hi 之间的元素已经有序,分别复制出来到两个新数组
int llen, rlen;
int i, j, k;
llen = mi - lo;
rlen = hi - mi;
int larr[llen];
int rarr[rlen];
for (i = 0; i < llen; i++) {
larr[i] = arr[lo + i];
}
for (i = 0; i < rlen; i++) {
rarr[i] = arr[mi + i];
}
// 依次判断两个新数组的开头元素,把小的放到原数组
i = 0;
j = 0;
k = lo;
while(i < llen && j < rlen) {
if (larr[i] <= rarr[j]) {
arr[k++] = larr[i++];
} else {
arr[k++] = rarr[j++];
}
}
// 新数组有一个为空时,把另一个完整的复制到原数组
while(i < llen) {
arr[k++] = larr[i++];
}
while(j < rlen) {
arr[k++] = rarr[j++];
}
}
完整示例代码
#include <stdio.h>
void merge(int arr[], int lo, int mi, int hi) {
if (lo + 1 == hi) {
return;
}
int llen, rlen;
int i, j, k;
llen = mi - lo;
rlen = hi - mi;
int larr[llen];
int rarr[rlen];
for (i = 0; i < llen; i++) {
larr[i] = arr[lo + i];
}
for (i = 0; i < rlen; i++) {
rarr[i] = arr[mi + i];
}
i = 0;
j = 0;
k = lo;
while(i < llen && j < rlen) {
if (larr[i] <= rarr[j]) {
arr[k++] = larr[i++];
} else {
arr[k++] = rarr[j++];
}
}
while(i < llen) {
arr[k++] = larr[i++];
}
while(j < rlen) {
arr[k++] = rarr[j++];
}
}
void merge_sort(int arr[], int lo, int hi) {
if (lo + 1 >= hi) {
return;
}
int mi = (hi + lo) / 2;
merge_sort(arr, lo, mi);
merge_sort(arr, mi, hi);
merge(arr, lo, mi, hi);
}
int main(void) {
int arr[] = {10, 9, 8, 7, 6, 5, 4, 3, 2, 1};
int n = 10;
int i;
for (i = 0; i < n; i++) {
printf("%4d", arr[i]);
}
printf("\n");
merge_sort(arr, 0, n);
for (i = 0; i < n; i++) {
printf("%4d", arr[i]);
}
return 0;
}