分冶策略生成PDF.docx
《分冶策略生成PDF.docx》由会员分享,可在线阅读,更多相关《分冶策略生成PDF.docx(24页珍藏版)》请在冰豆网上搜索。
分冶策略生成PDF
分治策略(DivideandConquer)
一、算法思想
任何一个可以用计算机求解的问题所需的计算时间都与其规模有关。
问题规模越小,解题所需的计算时间往往也越少,从而也越容易计算。
想解决一个较大的问题,有时是相当困难的。
分治法的思想就是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。
分而治之方法与软件设计的模块化方法非常相似。
为了解决一个大的问题,可以:
1)把它分成两个或多个更小的问题;2)分别解决每个小问题;3)把各小问题的解答组合起来,即可得到原问题的解答。
小问题通常与原问题相似,可以递归地使用分而治之策略来解决。
1、解决算法实现的同时,需要估算算法实现所需时间。
分治算法时间是这样确定的:
解决子问题所需的工作总量(由子问题的个数、解决每个子问题的工作量决定)合并所有子问题所需的工作量。
2、分治法是把任意大小问题尽可能地等分成两个子问题的递归算法
3、分治的具体过程:
begin {开始}
if①问题不可分then②返回问题解
elsebegin
③从原问题中划出含一半运算对象的子问题1;
④递归调用分治法过程,求出解1;
⑤从原问题中划出含另一半运算对象的子问题2;
⑥递归调用分治法过程,求出解2;
⑦将解1、解2组合成整修问题的解;
end;
end;{结束}
二、分治策略的应用
(1)找出伪币
给你一个装有16个硬币的袋子。
16个硬币中有一个是伪造的,并且那个伪造的硬币比真的硬币要轻一些。
你的任务是找出这个伪造的硬币。
[问题简析]为了帮助你完成这一任务,将提供一台可用来比较两组硬币重量的仪器,利用这台仪器,可以知道两组硬币的重量是否相同。
比较硬币1与硬币2的重量。
假如硬币1比硬币2轻,则硬币1是伪造的;假如硬币2比硬币1轻,则硬币2是伪造的。
这样就完成了任务。
假如两硬币重量相等,则比较硬币3和硬币4。
同样,假如有一个硬币轻一些,则寻找伪币的任务完成。
假如两硬币重量相等,则继续比较硬币5和硬币6。
按照这种方式,可以最多通过8次比较来判断伪币的存在并找出这一伪币。
另外一种方法就是利用分而治之方法。
假如把16硬币的例子看成一个大的问题。
第一步,把这一问题分成两个小问题。
随机选择8个硬币作为第一组称为A组,剩下的8个硬币作为第二组称为B组。
这样,就把16个硬币的问题分成两个8硬币的问题来解决。
第二步,判断A和B组中是否有伪币。
可以利用仪器来比较A组硬币和B组硬币的重量。
假如两组硬币重量相等,则可以判断伪币不存在。
假如两组硬币重量不相等,则存在伪币,并且可以判断它位于较轻的那一组硬币中。
最后,在第三步中,用第二步的结果得出原先16个硬币问题的答案。
若仅仅判断硬币是否存在,则第三步非常简单。
无论A组还是B组中有伪币,都可以推断这16个硬币中存在伪币。
因此,仅仅通过一次重量的比较,就可以判断伪币是否存在。
现在假设需要识别出这一伪币。
把两个或三个硬币的情况作为不可再分的小问题。
注意如果只有一个硬币,那么不能判断出它是否就是伪币。
在一个小问题中,通过将一个硬币分别与其他两个硬币比较,最多比较两次就可以找到伪币。
这样,16硬币的问题就被分为两个8硬币(A组和B组)的问题。
通过比较这两组硬币的重量,可以判断伪币是否存在。
如果没有伪币,则算法终止。
否则,继续划分这两组硬币来寻找伪币。
假设B是轻的那一组,因此再把它分成两组,每组有4个硬币。
称其中一组为B1,另一组为B2。
比较这两组,肯定有一组轻一些。
如果B1轻,则伪币在B1中,再将B1又分成两组,每组有两个硬币,称其中一组为B1a,另一组为B1b。
比较这两组,可以得到一个较轻的组。
由于这个组只有两个硬币,因此不必再细分。
比较组中两个硬币的重量,可以立即知道哪一个硬币轻一些。
较轻的硬币就是所要找的伪币。
(2)二分搜索(折半查找)
算法思想:
将数列按有序化(递增或递减)排列,查找过程中采用跳跃式方式查找,即先以有序数列的中点位置为比较对象,如果要找的元素值小于该中点元素,则将待查序列缩小为左半部分,否则为右半部分。
通过一次比较,将查找区间缩小一半。
折半查找是一种高效的查找方法。
它可以明显减少比较次数,提高查找效率。
但是,折半查找的先决条件是查找表中的数据元素必须有序。
算法步骤描述:
step1首先确定整个查找区间的中间位置:
mid=(left+right)/2
step2用待查关键字值与中间位置的关键字值进行比较;
●若相等,则查找成功
●若大于,则在后(右)半个区域继续进行折半查找
●若小于,则在前(左)半个区域继续进行折半查找
Step3对确定的缩小区域再按折半公式,重复上述步骤。
最后,得到结果:
要么查找成功,要么查找失败。
折半查找的存储结构采用一维数组存放。
折半查找算法举例
对给定数列(有序){3,5,11,17,21,23,28,30,32,50},按折半查找算法,查找关键字值为30的数据元素。
折半查找的算法讨论:
优点:
ASL(平均查找长度)≤log2n,即每经过一次比较,查找范围就缩小一半。
经log2n次计较就可以完成查找过程。
缺点:
因要求有序,所以要求查找数列必须有序,而对所有数据元素按大小排序是非常费时的操作。
例如:
由于数据按升序排列,故用折半查找最快捷。
programbinsearch;
constmax=10;
varnum:
array[1..max]ofinteger;
i,n:
integer;
proceduresearch(x,a,b:
integer);
varmid:
integer;
begin
ifa=bthen
ifx=num[a]thenwriteln('Found:
',a)elsewriteln('Numbernotfound')
elsebegin
mid:
=(a+b)div2;
ifx>num[mid]thensearch(x,mid,b);
ifxifx=num[mid]thenwriteln('Found:
',mid);
end;
end;
begin
write('Pleaseinput10numbersinorder:
');
fori:
=1tomaxdoread(num);
write('Pleaseinputthenumbertosearch:
');
readln(n);
search(n,1,max);
end.
非递归算法:
PROCEDUREBINASEARCH(A,N,K);
BEGINS:
=1;
T:
=N;
I:
=0;
WHILES<=TDO
BEGIN
M:
=(S+T)DIV2;
IFA[M]<>KTHENIFA[M]>KTHENT:
=M-1ELSES:
=M+1
ELSEBEGINI:
=M;
S:
=T+1;
END
END;
IFI>0THENWRITELN(I,A[I])ELSEWRITELN(‘NOFOUND!
’)
END;
(3)循环赛日程表
有n个编号为1到n的运动员参加某项运动的单循环比赛,即每个运动员要和所有其他运动员进行一次比赛。
试为这n个运动员安排一个比赛日程,使得每个运动员每天只进行一场比赛,且整个比赛在n-1天内结束。
输入运动员人数n(n<=10000),输出一个n阶方阵A[1..N,0..N-1],当J>0时,A[1,J]表示第1名运动员在第J天的比赛对手。
【分析提示】由于N个运动员要进行单循环比赛,且在N-1天内要结束全部比赛,经过分析,当且仅当N为2的整次幂时,问题才有解,当然解是不惟一的。
这样可以将运动员分成两组:
1,2,…,N/2和N/2+1,N/2+2,…,N。
给第一组运动员安排一个比赛日程,得到一个N/2阶的方阵A1;同时给第二组的运动员安排一个比赛日程,同样会得到一个N/2阶的一个方阵A2。
考虑到比赛的性质,设定第1个运动员在某一天的比赛对手为第K个运动员,则第K个运动员在同一天的比赛对手必然是第1个运动员,即若有A[1,J]=K,则A[1,K]=I。
因此原问题的解(一个N阶方阵)可以由分解后的两个子问题的解,按下图所示形式合并起来。
同时每一个子问题又可以按照上述的二分法分解下去,直至每个组中仅有2个运动员时为止。
procedurearrangment(K,N:
integer);begin
ifn=2then{处理只有2名运动员的情况,递归终止条件}
begin
A[K,0]:
=K;A[K,1]:
=K+I;
A[K+I,0]:
=K+I;A[K+I,1]:
=K;
end
else
begin
arrangment(K,Ndiv2);
arrangment(K+Ndiv2,Ndiv2);{递归分解原问题与求解子问题}
forI:
=KtoK+(Ndiv2)-1do{合并子问题,构造原问题的解A[I,J]}
forJ:
=(Ndiv2)toN-1do
A[I,J]:
=A[I+(Ndiv2),J-(Ndiv2)];
forI:
=K+(Ndiv2)toK+N-1do
forJ:
=(Ndiv2)toN-1do
A[I,j]:
=A[I-(Ndiv2),J-(Ndiv2)];
end;end;
(4)在n个元素中找出最大元素和最小元素
我们可以把这n个元素放在一个数组中,用直接比较法求出。
算法如下:
BEGIN
MIN:
=A[1]:
MAX:
=A[1];
FORI:
=2TONDO
BEGIN
IFA[I]>MAXTHENMAX:
=A[I];
IFA[I]=A[I];
END.
上面这个算法需比较2(N-1)次,即时间复杂度是2(N-1)。
能否找到更好的算法呢?
我们用分治策略来讨论。
我们把n个元素分成
A1={A[1],...,A[int(n/2)]}和A2={A[INT(N/2)+1],...,A[N]}两组,分别求这两组的最大值和最小值,然后分别将这两组的最大值和最小值相比较,求出全部元素的最大值和最小值。
如果A1和A2中的元素多于两个,则再用上述方法各分为两个子集。
直至子集中元素至多两个元素为止。
例如有下面一组元素:
-13,13,9,-5,7,23,0,15。
用分治策略比较的过程如下:
图中每个方框中,左边是最小值,右边是最大值。
从图中看出,用这种方法一共比较了10次,比直接比较法的14次减少4次,即约减少了1/3。
proceduremaxmin(i,j,max,min);
BEGIN
CASEJ-IOF
0:
MAX:
=A[I];MIN:
=A[I];
1:
IFA[I]=A[I];MAX:
A[J];
ELSEMAX:
=A[I];MIN:
=A[J];
ELSEMID:
=(I+J)DIV2
MAXMIN(I,MID,MAX1,MIN1);
MAXMIN(MID+1,J,MAX2,MIN2);
MAX:
=MAX(MAX1,MAX2);
MIN:
=MIN(MINI,MIN2);
END;
(5)归并排序(又称合并排序,MERGESORT)
归并排序是一类不同的排序方法,合并的含义就是将两个或两个以上的有序数据序列合并成一个新的有序数据序列。
它的基本思想就是假设数组A有N个元素,那么可以看成数组A是又N个有序的子序列组成,每个子序列的长度为1,然后再两两合并,得到了一个N/2个长度为2或1的有序子序列,再两两合并,如此重复,值得得到一个长度为N的有序数据序列为止,这种排序方法称为2—路合并排序。
例如数组A有7个数据,分别是:
49386597761327,那么采用归并排序算法的操作过程所示:
初始值[49][38][65][97][76][13][27]
看成由长度为1的7个子序列组成
第一次合并之后[3849][6597][1376][27]
看成由长度为1或2的4个子序列组成
第二次合并之后[38496597][132776]
看成由长度为4或3的2个子序列组成
第三次合并之后[13273849657697]
归并算法的核心操作就是将一维数组中前后相邻的两个两个有序序列合并成一个有序序列。
归并算法也可以采用递归算法来实现,形式上较为简单同,但实用性很差。
合并算法的合并次数是一个非常重要的量,根据计算当数组中有3到4个元素时,合并次数是2次,当有5到8个元素时,合并次数是3次,当有9到16个元素时,合并次数是4次,按照这一规律,当有N个子序列时可以推断出合并的次数是X2>=N,符合此条件的最小那个X)。
其时间复杂度为:
O(nlogn).所需辅助存储空间为:
O(n)。
参考程序:
{a为序列表,tmp为辅助数组}
proceduremerge(vara:
listtype;p,q,r:
integer);
{将已排序好的子序列a[p..q]与a[q+1..r]合并为有序的tmp[p..r]}
varI,j,t:
integer;
tmp:
listtype;
begin
t:
=p;i:
=p;j:
=q+1;{t为tmp指针,I,j分别为左右子序列的指针}
while(t<=r)dobegin
if(i<=q){左序列有剩余}and((j>r)or(a[i]<=a[j])){满足取左边序列当前元素的要求}
thenbegin
tmp[t]:
=a[i];inc(i);
end
elsebegin
tmp[t]:
=a[j];inc(j);
end;
inc(t);
end;
fori:
=ptordoa[i]:
=tmp[i];
end;{merge}
proceduremerge_sort(vara:
listtype;p,r:
integer);{合并排序a[p..r]}
varq:
integer;
begin
ifp<>rthenbegin
q:
=(p+r-1)div2;
merge_sort(a,p,q);
merge_sort(a,q+1,r);
merge(a,p,q,r);
end;
end;
{main}
begin
merge_sort(a,1,n);
end.
(6)快速排序
【基本思想】
快速排序对冒泡排序的一种改进。
它的基本思想是:
通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一部分的所有数据都要小,然后再按此方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
【算法过程】
设要排序的数组是A[1]……A[N],首先任意选取一个数据(通常选用第一个数据)作为关键数据,然后将所有比它小的数都放到它前面,所有比它大的数都放到它后面,这个过程称为一躺快速排序。
一躺快速排序的算法是:
1)、设置两个变量I、J,排序开始的时候I:
=1,J:
=N;
2)以第一个数组元素作为关键数据,赋值给X,即X:
=A[1];
3)、从J开始向前搜索,即由后开始向前搜索(J:
=J-1),找到第一个小于X的值,两者交换;
4)、从I开始向后搜索,即由前开始向后搜索(I:
=I+1),找到第一个大于X的值,两者交换;
5)、重复第3、4步,直到I=J;
例如:
待排序的数组A的值分别是:
(初始关键数据X:
=49)
A[1]A[2]A[3]A[4]A[5]A[6]A[7]:
49386597761327
进行第一次交换后:
27386597761349
(按照算法的第三步从后面开始找)
进行第二次交换后:
27384997761365
(按照算法的第四步从前面开始找>X的值,65>49,两者交换,此时I:
=3)
进行第三次交换后:
27381397764965
(按照算法的第五步将又一次执行算法的第三步从后开始找
进行第四次交换后:
27381349769765
(按照算法的第四步从前面开始找大于X的值,97>49,两者交换,此时J:
=4)
此时再执行第三步的时候就发现I=J,从而结束一躺快速排序,那么经过一躺快速排序之后的结果是:
27381349769765,即所以大于49的数全部在49的后面,所以小于49的数全部在49的前面。
快速排序就是递归调用此过程——在以49为中点分割这个数据序列,分别对前面一部分和后面一部分进行类似的快速排序,从而完成全部数据序列的快速排序,最后把此数据序列变成一个有序的序列,根据这种思想对于上述数组A的快速排序的全过程如图6所示:
初始状态{49386597761327}
进行一次快速排序之后划分为{273813}49{769765}
分别对前后两部分进行快速排序{273813}经第三步和第四步交换后变成{132738}完成排序
{769765}经第三步和第四步交换后变成{657697}完成排序
快速排序程序一:
procedurequicksort(varb:
arr;s,t:
integer);
vari,j,x,t1:
integer;
begin
i:
=s;j:
=t;x:
=b[i];
repeat
while(b[j]>=x)and(j>i)doj:
=j-1;
ifj>ithenbegint1:
=b[i];b[i]:
=b[j];b[j]:
=t1;end;
while(b[i]<=x)and(i=i+1;
ifi=b[j];b[j]:
=b[i];b[i]:
=t1;end
untili=j;
b[i]:
=x;
i:
=i+1;j:
=j-1;
ifsifiend;
快速排序二:
这个例程中假设待排序的数组是全局变量a[]。
procedureqsort(s,t:
longint);
var
i,j,x,temp:
longint;
begin
i:
=s;j:
=t;x:
=a[(i+j)div2];//x是中间项值
repeat
whilea[i]whilea[j]>xdodec(j);{找右边比他小的}
ifi<=jthen{交换}//i=j不能省略
begin
temp:
=a[i];a[i]:
=a[j];a[j]:
=temp;
inc(i);dec(j);
end;
untili>j;
ifs ifiend;
{注意j小于i,这种快速排序不同于标准快排,它不是将数列分成以主元为参照的两组子数列(左小右大),而是将数列分成两部分,左侧数字<右侧数字(参照值是中间值)
(7)取余计算
源程序名mod.?
?
?
(pas,c,cpp)
可执行文件名mod.exe
输入文件名mod.in
输出文件名mod.out
【问题描述】
输入b,p,k的值,求bpmodk的值。
其中b,p,k*k为长整型数。
【样例】
mod.inmod.out
21092^10mod9=7
【知识准备】
进制转换的思想、二分法。
【算法分析】
本题主要的难点在于数据规模很大(b,p都是长整型数),对于bp显然不能死算,那样的话时间复杂度和编程复杂度都很大。
下面先介绍一个原理:
a*bmodk=(amodk)*(bmodk)modk。
显然有了这个原理,就可以把较大的幂分解成较小的,因而免去高精度计算等复杂过程。
那么怎样分解最有效呢?
显然对于任何一个自然数P,有p=2*pdiv2+pmod2,如19=2*19div2十19mod2=2*9+1,利用上述原理就可以把b的19次方除以k的余数转换为求b的9次方除以k的余数,即b19=b2*9+1=b*b9*b9,再进一步分解下去就不难求得整个问题的解。
这是一个典型的分治问题,具体实现的时候是用递推的方法来处理的,如p=19,有19=2*9+1,9=2*4+1,4=2*2+0,2=2*1+0,1=2*0+1,反过来,我们可以从0出发,通过乘以2再加上一个0或1而推出1,2,4,9,19,这样就逐步得到了原来的指数,进而递推出以b为底,依次以这些数为指数的自然数除以k的余数。
不难看出这里每一次乘以2后要加的数就是19对应的二进制数的各位数字,即1,0,0,1,1,而19=(10011)2,求解的过程也就是将二进制数还原为十进制数的过程。
(8)麦森数
源程序名mason.?
?
?
(pas,c,cpp)
可执行文件名mason.exe
输入文件名mason.in
输出文件名mason.out
【问题描述】
形如2p-1的素数称为麦森数,这时P一定也是个素数。
但反过来不一定,即如果P是个素数,2p-1不一定也是素数。
到1998年底,人们已找到了37个麦森数。
最大的一个是P=3021377,它有909526位。
麦森数有许多重要应用,它与完全数密切相关。
任务:
从文件中输入P(1000
【输入】
文件中只包含一个整数P(1000
【输出】
第一行:
十进制高精度数2p-1的位数;
第2~11行:
十进制高精度数2p-1的最后500位数字(每行输出50位,共输出10行,不足500位时高位补0);
不必验证2p-1与P是否为素数。
【样例】
mason.in
1279
mason.out
386
00000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000000000
00000000000000104079321946643990819252403273640855
3861526224726670480531911235040360805967336029