# 分治算法 ## 思想 所谓分治就是指的分而治之,即将较大规模的问题分解成几个较小规模的问题,通过对较小规模问题的求解达到对整个问题的求解。当我们将问题分解成两个较小问题求解时的分治方法称之为二分法。 分治的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相似。 找出各部分的解,然后把各部分的解组合成整个问题的解。 解决算法实现的同时,需要估算算法实现所需时间。 + 分治算法时间是这样确定的: + 解决子问题所需的工作总量(由子问题的个数、解决每个子问题的工作量 决定)合并所有子问题所需的工作量。 + 分治法是把任意大小问题尽可能地等分成两个子问题的递归算法。 + 分治的具体过程: ``` {{开始}    if ①问题不可分 ②返回问题解     else {        ③从原问题中划出含一半运算对象的子问题1;         ④递归调用分治法过程,求出解1;         ⑤从原问题中划出含另一半运算对象的子问题2;         ⑥递归调用分治法过程,求出解2;         ⑦将解1、解2组合成整个问题的解;      }   } //结束 ``` ## 例题讲解 **【例1】快速排序(递归算法)** ``` c void qsort(int l,int r) { int i,j,mid,p; i=l;j=r; mid=a[(l+r)/2]; //将当前序列在中间位置的数定义为分隔数 do { while (a[i]mid) {j--;} //在右半部分寻找比中间数小的数 if (i<=j) { //若找到一组与排序目标不一致的数对则交换它们 p=a[i];a[i]=a[j];a[j]=p; i++;j--; //继续找 } }while(i<=j); //注意这里要有等号 if (l using namespace std; int jc(int,int); int n,a[1000],m; int main() { int x,y,i; cin>>n; x=1;y=n; for (i=1;i<=n;i++) //输入排序好的数 cin>>a[i]; cin>>m; //输入要查找的数 jc(x,y); //递归过程 cout<y) cout<<"no find"<m) jc(x,k-1); //在前半中查找 } } ``` **【例3】一元三次方程求解** ​ **有形如:ax3+bx2+cx+d=0这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值≥1。** ​ **要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。** ​ `提示:记方程f(x)=0,若存在2个数x1和x2,且x10`,则确定根x不在区间[x1,x2]内,设定[x2,x2+1]为下一个搜索区间 3. `f(x1)*f(x2)<0`,则确定根x在区间[x1,x2]内。 ​ 如果确定根x在区间[x1,x2]内的话(`f(x1)*f(x2)<0`),如何在该区间找到根的确切位置。采用二分法,将区间[x1,x2]分成左右两个子区间:左子区间[x1,x]和右子区间[x,x2]: ​ 如果`f(x1)*f(x)≤0`,则确定根在左区间[x1,x]内,将x设为该区间的右指针(x2=x),继续对左区间进行对分;如果`f(x1)*f(x)>0`,则确定根在右区间[x,x2]内,将x设为该区间的左指针(x1=x),继续对右区间进行对分; 上述对分过程一直进行到区间的间距满足精度要求为止(x2-x1<0.001)。此时确定x1为f(x)的根。 ``` c 由此得出算法: 输入方程中各项的系数a,b,c,d ; { for (x=-100;x<=100;x++) //枚举每一个可能的根 { x1=x;x2=x+1; //确定根的可能区间 if (f(x1)==0) printf("%.2f ",x1); //若x1为根,则输出 else if (f(x1)*f(x2)<0) //若根在区间[x1,x2]中 { while (x2-x1>=0.001) //若区间[x1,x2]不满足精度要求,则循环 { xx=(x2+x1)/2; //计算区间[x1,x2]的中间位置 if ((f(x1)*f(xx))<=0) //若根在左区间,则调整右指针 x2=xx; else x1=xx; //若根在右区间,则调整左指针 } printf("%.2f ",x1); //区间[x1,x2]满足精度要求,确定x1为根 } } cout< const int MAXN=33,MAXM=5; int matchlist[MAXN][MAXN]; int m; int main() { printf("Input m:"); scanf("%d",&m); int n=1< #include using namespace std; int b,p,k,a; int f(int p) //利用分治求b^p % k { if (p==0) return 1; // b^0 %k=1 int tmp=f(p/2)%k; tmp=(tmp*tmp) % k; // b^p %k=(b^(p/2))^2 % k if (p%2==1) tmp=(tmp * b) %k; //如果p为奇数,则 b^p % return tmp; //k=((b^(p/2))^2)* b%k } int main() { cin>>b>>p>>k; //读入3个数 int tmpb=b; //将b的值备份 b%=k; //防止b太大 printf("%d^%d mod %d=%d\n",tmpb,p,k,f(p)); //输出 return 0; } ``` **【例8】、黑白棋子的移动(chessman)** **【问题描述】** **有2n个棋子(n≥4)排成一行,开始位置为白子全部在左边,黑子全部在右边,如下图为n=5的情形:** **○○○○○●●●●●** **移动棋子的规则是:每次必须同时移动相邻的两个棋子,颜色不限,可以左移也可以右移到空位上去,但不能调换两个棋子的左右位置。每次移动必须跳过若干个棋子(不能平移),要求最后能移成黑白相间的一行棋子。如n=5时,成为:** **○●○●○●○●○●** **任务:编程打印出移动过程。** **【输入样例】chessman.in** **7** **【输出样例】chessman.out** ``` step 0:ooooooo*******-- step 1:oooooo--******o* step 2:oooooo******--o* step 3:ooooo--*****o*o* step 4:ooooo*****--o*o* step 5:oooo--****o*o*o* step 6:oooo****--o*o*o* step 7:ooo--***o*o*o*o* step 8:ooo*o**--*o*o*o* step 9:o--*o**oo*o*o*o* step10:o*o*o*--o*o*o*o* step11:--o*o*o*o*o*o*o* ``` **【算法分析】** **我们先从n=4开始试试看,初始时:** ​ **○○○○●●●●** **第1步:○○○——●●●○● {—表示空位}** **第2步:○○○●○●●——●** **第3步:○——●○●●○○●** **第4步:○●○●○●——○●** **第5步:——○●○●○●○●** **如果n=5呢?我们继续尝试,希望看出一些规律,初始时:** ​ **○○○○○●●●●●** **第1步:○○○○——●●●●○●** **第2步:○○○○●●●●——○●** ​ **这样,n=5的问题又分解成了n=4的情况,下面只要再做一下n=4的5个步骤就行了。同理,n=6的情况又可以分解成n=5的情况,……,所以,对于一个规模为n的问题,我们很容易地就把他分治成了规模为n-1的相同类型子问题。** **数据结构如下:数组c[1..max]用来作为棋子移动的场所,初始时,c[1]~c[n]存放白子(用字符o表示),c[n+1]~c[2n]存放黑子(用字符*表示),c[2n+1],c[2n+2]为空位置(用字符—表示)。最后结果在c[3]~c[2n+2]中。** ``` c #include using namespace std; int n,st,sp; char c[101]; void print() //打印 { int i; cout<<"step "<>n; init(n); mv(n); } ```