分治策略
對於所求的問題域,每次將其分爲大小相似的兩部分,然後再把每一部分當作最初的問題域再次分割,依次遞歸,直到得到基本問題(遞歸基),求解。然後,再逐步合併直到完成最初的問題域。
通常用遞歸的方式分割問題,例如這裏的歸併排序,每次將數組一分爲二,然後分別判斷分割動作是否到不可再分割了(數組中僅剩最後一個元素),否則繼續對左右兩個子數組進行分割:
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;
}