搜索与回溯算法(二)

深搜应用举例

【例6】素数环:从1到20这20个数摆成一个环,要求相邻的两个数的和是一个素数。
【算法分析】
非常明显,这是一道回溯的题目。从1开始,每个空位有20种可能,只要填进去的数合法:与前面的数不相同(判重);与左边相邻的数的和是一个素数。第20个数还要判断和第1个数的和是否素数。
【算法流程】
1、数据初始化;   2、递归填数:判断第J种可能是否合法;
A、如果合法:填数;判断是否到达目标(20个已填完):是,打印结果;不是,递归填下一个;
B、如果不合法:选择下一种可能;

 1 program ex5_1;框架[一]
 2 var  a:array[0..20]of integer;
 3         b:array[0..20]of boolean;  
 4         total:longint;
 5 function pd(x,y:integer):boolean;    //判断素数
 6 var   k,i:integer;
 7 begin
 8    k:=2; 
 9    i:=x+y; 
10    pd:=false;
11    while (k<=trunc(sqrt(i)))and(i mod k<>0) do inc(k);
12    if k>trunc(sqrt(i)) then pd:=true;
13 end;
14 procedure print;                                        //输出方案
15 var j:integer;
16 begin
17    inc(total);
18    write('<',total,'>:');
19    for j:=1 to 20 do write(a[j],' ');
20    writeln;
21 end;
22 procedure Search(t:integer);                  //回溯过程
23 var i:integer;
24 begin
25    for i:=1 to 20 do                                    //有20个数可选
26     if pd(a[t-1],i)and b[i] then                    //判断与前一个数是否构成素数及该数是否可用
27       begin
28         a[t]:=i;   b[i]:=false;
29         if t=20 then begin if pd(a[20],a[1]) then print;end
30           else Search(t+1);
31         b[i]:=true;
32      end;
33 end;
34 BEGIN
35   fillchar(b,sizeof(b),#1);                         //b数组赋初值为True
36   total:=0;
37   Search(1);write('total:',total);               //输出总方案数
38 END.
参考程序框架一
 1 var  a:array[0..20]of integer;    
 2       b:array[0..20]of boolean;    
 3       total:longint;   s:set of 1..40;
 4 procedure print;                                        //输出方案
 5 var j:integer;
 6 begin
 7    inc(total);   write('<',total,'>:');
 8    for j:=1 to 20 do write(a[j],' ');   writeln;
 9 end;
10 procedure Search(t:integer);                  //回溯过程
11 var i:integer;
12 begin
13   if t>20 then begin                          //如果20个数选用完则回溯
14     if (a[20]+a[1])in s then print;           //如果满足条件,则输出
15     exit;
16   end;
17    for i:=1 to 20 do                                    //有20个数可选
18     if ((a[t-1]+i) in s) and b[i] then              //判断与前一个数是否构成素数及该数是否可用
19       begin
20         a[t]:=i;   b[i]:=false;
21         Search(t+1);
22         b[i]:=true;
23      end;
24 end;
25 BEGIN
26   fillchar(b,sizeof(b),#1);                         //b数组赋初值为True
27   total:=0;  s:=[1,2,3,5,7,11,13,17,19,23,29,31,37];
28   Search(1);write('total:',total);               //输出总方案数
29 END.
参考程序框架二

【例7】设有n个整数的集合{1,2,…,n},从中取出任意r个数进行排列(r<n),试列出所有的排列。

 1 program ex5_2;
 2 VAR  s: set of 1..100;
 3 n,r,num:integer;
 4 a:array [1..100] of integer;
 5 PROCEDURE print;              //输出方案
 6 var i:integer;
 7 begin
 8    num:=num+1;
 9    for i:=1 to r do
10      write(a[i]:3);
11    writeln;
12 end;
13 PROCEDURE Search(k:integer);                    //回溯过程
14 VAR  i:integer;
15 begin
16    for i:=1 to n do
17      if i in s then                                                 //判断i是否可用
18        begin
19          a[k]:=i;                                                     //保存结果
20          s:=s-[i];                                                    //从s集合中删除i
21          if k=r then print
22           else Search(k+1);
23          s:=s+[i];                                                   //把i放回s集合中
24        end;
25 end;
26 BEGIN
27    write('Input n,r:');readln(n,r);
28    s:=[1..n];num:=0;
29    Search(1);
30    writeln('number=',num);                                //输出方案总数
31 END.
框架[一]
 1 program ex5_2;
 2 type se=set of 1..100;
 3 var  s:se;
 4        n,r,num,k:integer;
 5        a:array [1..100] of integer;
 6 PROCEDURE print;          //输出方案
 7 var i:integer;
 8 begin
 9    num:=num+1;
10    for i:=1 to r do
11      write(a[i]:3);
12    writeln;
13 end;
14 PROCEDURE Search(s:se;k:integer);       //回溯过程
15 VAR i:integer;
16 begin
17    if k>r then print
18     else 
19 for i:=1 to n do
20           if i in s then             //判断i是否可用
21              begin
22                  a[k]:=i;             //保存结果
23                  Search(s-[i],k+1); //进一步递归,i已放过的信息通过参数进行传递
24              end;
25 end;
26 BEGIN
27    write('Input n,r:'); readln(n,r);
28    s:=[1..n];num:=0;
29    Search(s,1);
30    writeln('number=',num); //输出方案总数
31    readln;
32 END.
框架[二]

【例8】任何一个大于1的自然数n,总可以拆分成若干个小于n的自然数之和。

当n=7共14种拆分方法:
7=1+1+1+1+1+1+1
7=1+1+1+1+1+2
7=1+1+1+1+3
7=1+1+1+2+2
7=1+1+1+4
7=1+1+2+3
7=1+1+5
7=1+2+2+2
7=1+2+4
7=1+3+3
7=1+6
7=2+2+3
7=2+5
7=3+4
total=14

 1 program ex5_3;
 2 var a:array[0..100]of integer;
 3       n,t,total:integer;
 4 procedure print(t:integer);
 5 var i:integer;
 6 begin
 7   write(n,'=');
 8   for i:=1 to t-1 do  write(a[i],'+');         //输出一种拆分方案
 9   writeln(a[t]);
10   total:=total+1;                          //方案数累加1
11 end;
12 procedure Search(s,t:integer);
13 var i:integer;
14 begin
15    for i:=1 to s do
16      if (a[t-1]<=i)and(i<n) then          //当前数i要大于等于前1位数,且不过n
17 begin
18         a[t]:=i;                             //保存当前拆分的数i
19         s:=s-a[t];                           //s减去数i, s的值将继续拆分
20         if s=0 then print(t)                  //当s=0时,拆分结束输出结果
21           else Search(s,t+1);            //当s>0时,继续递归
22         s:=s+a[t];                       //回溯:加上拆分的数,以便产分所有可能的拆分
23       end;
24 end;
25 BEGIN
26   readln(n);
27   Search(n,1);                           //将要拆分的数n传递给s
28   writeln('total=',total);                  //输出拆分的方案数
29   readln;
30 END.
参考程序

【例9】马的遍历
        中国象棋半张棋盘如图4(a)所示。马自左下角往右上角跳。今规定只许往右跳,不许往左跳。比如图4(a)中所示为一种跳行路线,并将所经路线打印出来。打印格式为:0,0->2,1->3,3->1,4->3,5->2,7->4,8…

【算法分析】
       如图4(b),马最多有四个方向,若原来的横坐标为j、纵坐标为i,则四个方向的移动可表示为:
1:  (i,j)→(i+2,j+1);  (i<3,j<8)
2:  (i,j)→(i+1,j+2);  (i<4,j<7)
3:  (i,j)→(i-1,j+2);  (i>0,j<7)
4:  (i,j)→(i-2,j+1);  (i>1,j<8)
     搜索策略:
        S1:A[1]:=(0,0);
        S2:从A[1]出发,按移动规则依次选定某个方向,如果达到的是(4,8)则转向S3,否则继续搜索下一个到达的顶点;
        S3:打印路径。

 

 1 program ex5_5;
 2 const  x:array[1..4,1..2] of integer=((2,1),(1,2),(-1,2),(-2,1));   //四种移动规则
 3 var  t:integer;                                                     //路径总数
 4      a:array[1..9,1..2] of integer;                                 //路径
 5 procedure print(ii:integer);                                        //打印
 6 var  i:integer;
 7 begin
 8   inc(t);                                                           //路径总数
 9   for i:=1 to ii-1 do
10      write(a[i,1],',',a[i,2],'-->');
11   writeln('4,8',t:5);
12 end;
13 procedure Search(i:integer);                                        //搜索
14 var   j:integer;
15 begin
16   for j:=1 to 4 do                                        //往4个方向跳
17     if (a[i-1,1]+x[j,1]>=0) and (a[i-1,1]+x[j,1]<=4) and
18        (a[i-1,2]+x[j,2]>=0) and (a[i-1,2]+x[j,2]<=8) then           //判断马不越界
19        begin
20          a[i,1]:=a[i-1,1]+x[j,1];                                   //保存当前马的位置
21          a[i,2]:=a[i-1,2]+x[j,2];
22             if (a[i,1]=4) and (a[i,2]=8) then print(i)
23            else Search(i+1);                                 //搜索下一步
24          a[i,1]:=0;a[i,2]:=0                              //马回到上一步即回溯,取消原记录
25        end;
26 end;
27 BEGIN                                      //主程序
28    a[1,1]:=0;  a[1,2]:=0;
29    Search(2);                           //从坐标(0,0)开始往右跳第二步
30 END.
参考程序

 

【例12】跳马问题。在5*5格的棋盘上,有一个国家象棋的马,从(1,1)点出发,按日字跳马,它可以朝8个方向跳,但不允许出界或跳到已跳过的格子上,要求在跳遍整个棋盘。
输出前5个方案及总方案数。
输出格式示例:
1    16   21   10   25
20  11   24   15    22
17  2     19   6     9
12  7     4     23   14
3   18    13   8     5

 1 program ex5_8;
 2 var a:array[1..5,1..5] of integer;                //记每一步走在棋盘的哪一格
 3       b:array[1..5,1..5] of boolean;              //棋盘的每一格有没有被走过
 4       u,v:array[1..8] of integer;                    //8个方向上的x,y增量
 5       i,j,num:integer;
 6 procedure print;                                        //打印方案
 7 var  k,kk:integer;
 8 begin
 9    num:=num+1;                                        //统计总方案
10    if num<=5 then  
11      begin                                                    //打印出前5种方案
12        for k:=1 to 5 do  
13           begin                                     //打印本次方案
14              for kk:=1 to 5 do   write(a[k,kk]:5);
15              writeln;
16           end;
17      end;
18 end;
19 procedure Search(i,j,n:integer);     //以每一格为阶段,在每一阶段中试遍8个方向
20 var   k,x,y:integer;                          //这三个变量一定要定义局部变量
21 begin
22    if n>25 then 
23      begin 
24         print; 
25         exit;
26      end ;                                         //达到最大规模打印、统计方案
27    for k:=1 to 8 do                          //试遍8个方向
28      begin
29         x:=i+u[k]; y:=j+v[k] ;              //走此方向,得到的新坐标
30         if (x<=5) and (x>=1) and (y<=5) and (y>=1)and b[x,y] then     
31           begin                                  //如果新坐标在棋盘上,并且这一格可以走
32              b[x,y]:=false;   a[x,y]:=n;
33              Search(x,y,n+1);             //从(x,y)去搜下一步该如何走
34              b[x,y]:=true;   a[x,y]:=0;
35           end;
36      end;
37 end;
38 BEGIN
39    u[1]:=1;     v[1]:=-2;                           //8个方向的x,y增量
40    u[2]:=2;     v[2]:=-1;
41    u[3]:=2;     v[3]:=1;
42    u[4]:=1;     v[4]:=2;
43    u[5]:=-1;    v[5]:=2;
44    u[6]:=-2;    v[6]:=1;
45    u[7]:=-2;    v[7]:=-1;
46    u[8]:=-1;    v[8]:=-2;
47    for i:=1 to 5 do                                //初始化
48       for j:=1 to 5 do  
49          begin
50             a[i,j]:=0;   b[i,j]:=true;
51          end;
52    a[1,1]:=1; b[1,1]:=false;                      //从(1,1)第一步开始走
53    Search(1,1,2);                              //从(1,1)开始搜第2步该怎样走
54    writeln(num);                             //输出总方案(304)
55 END.
参考程序

 

你可能感兴趣的:(搜索与回溯算法(二))