穷举法
![穷举法](https://img.360docs.net/imgac/0569yqb8p8oqeuque8zo-c1.webp)
![穷举法](https://img.360docs.net/imgac/0569yqb8p8oqeuque8zo-72.webp)
第16章 穷举算法与实验
穷举方法是基于计算机特点而进行解题的思维方法。一般是在一时找不出解决问题的更好途径(即从数学上找不到求解的公式或规则)时,可以根据问题中的的部分条件(约束条件)将所有可能解的情况列举出来,然后通过一一验证是否符合整个问题的求解要求,而得到问题的解。这样解决问题的方法我们称之为穷举算法。穷举算法特点是算法简单,但运行时所花费的时间量大。因此,我们在用穷举方法解决问题时,应尽可能将明显的不符合条件的情况排除在外,以尽快取得问题的解。虽然穷举法效率并不高,但是适应一些没有明显规律可循的问题的解决。
因为穷举算法就是从所有可能的情况中搜索正确的答案,所以一般可按如下步骤: 第1步: 对于一种可能的情况,列举出来并计算其结果;
第2步:判断结果是否满足要求,如果不满足则执行第1步来搜索下一个可能的情况,如果满足要求,则表示寻找到一个正确的答案,执行下一步操作,如寻找其他正确(合适)的答案或者中断循环。
16.1三角形数问题
16.1.1 问题描述
将 ,F ,E ,D ,C ,B ,A 这六个变量排成如图所示的三角形,这六个变量分别取[1,6]上的
整数,且均不相同。求使三角形三条边上的变量之和相等的全部解。如图就是一个解。 A 6
B C 3 1
D F 2 4
E 5
16.1.2 问题分析
程序引入变量123456,,,,,i i i i i i ,代表,F ,E ,D ,C ,B ,A 并让它们分别顺序取1至6的正整数,在它们互不相同的前提条件下,测试由它们排成的如图所示的三角形三条边上的变量之和是否相等,如相等即为一种满足要求的排列,把它们输出。当这些变量取尽所有的组合后,程序就可得到全部可能的解。细节见下面的程序。 【程序1】
%穷举法解三角形数 for i1=1:6 for i2=1:6 if i1==i2 continue;
end
for i3=1:6
if i1==i3 || i2==i3
continue;
end
for i4=1:6
if i1==i4 || i2==i4 || i3==i4
continue;
end
for i5=1:6
if i1==i5 || i2==i5 || i3==i5 || i4==i5
continue;
end
for i6=1:6
if i1==i6 || i2==i6 || i3==i6 || i4==i6 || i5==i6
continue;
end
if i1+i2+i4==i1+i3+i6 && i1+i2+i4==i4+i5+i6
fprintf ('%6d\n',i1) ;
fprintf ('%4d%4d\n',i2,i3) ;
fprintf ('%2d%4d%4d\n\n',i4,i5,i6) ;
end
end
end
end
end
end
End
16.1.3 问题讨论
按穷举法编写的程序通常不能适应变化的情况。如问题改成有9 个变量排成三角形,每条边有4 个变量的情况,程序的循环重数就要相应改变,而且程序的复杂程度大大增加。
对一组数穷尽所有排列,还有更直接的方法。将一个排列看作一个长整数,则所有排列对应着一组整数。将这组整数按从小到大的顺序排列排成一个整数,从对应最小的整数开始。按数列的递增顺序逐一列举每个排列对应的每个整数,这能更有效地完成排列的穷举。从一个排列找出对应数列的下一个排列可在当前排列的基础上作部分调整来实现。倘若当前排列为1,2,4,6,5,3,并令其对应的长整数为124653。要寻找比长整数124653 更大的排列,可从该排列的最后一个数字顺序向前逐位考察,当发现排列中的某个数字比它前一个数字大时,如本例中的6 比它的前一位数字4 大,这说明还有对应更大整数的排列。但为了顺序从小到大列举出所有的排列,不能立即调整得太大,如本例中将数字6 与数字4 交换得到的排列126453 就不是排列124653 的下一个排列。为了得到排列124653 的下一个排列,应从已经考察过的那部分数字中选出比数字大,但又是它们中最小的那一个数字,比如数字5,与数字4 交换。该数字也是从后向前考察过程中第一个比4 大的数字。5 与4 交换后,得到排
列125643。在前面数字1,2,5 固定的情况下,还应选择对应最小整数的那个排列,为此 还需将后面那部分数字的排列顺序颠倒,如将数字6,4,3 的排列顺序颠倒,得到排列1, 2,5,3,4,6,这才是排列1,2,4,6,5,3 的下一个排列。
按以上想法编写的程序如下。 【改进的程序1’】
%穷举法 解三角形的其他方法 a=1:6;%构建序列数组
while ~all(a==6:-1:1) %判断是否达到最大的数列,即654321
if a(1)+a(2)+a(4)==a(1)+a(3)+a(6) && a(1)+a(2)+a(4)==a(4)+a(5)+a(6) fprintf ('%6d\n',a(1)) ;
fprintf ('%4d%4d\n',a(2),a(3)) ;
fprintf ('%2d%4d%4d\n\n',a(4),a(5),a(6)) ; end
for i1=6:-1:2
if all(a(i1)-a(i1-1)>0) %如果遇到比较大得数则开始替换 zc=7;
for j1=i1:6 %寻找一个最小的但大于比较数的且在考察区域的值进行交换 if a(j1)>a(i1-1) && a(j1) a(j2)=a(i1-1); %替换 a(i1-1)=zc; %替换 a(i1:end)=sort(a(i1:end)); %重排序,交换后的考察区域按降序排列 break; end end end 从上述问题解决的方法中,最重要的因素就是确定某种方法来确定所有的候选解。下面 再用一个示例来加以说明。 16.2 背包问题 16.2.1问题描述 有不同价值、不同重量的物品n 件,求从这n 件物品中选取一部分物品的选 择方案,使选中物品的总重量不超过指定的限制重量,但选中物品的价值之和最大。 16.2.2 问题分析 设n 个物品的重量和价值分别存储于数组] w[和] v[中,限制重量为tw 。考虑一个n 元组()110,...,,-n x x x ,其中0 i x =表示第i 个物品没有选取,而1i x =则表示第i 个物品被选 取。显然这个n 元组等价于一个选择方案。用枚举法解决背包问题,需要枚举所有的选取方案,而根据上述方法,我们只要枚举所有的n 元组,就可以得到问题的解。 显然,每个分量取值为0 或 1的n 元组的个数共为n 2个。而每个n 元组其实对应了一个长度为n 的二进制数,且这些二进制数的取值范围为1-20n →。因此,如果把 1-20n →分别转化为相应的二进制数,则可以得到我们所需要的n 2个n 元组。 【算法2】 %背包算法,这个算法用函数实现 function [value pai]=beibao(w,v,tw,n) b=zeros(1,n);%用于存储二进制数 value=0;%初始化 for i1=0:2^n-1 for j1=1:n b(j1)=mod(i1,2); i1=floor(i1/2); end if sum(w.*b)<=tw && sum(b.*v)>value%满足重量限制 value=sum(b.*v);pai=b; end end end 16.3 边长为整数、周长为定数的三角形个数。 16.3.1问题描述 输入绳子的长度n ,将该绳子分成三段,每段的长度为正整数,输出由该三段绳子组成的三角形个数。 16.3.2算法分析 没有公式直接求出三角形的个数,所以程序只能采用穷举法,一一验证范围内的数是否能构成三角形,若是则累计。假设三角形的三个边分别是123,,i i i 。并设32i i >(防止重复的现象,如2,2,3与3,2,2重复其实是一个三角形),且123,,i i i 均为大于1的整数,312i n i i =--。由三角形三边的性质知:123i i i +>,132i i i +>,231i i i +>。当满足以上条件均满足时,可以判断这时的123,,i i i 可以构成一个三角形,计数加一。根据以上编写的程序如下: 【程序3】 %分隔绳子的算法 n=input('the length of string is '); num=0; for i1=1:n-2 %三个边长的穷举 for i2=i1:n-1-i1 i3=n-i1-i2; if i1+i2>i3 && i1+i3>i2 && i2+i3>i1 && i3>=i2 num=num+1;%计数加一 fprintf('the num is %d %d %d\n',i1,i2,i3); end end end fprintf('长度为 %d 的绳子分成三段可以组成 %d 个三角形\n',n,num); 16.4 一元三次方程的根 16.4.1 问题描述: 有形如:3 2 a*x *x *x 0b c d +++= 这样的一个一元三次方程。给出该方程中各项的系数(d ,c ,b ,a 均为实数),并约定该方程存在三个不同实根(根的范围在-100至100之间),且根与根之差的绝对值>=1。要求由小到大依次在同一行输出这三个实根(根与根之间留有空格),并精确到小数点后2位。 提示:记方程()0f x =,若存在2个数1x 和2x ,且12x x <,()()120f x f x *<,则在 ()12,x x 之间一定有一个根。 样例: 输入:1 -5 -4 20 输出:-2.00 2.00 5.00 16.4.2 问题分析: 首先我们使用一个循环因子x ,x 的范围从-100到100,每次增加0.01,这样我们可以把x 当作这个方程可能的根进行测试,记方程()0f x =,若存在这样的x , ()()0.010f x f x *-<,则x 是方程的一个根。先求出这个的第一个根,然后用同样的方 法,计算出另外的根,并且使其满足根与根之差的绝对值>=1,最后按顺序输出结果。根据以上思想编写的程序如下: 【程序4】: %ax3+bx2+cx+d=0 这样的一个一元三次方程的解的情况 function x=sol(a,b,c,d) % a=1 ; b=-5 ; c= -4 ; d= 20;%测试用的数据 i=1; x=zeros(1,3); for i1=-100:0.01:100 if (power(i1, 3)*a + power(i1, 2)*b + c*i1 +d)*(power(i1+0.01, 3)*a + ... power(i1+0.01, 2)*b + c*(i1+0.01) +d)<0 if i==2 && abs(x(i)-x(i-1))>=1 || i==3 && abs(x(i)-x(i-2))>=1 && ... abs(x(i)-x(i-1))>=1 || i==1 x(i)=i1+0.01; end i=i+1; end end fprintf('方程的解为 %.2f %.2f %.2f \n',x); end 16.5 学校名次 16.5.1问题描述 有A ,B ,C ,D ,E ,所学校。在一次检查评比中,已知E 校肯定不是第2名或第3名,他们互相进行推测。A 校有人说,E 校一定是第1名;B 校有人说,我校可能是第2名;C 校有人说,A 校最差;D 校有人说,C 校不是最好的;E 校有人说,D 校会获第1名。结果第1名和第2名学校的人猜对了。编程指出这5所学校的名次。 16.5.2 问题分析 本题是一个逻辑判断题,一般的逻辑判断题都可以采用穷举法进行解决。此题的难点在于确定判断条件。我们设立逻辑变量pai 来描述这一条件,主要有两个条件:“E 校肯定不是第2名或第3名”与“只有第1名和第2名的学校的人猜对”,后一条件要判断:1)学校获得的排名情况是怎样的?2)取得第一名和第二名的学校的预测结果与事实是否相符合? 我们对5所学校所得名次的所有可能情况进行穷举。在每种情况中,为了防止不同学校取相同的名次,设立了逻辑数组pai ,[]1,pai J K =表示J 校取第K 名,并使[]1,:pai 中没有相同的数,同时,[]pai 2:6,j 表示J 校的预测情况,[],pai I J K =表示在J 校的预测中I 校为第K 名,若0K =,则表示没有对其预测。然后我们对各校的可能取得的名次进行穷举,并取排名前二位的预测情况进行分析对比,若发现E 校是第2名或第3名或者第一、二名的预测与当前的情况不相符,则认为当前这种情况是不合理的,继续下一个循环,直到满足所有条件后输出结果。根据以上的思想编写的程序如下: 【程序5】 %学校排名问题 pai=zeros(6,5);% pai 的第一行用于存放排名,第二到六行用于存储假设方案。0表示无假设,1,2。。% 5表示假设排名,6表示不为第一名,第1.2..5列分别表示A ,B 。。E 的假设 pai(6,1)=1;% A 假设E 是第一 pai(3,2)=2;% B 假设B 是第二 pai(2,3)=5;% C 假设A 是最后 pai(4,4)=6;% D 假设A 不是第一 pai(5,5)=1;% E 假设D 是第一 for a=1:5 for b=1:5 for c=1:5 for d=1:5 for e=1:5 if a==b || b==c || a==c || a==d || b==d || c==d || a==e || b==e || c==e ... || d==e || e==2 || e==3 continue; end pai(1,:)=[a,b,c,d,e]; [pa x]=sort(pai(1,:));%排序 yuce=[pai(2:end,x(1)),pai(2:end,x(2))];%提取第一,第二名的预测信息 for i1=1:5 if yuce(i1,1)~=0 && yuce(i1,1)~=pai(1,i1) || yuce(3,1)==6 &&... pai(1,3)==1|| yuce(i1,2)~=0 && yuce(i1,2)~=pai(1,i1) || yuce(3,2)==6 && pai(1,3)==1 %第一,二名预测不准确则退出循环 判断C 不为第一名可C 又是第一名时,退出循环 break; end if i1==5 fprintf('a=%d,b=%d,c=%d,d=%d,e=%d\n',a,b,c,d,e); end end end end end end end 运行结果: a=5 b=2 c=1 d=3 e=4 16.6 阿姆斯特朗数。 16.6.1问题描述 编一个程序找出所有的三位数到七位数中的阿姆斯特朗数。 阿姆斯特朗数也叫水仙花数,它的定义如下:若一个n 位自然数的各位数字的n 次方之和等于它本身,则称这个自然数为阿姆斯特朗数。例如5)*5*53*3*31*1*1153(153++=是一个三位数的阿姆斯特朗数,8208则是一个四位数的阿姆斯特朗数。 16.6.2问题分析 列举所有的情况,并且用除10求余的方法提取一个n 位数的每一位数的数值,若各位数字的n 次方之和等于它本身,则找到一个阿姆斯塔朗数,打印出该结果,否则则跳过这次 循环。 【程序6】 %寻找阿姆斯特朗数 for cur=100:10000000 %从三位数到七位数,cur 存储当前数 dig=zeros(1,8);%digit 存储某一个数每一位的值 cur1=cur; for i1=1:8 dig(i1)=mod(cur,10);%提取第i1位 cur=floor(cur/10); if dig(i1)==0 c=c+1; end if cur==0 break; end end if sum(power(dig(1:i1),i1))==cur1 fprintf(' %07d \n',cur1); end end 运行结果: 0000153 0000370 0000371 0000407 0001634 0008208 0009474 0054748 0092727 0093084 0548834 1741725 4210818 9800817 9926315 ... 16.7 邮票面值。 16.7.1 问题描述: 邮局发行一套票面有四种不同值的邮票,如果每封信所贴邮票张数不超过三枚,存在整数R,使得用不超过三枚的邮票,可以贴出连续的整数1、2、3,... ,R来,找出这四种面值数,使得R值最大。 16.7.2 问题分析 可能的解是有限的,可用穷举法。 设四种邮票的面值分别为: D , C , B ,A ,可以设D C B A <<<,且必有1A =,再分别给D C,B,足够大的选择区间2A *3B A +<<、2B *3C B +<<、2C *3D C +<<。每一种情况下,带入一个值R 表示能贴到的最大整数,每一种情况刚开始的时候1R =。取 D , C , B ,A 的数量分别为1234,,,i i i i ,若存在能够在三张之内凑成R ,且不多于三张(1234***i a i b i c i d r *+++=并且12343i i i i +++≤),则认为这种情况下可以贴到整数R ,将R 加1后继续考察,直到1R +时不再满足判定条件,若此时凑成R 的邮票种类中有D 的构成(4i 不 等于0),则打印出这种邮票的组合以及能贴到的最大的R 值。 【程序7】 %邮票面值问题 a=1;%若满足1,则a只能为一 ji=0;j=0;%状态量 for b=2:3*a+1 %给b一个足够大的值选择区间 for c=b+1:3*b+1%给c一个足够大的值选择区间 for d=c+1:3*c+1%给c一个足够大的值选择区间 for r=1:3*d %简单推理即可发现最大面值R不会超过3*d ji=1; for i1=0:3 for i2=0:3-i1 for i3=0:3-i1-i2 for i4=0:3-i1-i2-i3 if i1*a+i2*b+i3*c+i4*d==r ji=0;j=i4; end end end end end if ji if j fprintf('%2d %2d %2d %2d,%2d\n',a,b,c,d,r-1); end break; end end end end end 程序运行后,部分输出结果如下: 1 2 3 5,13 1 2 3 6,15 1 2 3 7,17 1 2 3 8,14 1 2 3 9,15 ..... 1 4 6 14,16 1 4 6 15,23 1 4 7 9,23 16.8方格填数 16.8.1问题描述 如图所示的8个格子中放入1~8八个数字,使得相邻的和对角线的数字之差不为1。编程找出所有放法。 b1 b2 b3 b4 b5 b6 b7 b8 16.8.2问题分析 可能的解是有限的,可用穷举法。 构建一个数列b ,其中包含八个元素128,b b b ,使他们分别取1,28 ,且相互之间不相等,对于每一种情况,判断相邻的和对角线的数字之差是否为1,若都不是则打印出结果,否则跳过此次循环。 【程序8】 %方格填数 b=1:8; while ~all(b==8:-1:1) if all(abs(b(2:4)-b(1))-1) &&all(abs(b(1:7)-b(3))-1) && all(abs(b(2:8)-b(6))-1) && all(abs(b(5:7)-b(8))-1) if all(abs([b(3) b(5) b(6)]-b(2))-1) && all(abs([b(3) b(7) b(6)]-b(4))-1) if all(abs([b(7) b(5)]-b(8))-1) fprintf('%4d\n%2d%2d%2d\n%2d%2d%2d\n%4d\n\n',b); end end end for i1=8:-1:2 if all(b(i1)-b(i1-1)>0) zc=9; for j1=i1:8 if b(j1)>b(i1-1) && b(j1) j2=j1; end end b(j2)=b(i1-1); b(i1-1)=zc; b(i1:end)=sort(b(i1:end)); break; end end End 16.9:4皇后问题 16.9.1问题描述 在4×4的棋盘上安置4个皇后,要求任意两个皇后不在同一行、不在同一列、不在同一对角线上,输出所有的方案。 16.9.2问题分析 1) 本题是一个搜索问题,搜索范围 4*4,找出符合条件的方案; 2)方案必须满足的条件:任意两个不在同一行、同一列和同一对角线。 首先假设有一个虚拟的4*4的棋盘,由于任意两个皇后不在同一行,所以每一列均有一个皇后,假设每个皇后在该列的第128,i i i 行,对于128,i i i 进行遍历,已知它们的范围是1到4,且对于每一种情况如果有任意两个皇后不在同一行和同一对角线。那么我们认为满足条件,则输出这种情况。 提示:当两个皇后处于同一行或者同一对角线时,他们所在的行数的差绝对值等于列数的差的绝对值或者0. 可用穷举算法的代码实现方法 【程序9】 %皇后问题 for i1=1:4 %i1.。表示皇后的位置 for i2=1:4 for i3=1:4 for i4=1:4 hh=zeros(4,4);%用于模拟棋盘 hh(1,i1)=1; % 1表示此处有皇后 由于分列,所以不再同一列 hh(2,i2)=1; hh(3,i3)=1; hh(4,i4)=1; if i1==i2 || i1==i3 || i1==i4 || i2==i3 || i2==i4 || i3==i4 % 判断是否在同一行 continue; end if abs(i1-i2)==1 || abs(i1-i3)==2 || abs(i1-i4)==3 || abs(i2-i3)==1 || abs(i2-i4)==2 ... || abs(i3-i4)==1 % 判断是否在一条对角线上 continue; end disp(hh);%打印棋盘,1为皇后 end end end End 16.10巧妙填数 16.10.1问题描述: 将1~9这九个数字填入九个空格中。每一横行的三个数字组成一个三位数。如果要使第二行的三位数是第一行的两倍,第三行的三位数是第一行的三倍,应怎样填数。如图所示。 1 9 2 3 8 4 5 7 6 16.10.2问题分析 可能的解是有限的,可用穷举法。 建立一个3*3的数组,每个元素的取值均在1到9之间,且互不相同,若满足每一横行的三个数字组成一个三位数。如果要使第二行的三位数是第一行的两倍,第三行的三位数是第一行的三倍,则输出结果。 【程序10】 a=1:9;%构建序列数组 while ~all(a==9:-1:1) %判断是否达到最大的数列,即987654321 if sum(2*a(1:3).*[100 10 1])==sum(a(4:6).*[100 10 1]) && sum(3*a(1:3).*[100 10 1])… = =sum(a(7:9).*[100 10 1] ) disp(a); end for i1=9:-1:2 %此循环用于实现穷举算法,详细见本章改编程序1’ if all(a(i1)-a(i1-1)>0) zc=10; for j1=i1:9