深搜应用举例
【例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.