第二章 算法应用
一、穷举搜索法
穷举搜索法是穷举所有可能情形,并从中找出符合要求的解。
穷举所有可能情形,最直观的是联系循环的算法。
[例]找出n个自然数(1,2,3,…,n)中r个数的组合。例如,当n=5,r=3时,所有组合为:
5 4 3
5 4 2
5 4 1
5 3 2
5 3 1
5 2 1
4 3 2
4 3 1
4 2 1
3 2 1
total=10 {组合的总数}
[解]n个数中r的组合,其中每r 个数中,数不能相同。另外,任何两组组合的数,所包含的数也不应相同。例如,5、4、3与3、4、5。为此,约定前一个数应大于后一个数。
将上述两条不允许为条件,当r=3时,可用三重循环进行搜索。
[程序]
Program zuhe11;
const n=5;
var i,j,k,t:integer;
begin t:=0;
for i:=n downto 1 do
for j:=n downto 1 do
for k:=n downto 1 do
if (i<>j)and(i<>k)and(i>j)and(j>k) then
begin t:=t+1;writeln(i:3,j:3,k:3);end;
writeln(';total=';,t);
end.
或者
Program zuhe12;
const n=5;r=3;
var i,j,k,t:integer;
begin t:=0;
for i:=n downto r do
for j:=i-1 downto r-1 do
for k:=j-1 downto 1 do
begin t:=t+1;writeln(i:3,j:3,k:3);end;
writeln(';total=';,t);
end.
这两个程序,前者穷举了所有可能情形,从中选出符合条件的解,而后者比较简洁。但是这两个程序都有一个问题,当r变化时,循环重数改变,这就影响了这一问题的解,即没有一般性。
但是,很多情况下穷举搜索法还是常用的。
递归法也是常用的方法。
[例]仍以前节例题为例,找n个数的r个数的组合。要求:
输入:n,r=5 3
输出:5 4 3
5 4 2
5 4 1
5 3 2
5 3 1
5 2 1
4 3 2
4 3 1
4 2 1
3 2 1
total=10 {组合的总数}
[解]分析所提示的10组数。首先固定第一位数(如5),其后是在另4个数中再“组合”2个数。这就将“5个数中3个数的组合”推到了“4个数中2个数的组合”上去了。第一位数可以是n r(如5 3),n个数中r个数组合递推到n-1个数中r-1个数有组合,这是一个递归的算法。即:
Procedure comb(n,r:integer);
var i:integer;
begin for i:=n downto r do
begin {固定i的输出位置}
comb(i-1,r-1); {原过程递推到i-1个数的r-1个数组合}
end;
end;
再考虑打印输出格式。
[程序]
Program zuhe2;
var k,n,r:integer;
Produrce comb(n,r:integer);
var i,temp:integer;
begin for i:=n downto r do
if (i<>n)and(k<>r) then {k为过程外定义的}
begin for temp:=1 to (k-r)*3 do write('; ';); {确定i的输出位置}
end;
write(i:3);
if i>1 then comb(i-1,r-1); {递推到下一情形}
else writeln;
end;
Begin {main}
write(';n,r=';);readln(n,r);
if r>n then
begin writeln(';Input n,r error!';);halt; end;
comb(n,r); {调用递归过程}
End;
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
[例]再以前例说明,找n个数中r个数的组合。
[解]将自然数排列在数组A中:
A[1] A[2] A[3]
5 4 3
5 4 2
…
3 2 1
排数时从A[1] A[2] A[3],后一个至少比前一个数小1,并且应满足ri+A[ri]>r。若ri+A[ri]≤r就要回溯,该关系就是回溯条件。为直观起见,当输出一组组合数后,若最后一位为1,也应作一次回溯(若不回,便由上述回溯条件处理)。
[程序]
program zuhe3;
type tp=array[1..100] of integer;
var n,r:integer;
procedure comb2(n,r:integer;a:tp);
var i,ri:integer;
begin ri:=1;a[1]:=n;
repeat
if ri<>r then {没有搜索到底}
if ri+a[ri]>r then {是否回溯}
begin a[ri+1]:=a[ri]-1;
ri:=ri+1;
end
else
begin ri:=ri-1; a[ri]:=a[ri]-1;end; {回溯}
else
begin for j:=1 to r do write(a[j]:3);writeln; {输出组合数}
if a[r]=1 then {是否回溯}
begin ri:=ri-1; a[ri]:=a[ri]-1;end; {回溯}
else a[ri]:=a[ri]-1; {递推到下一个数}
end;
until a[1]<>r-1;
end;
begin {MAIN}
write(';n,r=';);readln(n,r);
if r>n then
begin writeln(';Input n,r error!';);halt; end
comb2(n,r);
end.