第四讲分治法.docx
- 文档编号:6470157
- 上传时间:2023-01-06
- 格式:DOCX
- 页数:20
- 大小:371.87KB
第四讲分治法.docx
《第四讲分治法.docx》由会员分享,可在线阅读,更多相关《第四讲分治法.docx(20页珍藏版)》请在冰豆网上搜索。
第四讲分治法
第四讲分治法
一、引入
问题1:
找出伪币
给你一个装有16枚硬币的袋子。
16枚硬币中有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。
你的任务是找出这枚伪造的硬币。
为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,比如天平。
利用这台仪器,可以知道两组硬币的重量是否相同。
方法1
任意取1枚硬币,与其他硬币进行比较,若发现轻者为伪币。
最多可能有15次比较。
方法2
将硬币分为8组,每组2个,每组比较一次,若发现轻的,则为伪币。
最多可能有8次比较。
方法3
分析
上述三种方法,分别需要比较15次,8次,4次,那么形成比较次数差异的根本原因在哪里?
方法1:
每枚硬币都至少进行了一次比较,而有一枚硬币进行了15次比较
方法2:
每一枚硬币只进行了一次比较
方法3:
将硬币分为两组后一次比较可以将硬币的范围缩小到了原来的一半,这样充分地利用了只有1枚伪币的基本性质。
问题2:
金块问题
有一个老板有一袋金块。
每个月将有两名雇员会因其优异的表现分别被奖励一个金块。
按规矩,排名第一的雇员将得到袋中最重的金块,排名第二的雇员将得到袋中最轻的金块。
根据这种方式,除非有新的金块加入袋中,否则第一名雇员所得到的金块总是比第二名雇员所得到的金块重。
如果有新的金块周期性的加入袋中,则每个月都必须找出最轻和最重的金块。
假设有一台比较重量的仪器,我们希望用最少的比较次数找出最轻和最重的金块。
方法1
假设袋中有n个金块。
可以用函数Max通过n-1次比较找到最重的金块。
找到最重的金块后,可以从余下的n-1个金块中用类似的方法通过n-2次比较找出最轻的金块。
这样,比较的总次数为2n-3。
算法如下:
max:
=a[1];min:
=a[1];
fori:
=2tondo {2n-2次比较}
begin
ifa[i]>maxthenmax:
=a[i];
ifa[i] =a[i]; end; 可对上述改进少1次 max: =a[1]; fori: =2tondo {n-1次比较,从n个元素中找到最大的} ifa[i]>maxthenbeginmax: =a[i];j: =i;end; fori: =j+1tondoa[i-1]: =a[i];{去掉最大的数a[j]} min: =a[1]; fori: =2ton-1do{n-2次比较,从剩下的元素中找最小的} ifa[i]>maxthenmin: =a[i]; 找金块的示例图 方法2: n≤2,识别出最重和最轻的金块,一次比较就足够了。 n>2,第一步,把这袋金块平分成两个小袋A和B。 第二步,分别找出在A和B中最重和最轻的金块。 设A中最重和最轻的金块分别为HA与LA,以此类推,B中最重和最轻的金块分别为HB和LB。 第三步,通过比较HA和HB,可以找到所有金块中最重的;通过比较LA和LB,可以找到所有金块中最轻的。 在第二步中,若n>2,则递归地应用分而治之方法。 分治过程: 比较过程: 分析 从图例可以看出,当有8个金块的时候,方法1需要比较15~16次,方法2只需要比较10次,那么形成比较次数差异的根本原因在哪里? 其原因在于方法2对金块实行了分组比较。 对于N枚金块,我们可以推出比较次数的公式: 假设n是2的次幂,c(n)为所需要的比较次数。 方法1: c(n)=2n-1 方法2: c(n)=2c(n/2)+2。 由c (2)=1,使用迭代方法可知c(n)=3n/2-2。 在本例中,使用方法2比方法1少用了25%的比较次数。 证明: 令n=2k C(2K)=2C(2K-1)+2 =2[2C(2K-2)+2]+2=22+2+22C(2K-2) =22+2+2[2C(2K-3)+2]=23+22+2+23C(2K-3) …… =2K-1+2K-2+…+2+2K-1C (2) =2K-1+2K-2+…+2+2K-1 =2K-2+2K-1 C(n)=3n/2-2 分治思想 分治(divide-and-conquer)就是“分而治之”的意思,其实质就是将原问题分成n个规模较小而结构与原问题相似的子问题;然后递归地解这些子问题,最后合并其结果就得到原问题的解。 当n=2时的分治法又称二分法。 其三个步骤如下; 1、分解(Divide): 将原问题分成一系列子问题。 2、解决(Conquer): 递归地解各子问题。 若子问题足够小,则可直接求解。 3、合并(combine);将子问题的结果合并成原问题的解。 问题的分解: 子集解的合并 由分治法所得到的子问题与原问题具有相同的类型。 如果得到的子问题相对来说还太大,则可反复使用分治策略将这些子问题分成更小的同类型子问题,直至产生出不用进一步细分就可求解的子问题。 分治求解可用一个递归过程来表示。 要使分治算法效率高,关键在于如何分割? 一般地,出于一种平衡原则,总是把大问题分成K个规模尽可能相等的子问题,但也有例外。 if问题不可分thenbegin 直接求解; 返回问题的解; end elsebegin 从原问题中划出含1/n运算对象的子问题1; 递归调用分治法过程,求出解1; 从原问题中划出含1/n运算对象的子问题2; 递归调用分治法过程,求出解2; ………… 从原问题中划出含1/n运算对象的子问题n; 递归调用分治法过程,求出解n; 将解1、解2、……、解n组合成整个问题的解; end; 二、应用举例 例1: 钢板分割问题。 设有一块正方形的钢板,现需将它分成n个小正方形。 例如,当: n=2不可能有解。 n=3不可能有解。 n=4可分成4个小正方形钢板。 n=5不可能有解。 n=6即一个大的加五个小的。 n=7即三个较大的加四个小的。 n=8即一个大的加七个小的。 问题为任给n,求出分成n个小正方形的方法。 分析: (1)按n=4的方法将1个小正方形分成4个,则增加了3个正方形。 (2)以n=6为基础,由 (1)可以得出n=9,12,15,。 (3)以n=7为基础,由 (1)可以得出n=10,13,16,。 (4)以n=8为基础,由 (1)可以得出n=11,14,17,。 由此可以得出如下的递归算法: proceduredevide(i: integer); begin ifn>8thenbegin 分解成四小块; 对于其中一块devide(n-3) end else casenof 6: 按n=6分割; 7: 按n=7分割; 8: 按n=8分割; end; end; 例2: 快速排序 基本思想 快速排序对冒泡排序的一种改进。 它的基本思想是: 通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。 算法过程 设要排序的数组是A[l]……A[r],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。 一躺快速排序的算法是: 1)设置两个变量i、j,排序开始的时候: i=l,j=r; 2)以第一个数组元素作为关键数据,赋值给X,即X=A[l]; 3)从j开始向前搜索,即由后开始向前搜索(j=j-1),找到第一个小于X的值,让该值与X交换; 4)从i开始向后搜索,即由前开始向后搜索(i=i+1),找到第一个大于X的值,让该值与X交换; 5)重复第3、4步,直到i=j; 例如: 待排序的数组A的值分别是: (初始关键数据: X=49) A[0]、A[1]、A[2]、A[3]、A[4]、A[5]、A[6]: 49386597761327 进行第一次交换后: 27386597761349 (按照算法的第三步从后面开始找) 进行第二次交换后: 27384997761365 (按照算法的第四步从前面开始找>X的值,65>49,两者交换,此时: i=3) 进行第三次交换后: 27381397764965 (按照算法的第五步将又一次执行算法的第三步从后开始找 进行第四次交换后: 27381349769765 (按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时: j=4) 此时再执行第三步的时候就发现i=j,从而结束一躺快速排序,那么经过一躺快速排序之后的结果是: 27381349769765,即所以大于49的数全部在49的后面,所以小于49的数全部在49的前面。 快速排序就是递归调用此过程——在以49为中点分割这个数据序列,分别对前面一部分和后面一部分进行类似的快速排序,从而完成全部数据序列的快速排序,最后把此数据序列变成一个有序的序列。 初始状态{49386597761327} 进行一次快速排序之后划分为{273813}49{769765} 分别对前后两部分进行快速排序{273813}经第三步和第四步交换后变成{132738}完成排序。 procedureqsort(l,r: longint); vari,j, m: longint; begin i: =l; j: =r; m: =a[random(r-l)+l]; repeat whilea[i] whilea[j]>mdodec(j); ifi<=jthen begin swap(a[i],a[j]); inc(i);dec(j); end; untili>j; ifl ifi end; pivot值的选择对快速排序具有决定性的作用。 理想的pivot值是子数组的中间值,即是排序数组的中间组成。 但是在排序之前我们无法得到中间值。 事实证明每个子数组的第一个,中间的和最后一个元素的中间值是上面所说的中间值的很好的替代。 它保证分块的每部分至少有两个元素,提供数组至少有4个,但是它的执行方式通常更好。 并且没有自然的情况会产生最坏的情况。 例3: 归并排序 某数列存储在对序列A[1],A[2],……,A[n],现采用归并思想进行排序。 分析: 这里我们采用二分法。 先将n个元素分成两个各含或()个元素的子序列;再用归并排序法对两个子序列递归的排序;最后合并两个已排序的子序列以得到排序结果。 在对子序列排序时,当其长度为1时递归结束。 单个元素被视为是已经排好的序列。 下面我们来分析一下对两个已排好序的子序列A[P..Q]和A[Q+1..R],将它们合并成一个已排好的子序列[P..R]。 引入一个辅助过程merge(A,P,Q,R)来完成这一合并工作,其中A是数组,P,Q,R是下标。 其方法是: 每次选两个子序列中较小的一个元素加入到目标序列中,直到某一个子序列为空,最后把另一子序列中剩下的元素加入到目标序列中。 proceduremerge(varA: listType;P,Q,R: Integer); {将A[P..Q]和A[Q+1..R],合并到序列A[P..R]} var I,{左子序列指针} J,{右子序列指针} T: Integer;{合并后的序列的指针} Lt: ListType;{暂存合并的序列} begin T: =P;I: =P;J: =Q+1; whileT<=Rdobegin{合并未完成} {若左序列剩有元素并且右序列元素全部合并或左序列的首元素小于等于右序列的首元素,则左序列的首元素进入合并序列} if(I<=Q)and((J>R)or(A[I]<=A[J]))thenbegin Lt[t]: =A[I];Inc(I);end elsebegin{否则右序列的首元素进入合并序列} Lt[t]: =A[J];Inc(J); end; inc(t);{合并后的序列的指针右移} end; A: =Lt;{合并后的序列赋给A} end; 下面我们来看看分治过程。 利用merge_sort(A,P,R)对数组A[P..R]进行排序。 若P=R,则子序列只有一个元素,分解完毕。 否则,计算出中间下标Q,将A[P..R]分成A[P..Q]和A[Q+1..R]。 若数组A[P..R]的元素个数K=R-P+1为偶数,则两个数组各含K/2个元素;否则A[P..Q]含个元素,A[Q+1..R]含个元素。 procedureMerge_Sort(varA: ListType;P,R: Integer); varQ: Integer; begin ifP<>Rthenbegin{若子序列A中不止一个元素} Q: =(P+R-1)div2;{计算中间下标Q} Merge_Sort(A,P,Q);{继续对左子序列A[P..Q]递归排序} Merge_Sort(A,Q+1,R);{继续对左子序列A[Q+1..R]递归排序} Merge(A,P,Q,R){对左子序列和右子序列归并排序} end; end; 用Merge_sort(A,1,N)便可对整个序列进行归并排序。 如果我们自底向上来看这个过程的操作时,算法将两个长度为1的序列合并成排好序的长度为2的序列,继而合并成长度为4的序列……,依次类推。 随着算法自底向上执行,被合并的排序序列长度逐渐增加,一直进行到将两个长度为n/2的序列合并成最终排好序的长度为n的序列。 下图列出了对序列(5,2,4,6,2,3,2,6)进行归并排序的过程。 例题4: 旅行家的预算 一个旅行家想驾驶汽车以最少的费用从一个城市到另一个城市(假设出 发时油箱是空的)。 给定两个城市之间的距离D1、汽车油箱的容量C(以升 为单位).每升汽油能行驶的距离D2、出发点每升汽油价格P和沿途油站数N (N可以为零),油站i离出发点的距离Di、每升汽油价格Pi(i=l, 2,...N)。 计算结果四舍五入至小数点后两位。 如果无法到达目的地,则输 出“Nosolution”。 INPUT(D1CD2PN) 275.611.927.42.82 油站号I离出发点的距离Di每升汽油价格 1102.02.9 2220.02.2 OUTPUT 26.95(该数据表示最小费用) 分析 首先找到(从后向前)油价最低的加油站,显然车至该站油箱应为空,这样就可将起点至该站与该站至终点作为两段独立考虑,分别求其最小费用,二者之和即为总费用,这样一直分下去,若某段只有起点与终点两个加油站时无需再分,如某一段油价最低的加油站即为起点,则如能一次加油即到达该段终点则最好,若不能,则加满油再考虑油箱有油情况下的二分法。 考虑起点之外所有的加油站中从后往前油价最低的加油站,若该加油站位于起点加满油后不能到达之处,则到达该站时油箱应该为空,以该站为界将全程分为两个独立段考虑,前半段为有油情况,后半段为无油情况。 第二种情况,若该加油站处于起点加满油后能到达之处,则将该段总路程缩短为该加油站至终点的情况,该加油站在该段路程中最便宜,若从该站加满油仍不能到达终点,则继续分治即可,程序被设计成一个递归函数money,形式参数start表示起点站,形式参数stop表示终点站,形式参数rest表示到达加油站start时汽车油箱余下的油的容量,money函数最终计算出从加油站start到stop区间内的最小费用。 functionmoney(start,stop: longint;rest: real): real; vark: longint; begin ifstop-start=1thenmoney: =((d[stop]-d[start])/d2-rest)*p[start] elsebegin k: =minp(start,stop-1); {minp(b,e: longint): longin;在b站到e站之间从后往前找油价最低的站} ifk<>startthen{油价最低的加油站不是起点站} money: =money(start,k,rest)+money(k,stop,0) else ifd[stop]-d[start]<=d2*cthen{在起点加满油能直接到达该段终点} money: =((d[stop]-d[start])/d2-rest)*p[start] else begin k: =minp(start+1,stop-1); ifd[k]-d[start]<=d2*cthen{在起点加满油能到达加油站k} money: =(c-rest)*p[start]+money(k,stop,c-(d[k]-d[start])/d2) elsemoney: =money(start,k,rest)+money(k,stop,0) end end end; 例5: 剔除多余括号(CTSC94-1) 输入表达式 应输出表达式 A+b(+c) A+b+c (a*b)+c/(d*e) A*b+a/(d*e) A+b/(c-d) A+b/(c-d) 键盘输入一个含有括号的四则运算表达式,可能含有多余的括号,编程整理该表达式,去掉所有多余的括号,原表达式中所有变量和运算符相对位置保持不变,并保持与原表达式等价。 例如: 注意输入a+b时不能输出b+a。 表达式以字符串输入,长度不超过255,输入不需要判错。 所有变量为单个小写字母。 只是要求去掉所有多余括号,不要求对表达式简化。 分析: 对于四则运算表达式,我们分析一下哪些括号可以去掉。 设待整理的表达式为(s1ops2);op为括号内优先级最低的运算符(“+”,“-”或“*”,“/”); ①左邻括号的运算符为“/”,则括号必须保留,即…/(s1ops2)…形式。 ②左邻括号的预算符为“*”或“-”。 而op为“+”或“-”,则保留括号,即…*(s1+s2)…或…-(s1+s2)…或…*(s1-s2)…或…-(s1-s2)…。 ③右邻括号的运算符为“*”或“/”,而op为“+”或“-”,原式中的op运算必须优先进行,因此括号不去除,即(s1+s2)*… 除上述情况外,可以括号去除,即…s1ops2…等价于…(s1ops2)… 我们从最里层嵌套的括号开始,依据上述规律逐步向外进行括号整理,直至最外层的括号保留或去除为止。 这个整理过程可以用一个递归过程来实现。 例如,剔除表达式“((a+b)*f)-(i/j)”中多余的括号。 依据上述算法进行整理的过程如上图。 最后,自底向上得到整理结果: (a+b)*f-i/j。 程序如下: programCTSC94_1; constinp='input.txt';outp='output.txt'; varch: char;expr: string; functionrightBracket(s: string;i: byte): byte; {在s串中找到下一个运算符的位置} varQ: byte;{Q用来记录括号层数} begin Q: =1; repeat inc(i); ifs[i]='('theninc(Q) else ifs[i]=')'thendec(Q); untilQ=0; rightbracket: =i; end; functionfind(s: string): byte;{找到优先级别最低的运算符的位置} vari,k: byte; begin i: =1;k: =0; whilei<=Length(s)dobegin if(s[i]='+')or(s[i]='-')thenbeginfind: =i;exit;end; if(K=0)and((s[i]='*')or(s[I]='/'))thenk: =i; ifs[i]='('theni: =rightBracket(s,i); inc(i); end; find: =k; end; functiondeleteBracket(s: string;varp: char): string; {剔除多余括号,S表示要处理的表达式;P表示表达式中最后一个运算符} vari: byte;ch1,ch2: char;Left,right: string; begin ifLength(s)=1thenbegin{当表达式中无运算符} deleteBracket: =s;p: =''; exit; end; if(s[1]='(')and(rightBracket(s,1)=length(s))then begin{当表达式最外层有括号} deleteBracket: =deleteBracket(copy(s,2,length(s)-2),p); exit; end; i: =find(s);{找到最低运算符} p: =s[i]; Left: =deletebracket(copy(s,1,i-1),ch1);{递归处理运算左边} right: =deleteBracket(copy(s,i+1,length(s)-i),ch2);{递归处理运算右边} if(pin['*','/'])and(ch1in['+','-'])thenLeft: ='('+Left+')'; if(pin['*','/'])and(ch2in['+','-'])or(P='/')and(ch2<>'')then right: ='('+right+')'; deletebracket: =Left+p+right; end; begin assign(input,inp);reset(input);readln(expr);close(input); assign(output,outp);rewrite(output); writeln(dele
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- 第四 分治
![提示](https://static.bdocx.com/images/bang_tan.gif)