# 分治法–处理数列问题
最近学了分治法,我发现分治法在求一组数列的某些数据时有着很简洁的技巧。分治算法基本思想–“分”、“治”、“合”
老板有一袋金块(共n块),最优秀的雇员得到其中最重的一块,最差的雇员得到其中最轻的一块。假设有一台比较重量的仪器,我们希望用最少的比较次数找出最重的金块。
用分治法(二分法)可用较少比较次数解决上述问题:
1)将数据等分为两组(两组数据可能差1),目的是分别选取其中的最大(小)值。
2)递归分解直到每组元素的个数≤2,可简单地找到最大(小)值。
3)回溯时,在分解的两组解中大者取大,小者取小,合并为当前问题的解。
float a[n];
maxmin (int i, int j ,float &fmax, float &fmin)
{ int mid; float lmax, lmin, rmax, rmin;
if (i=j) {
fmax= a[i];
fmin=a[i];}
else if (i=j-1)
if(a[i]rmax) fmax=lmax;
else fmax=rmax;
if (lmin>rmin) fmin=rmin;
else fmin=lmin;}
//合的部分因为每一小部分都有一个最大与最小值,合就可以再在前一种的情况下。
//再找出最大最小的情况以此递归就可以找出一整组数的最大最小值。
}
#include
#define N 10
void max_min(int *a,int m,int n,int *min1,int *min2,int *max1,int *max2);
int main(void)
{
int a[N]={7,8,9,10,11,12,13,14,15,16};
int min1,min2;
int max1,max2;
max_min(a,0,N-1,&min1,&min2,&max1,&max2);
//这四个参数的大小关系为 min1rmax1)//左子数组最大数>右子数组最大数
{
if(lmax2>rmax1)
{
*max1=lmax1;
*max2=lmax2;
}
else
{
*max1=lmax1;
*max2=rmax1;
}
}
else//右子数组最大数>左子数组最大数
if(rmax2>lmax1)
{
*max1=rmax1;
*max2=rmax2;
}
else
{
*max1=rmax1;
*max2=lmax1;
}
}//合的部分,对左右两组数列中的4个数进行再一次的排序,这种循环就可以重新确定出4个值
//两组一次进行这样的筛选,知道最后就可以找出整个数列中的4个满足要求的值。
}
#include
#define N 10
int getsum(int *a,int l,int r)//传数组a的每一个的值然后便于用来加减
{
int mid;
if(l==r)
return a[l];//如果数组只有一个数,返回这个数即可
else if(l==r-1)
return a[l]+a[r];//如果有数组只有两个数,返回值为这两个数的值相加
//最小情况的处理
else
{
mid=(l+r)/2;
return getsum(a,l,mid)+getsum(a,mid+1,r);
//利用递归来做分治数组求和
}
}
int main()
{
int c;
int a[N]={1,2,3,4,5,6,7,8,9,10};//定义数组,拿来求和
c=getsum(a,0,N-1);//获取要的到的值,即为数组的和
printf("最后分治法求的和为:%d",c);
}
我们可以把整个序列平均分成左右两部分,答案则会在以下三种情况中:
1、所求序列完全包含在左半部分的序列中。
2、所求序列完全包含在右半部分的序列中。
3、所求序列刚好横跨分割点,即左右序列各占一部分。
前两种情况和大问题一样,只是规模小了些,如果三个子问题都能解决,那么答案就是三个结果的最大值。
以分割点为起点向左的最大连续序列和、以分割点为起点向右的最大连续序列和,这两个结果的和就是第三种情况的答案。
#include
//N是数组长度,num是待计算的数组,放在全局区是因为可以开很大的数组
int N, num[16777216];
int solve(int left, int right)
{
//序列长度为1时
if(left == right)
return num[left];
//划分为两个规模更小的问题
int mid = left + right >> 1;
//除以2
int lans = solve(left, mid);
int rans = solve(mid + 1, right);
//划分成左右两个数列递归找到,
//横跨分割点的情况
int sum = 0, lmax = num[mid], rmax = num[mid + 1];
for(int i = mid; i >= left; i--) {
sum += num[i];
if(sum > lmax) lmax = sum;
}
//从分割点向左相加,并求和,若求出的总和大于中间分割点的那个值,就把这个和置为新的lmax,意为左边最大值。
sum = 0;
for(int i = mid + 1; i <= right; i++) {
sum += num[i];
if(sum > rmax) rmax = sum;
}//同理求出右边的最大值
//答案是三种情况的最大值
int ans = lmax + rmax;
if(lans > ans) ans = lans;
if(rans > ans) ans = rans;
return ans;
}
int main()
{
//输入数据
scanf("%d", &N);
for(int i = 1; i <= N; i++)
scanf("%d", &num[i]);
printf("%d\n", solve(1, N));
return 0;
}
其实这些题都是相同的类型的,结构上都是大同小异。定义一个可以递归的函数,设置一个递归出口,这里通常有对最小情况的处理。或是递归对问题规模再进行细小的划分。划分结束后就是对每一次小的情况找出来的一些值(通常二分法分治会找到左右两个数),对两个数再进行判断,从而找出再一次满足条件的值。循环下去就可以找到最终的值。
链接:http://note.youdao.com/noteshare?id=05752e16766f50ab5a7096f1f14aaecd&sub=E67840BAC3A14C0DAC685B62E5A3EC19