您好,欢迎访问三七文档
当前位置:首页 > 商业/管理/HR > 资本运营 > 第7章 分治算法(C++版)
第七章分治算法所谓分治就是指的分而治之,即将较大规模的问题分解成几个较小规模的问题,通过对较小规模问题的求解达到对整个问题的求解。当我们将问题分解成两个较小问题求解时的分治方法称之为二分法。分治的基本思想是将一个规模为n的问题分解为k个规模较小的子问题,这些子问题互相独立且与原问题相似。找出各部分的解,然后把各部分的解组合成整个问题的解。1、解决算法实现的同时,需要估算算法实现所需时间。分治算法时间是这样确定的:解决子问题所需的工作总量(由子问题的个数、解决每个子问题的工作量决定)合并所有子问题所需的工作量。2、分治法是把任意大小问题尽可能地等分成两个子问题的递归算法。3、分治的具体过程:{{开始}if①问题不可分②返回问题解else{③从原问题中划出含一半运算对象的子问题1;④递归调用分治法过程,求出解1;⑤从原问题中划出含另一半运算对象的子问题2;⑥递归调用分治法过程,求出解2;⑦将解1、解2组合成整个问题的解;}}//结束【例1】快速排序(递归算法)voidqsort(intl,intr){inti,j,mid,p;i=l;j=r;mid=a[(l+r)/2];//将当前序列在中间位置的数定义为分隔数do{while(a[i]mid){i++;}//在左半部分寻找比中间数大的数while(a[j]mid){j--;}//在右半部分寻找比中间数小的数if(i=j){//若找到一组与排序目标不一致的数对则交换它们p=a[i];a[i]=a[j];a[j]=p;i++;j--;//继续找}}while(i=j);//注意这里要有等号if(lj)qsort(l,j);//若未到两个数的边界,则递归搜索左右区间if(ir)qsort(i,r);}【例2】用递归算法实现二分查找即:有n个已经从小到大排序好的数据,输入一个数m,用二分查找算法,判断它是否在这n个数中。#includeiostreamusingnamespacestd;intjc(int,int);intn,a[1000],m;intmain(){intx,y,i;cinn;x=1;y=n;for(i=1;i=n;i++)//输入排序好的数cina[i];cinm;//输入要查找的数jc(x,y);//递归过程coutendl;}intjc(intx,inty)//递归过程{intk;k=(x+y)/2;//取中间位置点if(a[k]==m)coutthennuminkendl;//找到查找的数,输出结果if(xy)coutnofindendl;//找不到该数else{if(a[k]m)jc(k+1,y);//在后半中查找if(a[k]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,且x1x2,f(x1)*f(x2)<0,则在(x1,x2)之间一定有一个根。输入:a,b,c,d输出:三个实根(根与根之间留有空格)【输入输出样例】输入:1-5-420输出:-2.002.005.00【算法分析】这是一道有趣的解方程题。为了便于求解,设方程f(x)=ax3+bx2+cx+d=0,设根的值域(-100至100之间)中有x,其左右两边相距0.0005的地方有x1和x2两个数,即x1=x-0.0005,x2=x+0.0005。x1和x2间的距离(0.001)满足精度要求(精确到小数点后2位)。若出现如图1所示的两种情况之一,则确定x为f(x)=0的根。有两种方法计算f(x)=0的根x:1.枚举法根据根的值域和根与根之间的间距要求(≥1),我们不妨将根的值域扩大100倍(-10000≤x≤10000),依次枚举该区间的每一个整数值x,并在题目要求的精度内设定区间:x1=,x2=。若区间端点的函数值f(x1)和f(x2)异号或者在区间端点x1的函数值f(x1)=0,则确定为f(x)=0的一个根。由此得出算法:输入方程中各项的系数a,b,c,d;for(x=-10000;x=10000;x++)//枚举当前根*100的可能范围{x1=(x-0.05)/100;x2=(x+0.05)/100;//在题目要求的精度内设定区间if(f(x1)*f(x2)0||f(x1)==0)//若在区间两端的函数值异号或在x1处的函数值为0,则确定x/100为根printf(“%.2f”,x/100);}其中函数f(x)计算x3+b*x2+c*x+d:doublef(doublex)//计算x3+b*x2+c*x+d{f=x*x*x+b*x*x+c*x+d;}//f函数2.分治法枚举根的值域中的每一个整数x(-100≤x≤100)。由于根与根之差的绝对值≥1,因此设定搜索区间[x1,x2],其中x1=x,x2=x+1。若⑴f(x1)=0,则确定x1为f(x)的根;⑵f(x1)*f(x2)0,则确定根x不在区间[x1,x2]内,设定[x2,x2+1]为下一个搜索区间⑶f(x1)*f(x2)0,则确定根x在区间[x1,x2]内。如果确定根x在区间[x1,x2]内的话(f(x1)*f(x2)0),如何在该区间找到根的确切位置。采用二分法,将区间[x1,x2]分成左右两个子区间:左子区间[x1,x]和右子区间[x,x2](其中x=):如果f(x1)*f(x)≤0,则确定根在左区间[x1,x]内,将x设为该区间的右指针(x2=x),继续对左区间进行对分;如果f(x1)*f(x)0,则确定根在右区间[x,x2]内,将x设为该区间的左指针(x1=x),继续对右区间进行对分;上述对分过程一直进行到区间的间距满足精度要求为止(x2-x10.001)。此时确定x1为f(x)的根。由此得出算法:输入方程中各项的系数a,b,c,d;{for(x=-100;x=100;x++)//枚举每一个可能的根{x1=x;x2=x+1;//确定根的可能区间if(f(x1)==0)printf(%.2f,x1);//若x1为根,则输出elseif(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;elsex1=xx;//若根在右区间,则调整左指针}printf(%.2f,x1);//区间[x1,x2]满足精度要求,确定x1为根}}coutendl;}doublef(doublex)//将x代入函数{return(x*x*x*a+b*x*x+x*c+d);}其中f(x)的函数说明如枚举法所示。【例4】、循环比赛日程表(match)【问题描述】设有N个选手进行循环比赛,其中N=2M,要求每名选手要与其他N-1名选手都赛一次,每名选手每天比赛一次,循环赛共进行N-1天,要求每天没有选手轮空。输入:M输出:表格形式的比赛安排表【样例输入】match.in3【样例输出】match.out1234567821436587341278564321876556781234658721437856341287654321【问题分析】以M=3(即N=23=8)为例,可以根据问题要求,制定出如下图所示的一种方案:以表格的中心为拆分点,将表格分成A、B、C、D四个部分,就很容易看出有A=D,B=C,并且,这一规律同样适用于各个更小的部分。设有n个选手的循环比赛,其中n=2m,要求每名选手要与其他n-1名选手都赛一次。每名选手每天比赛一次,循环赛共进行n-1天。要求每天没有选手轮空.以下是八名选手时的循环比赛表,表中第一行为八位选手的编号,下面七行依次是每位选手每天的对手。1234567821436587341278564321876556781234658721437856341287654321【算法分析】从八位选手的循环比赛表中可以看出,这是一个具有对称性的方阵,可以把方阵一分为四来看,那么左上角的4*4的方阵就是前四位选手的循环比赛表,而右上角的4*4的方阵就是后四位选手的循环比赛表,它们在本质上是一样的,都是4个选手的循环比赛表,所不同的只是选手编号不同而已,将左上角中方阵的所有元素加上4就能得到右上角的方阵.下方的两个方阵表示前四位选手和后四位选手进行交叉循环比赛的情况,同样具有对称性,将右上角方阵复制到左下角即得到1,2,3,4四位选手和5,6,7,8四位选手的循环比赛表,根据对称性,右下角的方阵应与左上角的方阵相同.这样,八名选手的循环比赛表可以由四名选手的循环比赛表根据对称性生成出来.同样地,四名选手的循环比赛表可以由二名选手的循环比赛表根据对称性生成出来,而两名选手的循环比赛表可以说是已知的,这种程序设计方法叫做分治法,其基本思想是把一个规模为n的问题分成若干个规模较小的问题,使得从这些较小问题的解易于构造出整个问题的解。程序中用数组matchlist记录n名选手的循环比赛表,整个循环比赛表从最初的1*1的方阵按上述规则生成出2*2的方阵,再生成出4*4的方阵,……,直到生成出整个循环比赛表为止.变量half表示当前方阵的大小,也是要生成的下一个方阵的大小的一半。【参考程序】#includecstdioconstintMAXN=33,MAXM=5;intmatchlist[MAXN][MAXN];intm;intmain(){printf(Inputm:);scanf(%d,&m);intn=1m,k=1,half=1;//1m相当于2^mmatchlist[0][0]=1;while(k=m){for(inti=0;ihalf;i++)//构造右上方方阵for(intj=0;jhalf;j++)matchlist[i][j+half]=matchlist[i][j]+half;for(inti=0;ihalf;i++)//对称交换构造下半部分方阵for(intj=0;jhalf;j++){matchlist[i+half][j]=matchlist[i][j+half];//左下方方阵等于右上方方阵matchlist[i+half][j+half]=matchlist[i][j];//右下方方阵等于左上方方阵}half*=2;k++;}for(inti=0;in;i++){for(intj=0;jn;j++)printf(%4d,matchlist[i][j]);putchar('\n');}return0;}【例5】取余运算(mod)【问题描述】输入b,p,k的值,求bpmodk的值。其中b,p,k*k为长整形数。【输入样例】mod.in2109【输出样例】mod.out2^10mod9=7【算法分析】本题主要的难点在于数据规模很大(b,p都是长整型数),对于bp显然不能死算,那样的话时间复杂度和编程复杂度都很大。下面先介绍一个原理:A*B%K=(A%K)*(B%K)%K。显然有了这个原理,就可以把较大的幂分解成较小的,因而免去高精度计算等复杂过程。那么怎样分解最有效呢?显然对于任何一个自然数P,有P=2*P/2+P%2,如19=2*19/2+19%2=2*9+1,利用上述原理就可以把B的19次方除以K的余数转换为求B的9次方除以K的余数,即B19=B2*9+1=B*B9*B9,再进
本文标题:第7章 分治算法(C++版)
链接地址:https://www.777doc.com/doc-3995331 .html