分治算法(一)
2016-02-04 21:44
337 查看
当我们求解某些问题时,由于这些问题要处理的数据相当多,或求解过程相当复杂,使得直接求解法在时间上相当长,或者根本无法直接求出。对于这类问题,我们往往先把它分解成几个子问题,找到求出这几个子问题的解法后,再找到合适的方法,把它们组合成求整个问题的解法。如果这些子问题还较大,难以解决,可以再把它们分成几个更小的子问题,以此类推,直至可以直接求出解为止。这就是分治策略的基本思想。
常规的解决方法是先将这些硬币分成两枚一组,每一次只称一组硬币,如果运气好的话只要称1次就可以找到,最坏最多称8次才可以找出那枚硬币,这种直接寻找的方法存在着相当大的投机性,适用于硬币数量少的情况,在硬币数量多的情况下就成为一件费时费力又需要运气的事。
试着改变一下方法:如果我们将全部硬币分成两组,将原来设计的一次比较两枚硬币变为一次比较两组硬币,我们会发现通过一次比较后.完全可以舍弃全部是真币的一组硬币,选取与原有问题一致的另一半进行下一步的比较,这样问题的规模就明显缩小,而且每一次比较的规模都是成倍减少(如图4-1所示)。
非递归法
【例2】一元三次方程求解
有形如:ax3 +bx2 +cx+d=0这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100 至 100 之间),且根与根之差的绝对值≥1。 要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位。
提示:记方程 f(x)=0,若存在 2 个数 x1 和 x2,且 x1<x2,f(x1)*f(x2)<0,
则在(x1,x2)之间一定有一个根。
输入:
a,b,c,d
输出:
三个实根(根与根之间留有空格)
输入输出样例
输入: 1 -5 -4 20
输出: -2.00 2.00 5.00
【例3】循环比赛
【问题描述】 设有N个选手的循环比赛。其中N=2M,要求每名选手要与其他N-1名选手都赛一次。每名选手每天比赛一次,循环赛共进行N-1天.要求每天没有选手轮空。
输入:M
输出:表格形式的比赛安排表
【样例输入】
3
【样例输出】
1 2 3 4 5 6 7 8
2 1 4 3 6 5 8 7
3 4 1 2 7 8 5 6
4 3 2 1 8 7 6 5
5 6 7 8 1 2 3 4
6 5 8 7 1 1 4 3
7 8 5 6 3 1 1 2
8 7 6 5 d 3 2 1
【问题分析】:此题很难直接给出结果,我们先将问题进行分解,n=2^m,将规模减半,如果m=3(即n=8),8名选手的比赛,减半后变成4名选手的比赛(n=4),4个选手的比赛的安排方式还不是很明显,再减半到两名选手队的比赛(n=2),两名选手的比赛安排方式很简单,只要让两名选手直接进行一场比赛即可:
分析两个球队的比赛的情况不难发现,这是一个对称的方阵,我们把这个方阵分成4部分(即左上,右上,左下,右下),右上部分可由左上部分加1(即加n/2)得到,而右上与左下部分、左上与右下部分别相等。因此我们也可以把这个方阵看作是由n=1的方阵所成生的,同理可得n=4的方阵:
同理可由n=4方阵生成n=8的方阵:
这样就构成了整个比赛的安排表。
在设计程序时,我们采用由小到大的方法进行扩展,而数组下标的处理是解决该问题的关键。用数组a记录2^m名选手的循环比赛表,整个循环比赛表从最初的1*1方阵按上述规则生成2*2的方阵,再生成4*4的方阵,……直到生成出整个循环比赛表为止。变量h表示当前方阵的大小,也就是要生成的下一个方阵的一半。
【参考程序】
[例4] 求方程的根
【问题描述】
输入m,n,p,a,b,求方程f(x)=mx+nx-px=0在[a,b]内的根。m,n,p,a,b均为整数,且a<b;m,n,p都大于等于1。如果有根,则输出,精确到1E-11;如果无方程根,则输出“NO”。
【样例】
equation.in
2 3 4 1 2
equation.out
1.5071265916E+00
2.9103830457E-11
【算法分析】
首先这是一个单调递增函数,对于一个单调递增(或递减)函数,如图4-7所示,判断在[a, b]范围内是否有解,解是多少。方法有多种,常用的一种方法叫“迭代法”,也就是“二分法”。先判断f(a)·f(b)≤0,如果满足则说明在[a, b]范围内有解,否则无解。如果有解再判断x=(a+b)/2是不是解,如果是则输出解结束程序,否则我们采用二分法,将范围缩小到[a, x)或(x, b],究竟在哪一半区间里有解,则要看是f(a)·f(x)<0还是f(x)·f(b)<0。
当然对于yx,我们需要用换底公式把它换成exp(x*ln(y))。
该问题的规模缩小到一定的程度就可以容易地解决;
该问题可以分解为若干个规模较小的相同问题,即该问题具有子结构性质
利用该问题分解出的子问题的解可以合并为该问题的解;
该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
第4条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。
1、引例:
如果给你一个装有16枚硬币的袋子,其中有一枚是伪造的,并且那枚伪造硬币的重量和真硬币的重量不同。你能不能用最少的比较次数找出这个伪造的硬币?为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。常规的解决方法是先将这些硬币分成两枚一组,每一次只称一组硬币,如果运气好的话只要称1次就可以找到,最坏最多称8次才可以找出那枚硬币,这种直接寻找的方法存在着相当大的投机性,适用于硬币数量少的情况,在硬币数量多的情况下就成为一件费时费力又需要运气的事。
试着改变一下方法:如果我们将全部硬币分成两组,将原来设计的一次比较两枚硬币变为一次比较两组硬币,我们会发现通过一次比较后.完全可以舍弃全部是真币的一组硬币,选取与原有问题一致的另一半进行下一步的比较,这样问题的规模就明显缩小,而且每一次比较的规模都是成倍减少(如图4-1所示)。
var a:array[1..20]of integer; n,i,m,x,y,k:integer; begin readln(n); x:=1;y:=n; for i:=1 to n do readln(a[i]); readln(m); repeat k:=(x+y)div 2; if a[k]=m then begin writeln('the num in ',k);halt;end else begin if a[k]<m then x:=k+1; if a[k]>m then y:=k-1; end; until x>y; writeln('No find'); end.
非递归法
【例2】一元三次方程求解
有形如:ax3 +bx2 +cx+d=0这样的一个一元三次方程。给出该方程中各项的系数(a,b,c,d 均为实数),并约定该方程存在三个不同实根(根的范围在-100 至 100 之间),且根与根之差的绝对值≥1。 要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后 2 位。
提示:记方程 f(x)=0,若存在 2 个数 x1 和 x2,且 x1<x2,f(x1)*f(x2)<0,
则在(x1,x2)之间一定有一个根。
输入:
a,b,c,d
输出:
三个实根(根与根之间留有空格)
输入输出样例
输入: 1 -5 -4 20
输出: -2.00 2.00 5.00
【例3】循环比赛
【问题描述】 设有N个选手的循环比赛。其中N=2M,要求每名选手要与其他N-1名选手都赛一次。每名选手每天比赛一次,循环赛共进行N-1天.要求每天没有选手轮空。
输入:M
输出:表格形式的比赛安排表
【样例输入】
3
【样例输出】
1 2 3 4 5 6 7 8
2 1 4 3 6 5 8 7
3 4 1 2 7 8 5 6
4 3 2 1 8 7 6 5
5 6 7 8 1 2 3 4
6 5 8 7 1 1 4 3
7 8 5 6 3 1 1 2
8 7 6 5 d 3 2 1
【问题分析】:此题很难直接给出结果,我们先将问题进行分解,n=2^m,将规模减半,如果m=3(即n=8),8名选手的比赛,减半后变成4名选手的比赛(n=4),4个选手的比赛的安排方式还不是很明显,再减半到两名选手队的比赛(n=2),两名选手的比赛安排方式很简单,只要让两名选手直接进行一场比赛即可:
1 | 2 |
2 | 1 |
1 | 2 | 3 | 4 |
2 | 1 | 4 | 3 |
3 | 4 | 1 | 2 |
4 | 3 | 2 | 1 |
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
2 | 1 | 4 | 3 | 6 | 5 | 8 | 7 |
3 | 4 | 1 | 2 | 7 | 8 | 5 | 6 |
4 | 3 | 2 | 1 | 8 | 7 | 6 | 5 |
5 | 6 | 7 | 8 | 1 | 2 | 3 | 4 |
6 | 5 | 8 | 7 | 2 | 1 | 4 | 3 |
7 | 8 | 5 | 6 | 3 | 4 | 1 | 2 |
8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 |
在设计程序时,我们采用由小到大的方法进行扩展,而数组下标的处理是解决该问题的关键。用数组a记录2^m名选手的循环比赛表,整个循环比赛表从最初的1*1方阵按上述规则生成2*2的方阵,再生成4*4的方阵,……直到生成出整个循环比赛表为止。变量h表示当前方阵的大小,也就是要生成的下一个方阵的一半。
【参考程序】
program p4_1; var i,j,h,m,n:integer; a:array[1..100,1..100]of integer; begin assign(input,'word.in'); reset(input); assign(output,'word.out'); rewrite(output); readln(m); n:=1;a[1,1]:=1;h:=1; for i:=1 to m do n:=n*2; repeat for i:=1 to h do for j:=1 to h do begin a[i,j+h]:=a[i,j]+h;{构造右上角方阵} a[i+h,j]:=a[i,j+h];{构造左下角方阵} a[i+h,j+h]:=a[i,j];{构造右下角方阵} end; h:=h*2; until h=n; for i:=1 to n do begin for j:=1 to n do write(a[i,j]:4); writeln; end; close(input); close(output); end.
[例4] 求方程的根
【问题描述】
输入m,n,p,a,b,求方程f(x)=mx+nx-px=0在[a,b]内的根。m,n,p,a,b均为整数,且a<b;m,n,p都大于等于1。如果有根,则输出,精确到1E-11;如果无方程根,则输出“NO”。
【样例】
equation.in
2 3 4 1 2
equation.out
1.5071265916E+00
2.9103830457E-11
【算法分析】
首先这是一个单调递增函数,对于一个单调递增(或递减)函数,如图4-7所示,判断在[a, b]范围内是否有解,解是多少。方法有多种,常用的一种方法叫“迭代法”,也就是“二分法”。先判断f(a)·f(b)≤0,如果满足则说明在[a, b]范围内有解,否则无解。如果有解再判断x=(a+b)/2是不是解,如果是则输出解结束程序,否则我们采用二分法,将范围缩小到[a, x)或(x, b],究竟在哪一半区间里有解,则要看是f(a)·f(x)<0还是f(x)·f(b)<0。
当然对于yx,我们需要用换底公式把它换成exp(x*ln(y))。
4、小结:
分治法所能解决的问题一般具有以下几个特征:该问题的规模缩小到一定的程度就可以容易地解决;
该问题可以分解为若干个规模较小的相同问题,即该问题具有子结构性质
利用该问题分解出的子问题的解可以合并为该问题的解;
该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子问题。
第4条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然也可用分治法,但一般用动态规划较好。
相关文章推荐
- Unable to load configuration. - action - file:/D:/bianchenggggggggggggg/Tomcat/Tomcat%208/me-webapps
- IMP-00037 未知字符集标记 IMP-00000 未导入成功
- 第4讲项目1——点阵图
- Dijkstra算法和Floyd算法简介(最短路径算法)
- leetcode226题 题解 翻译 C语言版 Python版
- 应用css3制作loading效果
- 图解 SQL 各种连接查询之间的区别
- 2015年总结
- CocoaPods安装和使用教程
- HTML中meta 标签使用详解
- hdu1695(莫比乌斯)或欧拉函数+容斥
- jQuery第九章
- XCGLogger Swift日志打印
- jQuery第十一章
- 244,在开发线通讯录过程,积累知识
- 个人学习之HTML语句(1)
- [CCF]201312-2 ISBN号码
- STL list链表的用法详解
- Android 百度地图 SDK v3.0.0 (二) 定位与结合方向传感器
- IT系统设计中的5S原则