回 帖 发 新 帖 刷新版面

主题:[原创]四个Pascal回溯法的经典应用

我们在解决一些算法时,会遇到这样的一类题目,它的问题可以分解,但是又不能得出明确的动态规划或是递归解法,此时可以考虑用回溯法解决此类问题。回溯法的优点在于其程序结构明确,可读性强,易于理解,而且通过对问题的分析可以大大提高运行效率。但是,对于可以得出明显的递推公式迭代求解的问题,还是不要用回溯法,因为它花费的时间比较长。 
回溯算法在Pascal中的一般形式: 
procedure tryit(i:integer); 
var j:integer; 
begin 
    for j:=1 to maxj do 
      if   新结点符合条件 then 
        begin 
           记录新结点; 
           if  新结点是目标结点 then  输出  else tryit(i+1); 
           删除结点; 
        end; 
end; 

下面给出四个回溯算法在Pascal中的应用。 

1、八皇后问题 
这个实例堪称回溯算法的经典。 
【题目叙述】我们都知道,在国际象棋中,皇后的攻击范围是其所在的行,列以及两条对角线;如果在一个8×8的棋盘上要放置8个皇后,并且使得它们互不攻击,给出所有放法。 
【问题分析】1、冲突。包括行、列、两条对角线: 
  (1)列:规定每一列放一个皇后,不会造成列上的冲突; 
  (2)行:当第I行被某个皇后占领后,则同一行上的所有空格都不能再放皇后,要把以I为下标的标记置为被占领状态; 
  (3)对角线:对角线有两个方向。在同一对角线上的所有点(设下标为(i,j)),要么(i+j)是常数,要么(i-j)是常数。因此,当第I个皇后占领了第J列后,要同时把以(i+j)、(i-j)为下标的标记置为被占领状态。 
  2、数据结构。 
  (1)解数组A。A表示第I个皇后放置的列;范围:1…8 
  (2)行冲突标记数组B。B=0表示第I行空闲;B=1表示第I行被占领;范围:1…8 
  (3)对角线冲突标记数组C、D。 
  C=0表示第(I-J)条对角线空闲;C=1表示第(I-J)条对角线被占领;范围:-7…7 
  D=0表示第(I+J)条对角线空闲;D=1表示第(I+J)条对角线被占领;范围:2…16 
【参考程序】 
var a:array[1..8] of integer; 
    b,c,d:array[-7..16] of integer; 
    t,i,j,k:integer; 
procedure print; 
begin 
  t:=t+1; 
  write(t,':'); 
  for k:=1 to 8 do write(a[k]:4); 
  writeln; 
end; 
procedure tryit(i:integer); 
var j:integer; 
begin 
  for j:=1 to 8 do 
    if (b[j]=0) and (c=0) and (d=0) then 
    begin 
      a:=j; 
      b[j]:=1; 
      c:=1; 
      d:=1; 
      if i=8 then print else tryit(i+1); 
      b[j]:=0; 
      c:=0; 
      d:=0; 
    end; 
end; 
begin 
  fillchar(b,sizeof(b),0); 
  fillchar(c,sizeof(c),0); 
  fillchar(d,sizeof(d),0); 
  tryit(1); 
end. 

2、走台阶问题 
【题目叙述】你眼前一共有n级台阶(n为正整数),现在你要走完这n级台阶,已知你只能一步走一级或一步走两级,请你算算一共有多少种走法,并把这些走法都给出。 
【输入输出样例】输入:3 
                输出:1 1 1 
                      1 2 
                      2 1 
【问题分析】每走一步(可以用变量i来表示),都有两种选择,要么走一级,要么走两级,于是这里的maxj便是2;还需要一个计数器s,用来统计走过的台阶数,目标结点便是当s=n的时候。 
【参考程序】 
const n=10;m=5000; 
var a:array[1..m] of integer; 
    i,j,w,k,t,s:integer; 
procedure print; 
begin 
  t:=t+1; 
  write(t,':'); 
  for k:=1 to w do write(a[k]:4); 
  writeln; 
end; 
procedure tryit(i:integer); 
var j:integer; 
begin 
  for j:=1 to 2 do 
    if s+j<=n then 
    begin 
      s:=s+j;w:=w+1; 
      a:=j; 
      if s=n then print else tryit(i+1); 
      s:=s-j;w:=w-1; 
    end; 
end; 
begin 
  s:=0;w:=0; 
  for i:=1 to m do a:=0; 
  tryit(1); 
  writeln('There are ',t,' times.'); 
end. 

3、找数 
【题目叙述】键盘输入n个正整数(互不相等),找出其中和等于m的数的组合。 
【输入输出样例】输入:5(m的值) 1 2 3 4 5 
                输出:1 4 
                      2 3 
                      5 
