分治算法核心思想
分治算法用四个字概括就是分而治之。
将原问题划分成多个小规模并且简单的子问题,这些子问题的结构与原问题相似,因此,递归地解决这些子问题,将子问题的解合并就可以得到原问题的解了。
分治算法的思路与递归很相似,但是分治算法是一种算法思想,是一种处理问题的思想,而递归是一种编程技巧。所以,分治算法通常适合使用递归来实现。
在使用递归实现分治算法时,有以下三步操作:
- 分解:将子问题分解成一系列小规模并且与原问题结构相似的子问题
- 解决:递归的解决每一个子问题
- 合并:将子问题的解合并得到原问题的解
那么,哪些问题适合使用分治算法来解决呢?
- 原问题可以分解成一系列与原问题结构相似的子问题,并且子问题很容易解决
- 原问题分解成的子问题可以独立求解,并且子问题与子问题之间没有关联性(区别于动态规划)
- 原问题在分解子问题的时候存在终止条件,即分解到一定程度后,子问题很容易解决。
- 最后求得子问题的解后,可以合并成原问题的解,并且这个合并操作复杂度不能太高。
分治算法实践:归并排序
两路归并排序的思路是,首先将数组分为两半,再递归的进行分割,直到每一份都只剩 1 个数据,再将每一份两两合并。如图所示:
- 分解
- 解决
可以发现,在分解到只有单个数据时,从排序的角度看,实际上已经有序的,因此,直接再合并即可 - 合并
C代码实现:
#include<stdio.h>
#include<stdlib.h>
//一遍归并
void merge(int a[], int temp[], int start, int mid, int end) {
int i = start, j = mid + 1, k = start; //第一部分:start到mid,第二部分:mid+1到end
while(i != mid + 1 && j != end + 1) {
if(a[i] > a[j]) { //取较大者导入临时数组
temp[k++] = a[i++];
} else {
temp[k++] = a[j++];
}
}
while(i != mid + 1) { //两个部分若有多余的,直接导入temp数组
temp[k++] = a[i++];
}
while(j != end + 1) {
temp[k++] = a[j++];
}
for(i = start; i <= end; i ++) { //从临时数组导出到原数组
a[i] = temp[i];
}
}
void merge_sort(int a[], int temp[], int start, int end) {
int mid;
if(start < end) {
mid = (start + end) / 2;
merge_sort(a,temp,start,mid); //前半部分递归
merge_sort(a,temp,mid+1,end); //后半部分递归
merge(a,temp,start,mid,end); //排序
}
}
void Sort(int a[], int N) {
int* b;
b = (int*)malloc(N*sizeof(int));
if(b != NULL) {
merge_sort(a,b,0,N);
free(b);
} else
printf("空间不足");
}
int main() {
int a[10] = {0,1,2,3,4,5,6,7,8,9,};
Sort(a,9);
for(int i = 0 ; i < 10 ; i ++) {
printf("%d ",a[i]);
}
}