分治法概述
分治法的设计思想
分治算法的设计思想是将一个规模较大的问题分解成若干个规模较小的问题分开解决,然后将规模较小的问题的解合并成规模较大的问题的解,分解的过程我们称为“分”,求解和合并解的过程我们称为“治”。
乍一看这不就是前面递归算法的一种特殊情况么?事实上分治法确实属于递归算法一类,比起一般的递归算法它有以下的特性:
1.该问题可以分解为若干个规模较小的相似问题,且当规模缩小到一定的程度就可以容易地解决,即递归出口的诞生是自然的。
2.该问题所分解出的各个子问题是相互之间独立的,即子问题之间不包含公共的子问题(区别使用动态规划算法场合和使用分治算法场合的方法)。
3.利用该问题分解出的子问题的解确定可以合并为该问题的解。
其中第一点和第三点是分治法的前提,第二点则是分治法的效率保证。
分治法的求解过程
分治法作为递归算法的一个特殊大类,在实现是按照递归算法设计的步骤进行的,在每一层递归上都有3个步骤:
1.分解:将规模较大的问题分解为若干个规模较小,相互独立,与原问题形式相同的子问题。
2.求解子问题:子问题规模缩小到容易被解决就直接求解,否则递归地分解各个子问题。
3.合并:将各个子问题的解合并为原问题的解。
分治法的一般的算法设计框架如下:
divide-and-conquer(P){
//当问题的规模缩小到一定的范围可以轻易解决时,直接返回解
if |P|≤n0 return adhoc(P);
//将P分解为较小的子问题:P1,P2······Pk;
for(i=1;i<=k;i++) yi=divide-and-conquer(Pi);
//合并子问题的解,得到规模较大的问题的解
return merge(y1,y2,…,yk);
}
人们从大量实践中发现,在用分治法设计算法时,最好使子问题的规模大致相同。就是说,将一个问题分成规模相等的k个子问题的处理方法在分治法中是行之有效的。
k=1时称为减治法,k=2的情况比较常见称为二分法。
求解排序问题
归并排序我个人觉得是非常能够体现分治法的一种排序算法,快速排序理论上也是,但是我个人觉得从另一种放置元素和调整其他元素的角度与分治法的结合来理解会更加清晰。
归并排序
用分治法去解决排序问题,最容易直接想到到的策略就是将长度为n的序列基本等分成k个序列分别进行排序,再将k个序列进行合并。
k=2时称为二路归并排序算法,k>2的情况称为多路归并排序算法。
对于二路归并排序算法,有从底到上的二路归并排序算法和从顶到下的二路归并排序算法,算法思想是一样的,只是在实现的方式上有所区别。
自底向上的二路归并排序算法
自底向上的二路归并排序算法的策略是按照从1开始每次增长一倍的length将整个序列分割成若干个子序列然后对子序列进行排序。
算法进行到最终解决的原问题就是当length的值尽可能的接近n时,整个序列排序完成。当我们有长度为length的各个子序列排序完成时,我们只需要对两个子序列进行一个归并就可以得到长度为length*2的子序列。
对应的分治算法如下:
//将长度为length的子序列归并成长度为length*2的子序列
//将a[low..mid]和a[mid+1..high]归并成a[low..high]
void Merge(int a[],int low,int mid,int high){
int *tmpa;//tmpa临时存放归并后的结果
int i=low,j=mid+1,k=0;
tmpa=(int *)malloc((high-low+1)*sizeof(int));
//对两个数组的元素进行归并,将较小的元素优先放入tmpa中直到某个数组的元素全部取完
while (i<=mid&&j<=high){
if (a[i]<=a[j]){
tmpa[k]=a[i]; i++; k++;
}
else{
tmpa[k]=a[j]; j++; k++;
}
}
//将两个数组中未放完的元素复制到tmpa中
while (i<=mid){
tmpa[k]=a[i]; i++; k++;}
while (j<=high){
tmpa[k]=a[j]; j++; k++;}
for (k=0,i=low;i<=high;k++,i++) a[i]=tmpa[k];//将tmpa复制回a中
free(tmpa);//释放tmpa所占内存空间
}
//进行长度为length的操作
void MergePass(int a[],int length,int n){
int i;
//归并length长的两个相邻子表
for (i=0;i+2*length-1<n;i=i+2*length) Merge(a,i,i+length-1,i+2*length-1);
//余下两个子表,后者长度小于length
if (i+length-1<n) Merge(a,i,i+length-1,n-1);//归并这两个子表
}
//从底向上的二路归并算法
void MergeSort(int a[],int n){
for (int length=1;length<n;length=2*length) MergePass(a