【问题分析】这个问题和上面的走台阶问题类似,不同的是,这里要用到集合类型的数组,来统计出现过的组合,比如说组合"2,3"与"3,2"是同一个组合,因此不能重复出现,之所以是集合类型的,是因为集合[2,3]与集合[3,2]是完全相同的两个集合,而数组不具备这样的特点,这也就是这道题目的难点。 
【参考程序】 
type nb=set of 0..255; 
const max=10000; 
var a,b:array[1..max] of integer; 
    sb:array[1..max] of nb; 
    jh,jh2:set of 0..255; 
    n,m,i,j,w,k,t,s,p,q,r:integer; 
procedure print; 
begin 
  for k:=1 to w do jh2:=jh2+]; 
  for p:=1 to max do 
  begin 
    q:=p; 
    if jh2=sb[p] then 
    begin 
      q:=q-1; 
      break; 
    end; 
  end; 
  if q=max then begin 
    sb[r]:=jh2;r:=r+1; 
    t:=t+1; 
    write(t,':'); 
    for k:=1 to w do write(b[k]:4); 
    writeln; 
  end; 
  jh2:=[]; 
end; 
procedure tryit(i:integer); 
var j:integer; 
begin 
  for j:=1 to n do 
    if (s+a[j]<=m) and not(a[j] in jh) then 
    begin 
      s:=s+a[j];b:=a[j];w:=w+1;jh:=jh+[a[j]]; 
      if s=m then print else tryit(i+1); 
      s:=s-a[j];w:=w-1;jh:=jh-[a[j]]; 
    end; 
end; 
begin 
  s:=0;w:=0;jh:=[];jh2:=[];r:=1; 
  for i:=1 to max do sb:=[]; 
  fillchar(b,sizeof(b),0); 
  write('How many numbers do you want to process:');read(n); 
  write('Please input the total:');read(m); 
  write('Please input the numbers:'); 
  for i:=1 to n do read(a); 
  writeln('--------------------------------------------------------------'); 
  tryit(1); 
  if t=0 then writeln('Sorry,there are no suitable numbers.'); 
  writeln('--------------------------------------------------------------'); 
  writeln('There are ',t,' suitable groups.'); 
end. 
(程序变量太多,我自己写的时候都差点晕了,所以有待优化与改进) 

4、液晶数字问题 
【题目叙述】 如下图所示:0..9这10个数字可以由下列7个发光二极管组成。0-9十个数字排成一排,每个数字只可以用一次。排列要求:相邻数字后一个是由前一个数字添上几笔或是减去几笔得到的,不允许同时加减。例如:3和8,8和3可以相邻,3和5不能相邻等。问:有多少符合要求的排列?给出所有排列方法。 
[img]http://b7.photo.store.qq.com/http_imgload.cgi?/rurl4_b=aa6c2d3c76d790585ad863ad2c819b721a91f6d06f0676a44fd5ee3c76e191092103e77c8fd1945027d037893099dad60695548e6c38564b5b9b47789e09a3f817e8850d90280720b04000b359d3d40865acde80[/img]
【问题分析】由图可以知道,每一个数字(0-9)在液晶屏上都可以表示为几个数字的编号,比如数字2可以表示为2,3,4,5,6,这样就可以定义一个集合类型的数组,其中的每一个集合代表一个数字,其内容为编号1-7。 
【参考程序】 
type jh=set of 1..7; 
const num:array[0..9] of jh=([1..3,5..7],[3,7],[2..6],[2..4,6,7],[1,3,4,7],[1,2,4,6,7],[1,2,4..7],[2,3,7],[1..7],[1..4,6,7]); 
var a:array[1..10] of integer; 
    b:array[0..10] of integer; 
    ss:set of 0..9; 
    t,k,i,j:integer; 
procedure print; 
begin 
  t:=t+1; 
  write(t,':'); 
  for k:=1 to 10 do write(a[k]:4); 
  writeln; 
end; 
procedure tryit(i:integer); 
var j:integer; 
begin 
  for j:=0 to 9 do 
    if not(j in ss) and ((num]<=num[j]) or 
    (num]>=num[j])) then 
    begin 
      ss:=ss+[j];a:=j;b:=j; 
      if i=10 then print else tryit(i+1); 
      ss:=ss-[j]; 
    end; 
end; 
begin 
  b[0]:=8; 
  t:=0; 
  tryit(1); 
end. 

总结:回溯法在算法中处于非常重要的地位,其算法有着其他算法不可替代的优点,希望大家能够通过以上四个实例加深对回溯法的印象。

回复列表 (共1个回复)

沙发

谢谢楼主!

我来回复

您尚未登录,请登录后再回复。点此登录或注册