穷举法详细
- 1、下载文档前请自行甄别文档内容的完整性,平台不提供额外的编辑、内容补充、找答案等附加服务。
- 2、"仅部分预览"的文档,不可在线预览部分如存在完整性等问题,可反馈申请退款(可完整预览的文档不适用该条件!)。
- 3、如文档侵犯您的权益,请联系客服反馈,我们会尽快为您处理(人工客服工作时间:9:00-18:30)。
第三讲穷举法
一、穷举法的基本概念
穷举方法是基于计算机特点而进行解题的思维方法。
一般是在一时找不出解决问题的更好途径(即从数学上找不到求解的公式或规则)时,可以根据问题中的的部分条件(约束条件)将所有可能解的情况列举出来,然后通过一一验证是否符合整个问题的求解要求,而得到问题的解。
这样解决问题的方法我们称之为穷举算法。
穷举算法特点是算法简单,但运行时所花费的时间量大。
有些问题所列举出来的情况数目会大得惊人,就是用高速的电子计算机运行,其等待运行结果的时间也将使人无法忍受。
因此,我们在用穷举方法解决问题时,应尽可能将明显的不符合条件的情况排除在外,以尽快取得问题的解。
二、穷举算法模式
穷举算法模式:
(1)问题解的可能搜索的范围:用循环或循环嵌套结构实现
(2)写出符合问题解的条件。
(3)能使程序优化的语句,以便缩小搜索范围,减少程序运行时间。
三、使用穷举法设计算法
穷举法应用很多,比如一些密码破译软件通常就是用的穷举算法。
如在QQ上,OicqPassOver这个工具穷举你的口令,它根据机器性能最高可以每秒测试20000个口令,如果口令简单,一分钟内,密码就会遭到破译。
下面我们来以三个例子说明穷举法的具体应用。
实例一:古希腊人认为因子的和等于它本身的数是一个完全数(自身因子除外),例如28的因子是1、2、4、7、14,且1+2+4+7+14=28,则28是一个完全数,编写一个程序求2~1000内的所有完全数。
分析:
(1)本题是一个搜索问题,搜索范围 2~1000,找出该范围内的完全数;
(2)完全数必须满足的条件:因子的和等于该数据的本身。
(3)问题关键在于将该数的因子一一寻找出来,并求出因子的和。
程序如下:
Program p3_1 ;
Var a , b,s :integer ;
Begin
For a:=2 to 1000 do
Begin
S:=0 ;
For b:=1 to a -1 do
If a mod b =0 then s:=s+b ; { 分解因子并求和 }
If a=s then begin
Write( a, ‘=’ ,1, );
For b:=2 to a -1 do
If a mod b=0 then write( ’+’, b );
Writeln ;
End;
End;
End.
当程序运行后,输出结果:
6 = 1 + 2 + 3
28 = 1 + 2 + 4 + 7 + 14
496 =1 + 2 + 4 + 8 + 16 + 31 + 62 + 124 + 248
实例二:(第七届全国青少年信息学(计算机)奥林匹克分区联赛初赛试题)
在A,B两个城市之间设有N个路站(如下图中的S1,且N<100),城市与路站之间、路站和路站之间各有若干条路段(各路段数≤20,且每条路段上的距离均为一个整数)。
A,B的一条通路是指:从A出发,可经过任一路段到达S1,再从S1出发经过任一路段,…最后到达B。
通路上路段距离之和称为通路距离(最大距离≤1000)。
当所有的路段距离给出之后,求出所有不同距离的通路个数(相同距离仅记一次)。
例如:下图所示是当N=1时的情况:
从A到B的通路条数为6,但因其中通路5+5=4+6,所以满足条件的不同距离的通路条数为5。
算法说明:本题采用穷举算法。
数据结构:N:记录A,B间路站的个数
数组D(I,0)记录第I-1到第I路站间路段的个数
D(I,1),D(I,2),…记录每个路段距离
数组G记录可取到的距离
PROGRAM CHU7_6;
VAR I,J,N,S:INTEGER;
B:ARRAY[0..100]OF INTEGER;
D:ARRAY[0..100,0..20]OF INTEGER;
G :ARRAY[0..1000]OF 0..1;
BEGIN
READLN(N);
FOR I:=1 TO N+1 DO
BEGIN
READLN(D[I,0]);
FOR J:=1 TO D[I,0]DO READLN(D[I,J]);
END;
D[0,0]:=1;
FOR I:=1 TO N+1 DO B[I]:=1;
B[0]:=0;
FOR I:=0 TO 1000 DO G[I]:=0;
WHILE ①DO
BEGIN
S:=0;
FOR I:=1 TO N+1 DO
S:= ②
G[S]:=1;J:=N+1;
WHILE ③DO J:=J-1;
B[J]:=B[J]+1;
FOR I:=J+1 TO N+1 DO B[I]:=1;
END;
S:=0;
FOR I:=1 TO 1000 DO
④;
WRITELN(S);READLN;
END.
答案:
① B[0]=0② S+D[I,B[I]]; ③ B[J]=D[J,0] ④S:=S+G[I]
实例三(第八届全国青少年信息学奥林匹克联赛(NOIP2002)试题)
将n个整数分成k组(k≤n,要求每组不能为空),显然这k个部分均可得到一个各自的和s1,s2,……sk,定义整数P为:P=(S1-S2)2+(S1一S3)2+……+(S1-Sk)2+(s2-s3)2+……+(Sk-1-Sk)2
问题求解:求出一种分法,使P为最小(若有多种方案仅记一种)
程序说明:
数组:a[1],a[2],...A[N]存放原数
s[1],s[2],...,s[K]存放每个部分的和
b[1],b[2],...,b[N]穷举用临时空间
d[1],d[2],...,d[N]存放最佳方案
程序:
program exp4;
Var i,j,n,k : integer;
a :array [1..100] of integer;
b,d:array [0..100] of integer;
s :array[1..30] of integer;
begin
readln(n,k);
for I:=1 to n do read(a[I]);
for I:=0 to n do b[I]:=1;
cmin:=1000000;
while (b[0]=1) do
begin
for I:=1 to k do ①
for I:=1 to n do
②
sum:=0;
for I:=1 to k-1 do
for j:= ③
sum:=sum+(s[I]-s[j])*(s[I]-s[j]);
if ④ then
begin
cmin:=sum;
for I:=1 to n do d[I]:=b[I];
end;
j:=n;
while ⑤ do j:=j-1;
b[j]:=b[j]+1;
for I:=j+1 to n do ⑥
end;
writeln(cmin);
for I:=1 to n do write(d[I]:40);
writeln;
end.
四、穷举算法的深入应用
实例一:一根29厘米长的尺子, 只允许在上面刻七个刻度, 要能用它量出1~29 厘米的各种长度。
试问这根尺的刻度应该怎样选择?
分析:
(1) 从1~29 厘米中选择七个刻度的所有可能情况数是:
C7 29= 29·28·26·25·24·23 = 29·9·26·5·2·23= 29·26·23·90= 1560780
1·2·3·4·5·6·7
(2) 对于每一组刻度的选择都需要判断是否能将1~29 厘米的各种刻度量出来, 例如选择的刻度为: a1,a2,a3,a4,a5a,6,a7 那么能量出的刻度为:
a1, 29-a1; 2
a2, a2-a1, 29-a2; 3
a3, a3-a1 ,a3-a2, 29-a3 ; 4
a4, a4-a1, a4-a2, a4-a3, 29-a4; 5
a5, a5-a1, a5-a2, a5-a3, a5-a4, 29-a5; 6
a6, a6-a1, a6-a2, a6-a3, a6-a4, a6-a5, 29-a6; 7
a7-a1, a7-a2, a7-a2, a7-a3, a7-a4, a7-a5, a7-a6, 29-a7; 8 共可量出2+3+4+5+6+7+8 种刻度, 即35 种刻度, 事实上其中有许多刻度是重复的, 不可能复盖1~29。
例如:取a1, a2, a3, a4, a5, a6, a7为1, 3, 6, 10, 15, 21, 28
能量出的刻度为:
1 , 28
3, 2, 26
6, 5, 3, 23
10, 9, 7, 4, 19
15, 14, 12, 9, 5, 14
21, 20, 18, 15, 11, 6, 8
28, 27, 25, 22, 18, 13, 7, 1
缺16,17,24 ( 29 即尺子长度)
如果找出了刻度a1, a2, a3, a4, a5, a6, a7 那么我们可以利用其对称性29-a1,29-a2,29-a3,29-a4,29-a5,29-a6,29-a7, 也是一组解, 所以求解过程中可仅考虑a1<a2<a3<a4<a5<a6<a7 的情况。
很显然要使1,28 两种刻度能量出来, 则在七个刻度就必须有 1 或28; 这样就可设a1=1 ( 或a1=28 )。
本题就变成了只要在2~27 中选取六个刻度问题了。
其刻度选择的数目为C6
= 26·25·24·23·22·21 = 26·5·23·11·7 = 230230
26
1·2·3·4·5·6
这样解的范围就从百万变成了十万的数量级, 大大减少运行次数。
因此,我们在用穷举法求问题解时,应注意程序的优化,尽可能减少搜索时间。
{ 程序优化}
(3) 为了判定七个刻度是否能够度量1~29的所有长度, 可以用集合的方法, 也可以用数组的0,1数据判断。
下面的程序使用了数组的0,1方法。
Program p12_2 ;
Const n=29 ; m=1;
Var a:array [1..7] of integer;
b:array [1..n] of 0..1 ; { 记录能量的刻度}
f:Boolean ;
I , j :integer ;
BEGIN
a[1]:=m;
for a[2]:=2 to n-7 do
for a[3]:=a[2]+1 to n-6 do
for a[4]:=a[3]+1 to n-5 do
for a[5]:=a[4]+1 to n-4 do
for a[6]:=a[5]+1 to n-3 do
for a[7]:=a[6]+1 to n-2 do
begin
for i:=1 to 29 do b[i]:=0;
for i:=1 TO 7 do
begin
b[a[i]]:=1; b[n-a[i]]:=1; b[n]:=1 ; { 初始化}
for j:=i+1 TO 7 do b[abs(a[j]-a[i])]:=1
end;
j: =0;
for i;=1 to n do j:=j + b[i];
if j=n then begin
for i:=1 to 7 do write (a[i]:4);
writeln ;
end ;
end;
end.
运行程序的结果: 当m=1 时得出两组刻度
1 2 14 18 21 24 27
1 4 10 17 2
2 24 27
m=28 时也可获得两组刻度
28 2 5 7 13 19 25
28 2 5 8 11 15 27
这两组刻度实际上是m=1 的对称情况, 所以问题的解实质上为两组结果。
如果调整n=30 可以发现在这样的情况下程序无解, 这说明取30cm 长的尺子, 若仍取七个刻度, 要能量出1~30cm 的各种长度, 是不可能的。
实例二:邮局发行一套票面有四种不同值的邮票,如果每封信所贴邮票张数不超过三枚,存在整数R,使得用不超过三枚的邮票,可以贴出连续的整数1、2、3,…,R来,找出这四种面值数,使得R值最大。
分析:
本题知道每封信邮票数的范围(<=3 ),邮票有四种类型,编程找出能使面值最大邮票。
其算法是:
(1) 面值不同的四种邮票,每封信所贴邮票不超过3 张。
(2) 用这四种邮票贴出连序的整数,并且使R值最大。
(3) 用穷举法,找出所有符合条件的解。
(4) 本题用集合的方法统计邮票的面值,提高判重的速度。
设四种邮票的面值分别为:A , B , C , D ,根据题意设:
A <
B <
C < D,因此A=1 ,用循环语句完成搜索。
Program p12_3 ;
var a, b , c , d :integer ;
x, x0, x1 , x2 , x3, x4 :integer ;
st1 :set of 1 ..100 ;
Function number( a, b, c, d : integer ) :integer ;
var n1, n2, n3 ,n4 , sum :integer ;
begin
st1:=[ ] ;
for n1:= 0 to 3 do {每种邮票所取的张数}
for n2:= 0 to 3-n1 do
for n3:= 0 to 3-n1- n2 do
for n4:= 0 to 3-n1-n2-n3 do
begin
if n1+n2+n3+n4 <= 3 then
begin
sum:= n1*a+n2*b+n3*c+n4*d ;
{计算信封的邮票面值}
st1:=st1+[sum]
end;
end;
sum:=1 ;
while sum in st1 do
sum:=sum+1;
number:= sum-1;
end ; { 函数结束}
BEGIN { main }
a:=1 ; x0:=0 ;
for b:= a+1 to 3*a+1 do
for c:= b+1 to 3*b+1 do {每种邮票的可取值的范围}
for d:= c+1 to 3*c+1 do
begin
x:= number (a, b, c, d ); {调用函数求每封信的邮票总面值}
if x > x0 then
begin
x0:=x; x1:=a ; x2:=b ; x3:=c ; x4:=d
{保存最大面值邮票}
write( x1:5, x2:5, x3:5, x4:5 );
writeln( ‘‘:10, 'x0=', x0 );
end;
end;
end.
程序运行后,其输出结果是:{ 解答结果有11 组}
1 2 3 4 x0=12
1 2 3 5 x0=13
…….
1 3 6 10 x0=23
1 4 7 8 x0=24
【例题3】如图所示的8个格子中放入1~8八个数字,使得相邻的
和对角线的数字之差不为1。
编程找出所有放法。
我们先不考虑后一条
件,只考虑第一个条件,即把1~8八个数字放入8个格子中。
这是容
易做到的,就是8个数字的全排列,共有8!=40320种放法。
然后对这
8!个可行解用后一个条件加以检验,输出符合条件的解。
对于后一个条
件中“相邻”的判断,可以建立一个邻接表来解决:
i│1 2 3 4 5 6 7 8 9 10 11 12 13 14
──┼──────────────────────
j 1│1 1 1 2 2 2 3 3 3 4 5 5 6 7
2│2 3 4 3 5 6 4 6 7 7 6 8 7 8
表中表示哪两个格子是相邻的,link[i,1]和link[i,2]是相邻的格子的编号。
全排列的产生,可以用八重循环,也可以用专门的算法,程序留给同学们自己去完成。
利用穷举策略编制的程序,其运算量一般是很大的,因此如何提高算法效率是穷举算法一个很重要的问题。
一般应尽量减少可行解的个数,使得第二步的检验运算量尽可能地少。
例如对于例5-1,如何来优化算法呢?如果注意到b3和b6两个格子,与它们“相邻”的格子有6个,也就是说,放入这两个格子中的数,必须和6个数不连续,仅可以和一个数是连续的,这样的数只有2个,即1和8。
这样,b1,b3,b6,b8;4个格子中数的放法仅有两种可能:2、8、1、7和7、1、8、2。
而b2、b4、b5、b7四个格子中的数仅需在3~6四个数中选择。
经过上述优化,可行解仅有:2×4!=48个,大大减少了计算量。
并且检验是否符合要求,也只需检查(1,2),(1,4),(2,5),(4,7),(5,8),(7,8)这6对数之差就可以了。
按改进的算法编制的PASCAL程序如下:
program exampleb;
uses Crt:
const link:array[1..6,1..2] of integer=
((1,2),(1,4),(2,5),(4,7),(5,8),(7,8));
var b:array[1..8] of integer;
procedure print;
begin
writeln(' ',b[1]:2);
writeln(b[2]:2,b[3]:2,b[4]:2);
writeln(b[5]:2,b[6]:2,b[7]:2);
writeln('',b[8])
end;
function choose:boolean;
var i: integer;
begin
choose:= false;
fori:=1 to 6 do
if abs(b[link[i, 1]] - b[link[i ,2]]) = 1 then exit;
choose := true
end;
procedure try;
begin
for b[2]:=3 to 6 do
for b[4]:= 3 to 6 do
if b[2]<>b[4] then
for b[5]:= 3 to 6 do
if (b[5]<>b[2]) and (b[5]<>b[4]) then
begin
b[7]:= 18 - b[2] - b[4] - b[5];
if choose then print;
end;
end;
{ main program }
begin
clrscr;
b[1]:=2;b[3]:=8;b[6]:= 1;b[8]:=7;
try;
b[1]:=7;b[3]:=1 ;b[6]:=8;b[8]:=2;
try; readln
end.
上面优化算法的方法是尽可能减少可行解的数目,也称为“剪枝”,即把明显不符合条件的可行解尽可能地剪去,减少穷举的计算量。
本次作业:
一、问题描述:
Farmer John 的奶牛们喜欢看书,并且Farmer John 发现在他的奶牛们稍微看了些有关于自然科学的书时,会产出更多的牛奶。
他决定更新牛棚里的图书馆,把原廉价的小说换成算术和数学的课本。
不幸的是,有些新书掉到了泥浆里面,现在它们的ISBN号码很难分辨出来了。
ISBN(国际标准图书编号)是由十个阿拉伯数字组成的编码,用来唯一地标识一本书。
前九个阿拉伯数字描述这本书的一些信息,最后一个数字用来验证ISBN码是否正确。
要验证ISBN码的正确性,你要把第一个数字乘以十,你要把第二个数字乘以九,你要把第三个数字乘以八……直到最后一个数字乘上一,再把这些积累加起来。
如果所得的和可以被11整除的话,那么这就是一个合法的ISBN码。
比如说 020******* 是一个合法的ISBN,因为
10*0+9*2+8*0+7*1+6*1+5*0+4*3+3*3+2*1+1*1=55
前九个数字都在0到9之间。
有时候,最后一个数字需要取到10,那么我们就把最后一个数字写成大写X(这时就不叫数字了,呵呵)。
比如156881111X也是一个合法的ISBN 码。
你的任务就是在给你丢失了一个数字的ISBN码之后,确定那个丢失的数字。
丢失数字的地方用?表示。
输入格式:
总共1行,一个十个数字组成的ISBN码,其中包含用?表示的一个丢失的数字。
输出格式:
总共1行:就是那个丢失的数码(0..9或大写X)。
如果标有的?的位置上没有数字可以使之成为一个合法的ISBN码的话,就输出-1。
二、求数组元素[问题描述]
给出任意一个自然数N (N≤100),输出满足下列条件的数组元素及不同方案数,条件是:
<1>数组元素由各不相同自然数组成;
<2>数组元素的最后一个元素必为N;
<3>每一个数组元素都不小于它前面一个元素的平方 (第一个元素除外);
<4>数组中包含的元素个数可不相同,但至少要有一个元素。
例如:
N=1 输入: N (不用判错)
数组 (1) 输出:一个 (不同方案数)
K=1 (以K记录不同的方案数)
又如:
N=5
数组 (5)
(1,5)
(1,2,5)
(2,5)
K=4。