• 搜索与回溯算法(三)


    本节学习要点:

    1、 深度优先搜索的基本思想是什么?

    2、 深度优选搜索的基本框架(用回溯递归实现)

    3、 深度优先搜索算法要点

    4、 搜索与回溯练习题二部分试题讲解。

    搜索是人工智能中的一种基本方法,也是信息学竞赛选手所必须熟练掌握的一种方法,它最适合于设计基于一组生成规则集的问题求解任务,每个新的状态的生成均可使问题求解更接近于目标状态,搜索路径将由实际选用的生成规则的序列构成。我们在建立一个搜索算法的时候.首要的问题不外乎两个:以什么作为状态?这些状态之间又有什么样的关系?其实.在这样的思考过程中.我们已经不知不觉地将一个具体的问题抽象成了一个图论的模型——树(如图7-l所示)。

    状态对应着顶点.状态之间的关系(或者说从一个状态到另一个状态的形成过程即生成规则)对应着边。这样的一棵树就叫做搜索树。初始状态对应着根结点,目标状态对应着目标结点。我们的任务就是找到一条从根结点到目标结点的路径——一个成功的解。搜索算法的实现类似于图或树的遍历,通常可以有两种不同的实现方法:深度优先搜索(DFS——Depth First Search)和宽度优先搜索(BFS——Breadth First Search).

    1、深度优先搜索的基本思想:

    如算法名称那样,深度优先搜索所遵循的搜索策略是尽可能“深”地搜索树。在深度优先搜索中,对于当前发现的结点,如果它还存在以此结点为起点而未探测到的边,就沿此边继续搜索下去,若当结点的所有边都己被探寻过.将回溯到当前结点的父结点,继续上述的搜索过程直到所有结点都被探寻为止。

        深度优先搜索在树的遍历中也称作树的先序遍历。对于树而言,深度优先搜索的思路可以描述为:

        (1)将根结点置为出发结点。

        (2)访问该出发结点.

        (3)依次将出发结点的子结点置为新的出发结点.进行深度优先遍历(执行(2))。

        (4)退回上一层的出发结点。

    2、深度优先搜索的基本框架:(回溯递归实现)

     1 Procedure DFS(step) 
     2 Begin
     3     for i:=1 to max do   //枚举可扩展的子结点
     4       if 子结点i符合扩展条件 then begin 
     5          记录扩展的状态i; 
     6          if 子结点是目标结点 then 输出 
     7            else DFS(step+1); 
     8           删除扩展的状态i; 
     9         end10 end
     1 Procedure DFS(step);
     2  begin
     3     if 子结点是目标结点 then begin 输出;exit;end;
     4      for i:=1 to max do   //枚举可扩展的子结点,也就是搜索宽度
     5         if 子结点i符合扩展条件 then begin
     6        记录扩展的状态i; 
     7            DFS(step+1); 
     8            删除扩展的状态i
     9         end;
    10  end;

    3、深度优先搜索算法要点:

    我们在应用深度优先搜索算法解题时.一般应考虑如下几个重要因素:

        (1).选择合适角度定义结点状态

         选择合适的角度来定义结点状态,是设计搜索算法重要的一步,往往搜索算法可以从不同角度进行搜索,哪个角度更容易描述结点状态(结点定义),哪个角度搜索的层次更明确(搜索深度),哪个角度状态间的转换关系更容易实现(产生式),哪个角度可以搜索的效率更高(剪枝)等等,这是我们选择合适角度定义状态需要综合考虑的问题。

        (2).产生式

         所谓产生式,即从当前结点状态变换到下一结点状态的关系式。每个结点产生新结点的个数实际上就是该结点的搜索宽度。有的产生式很简单很直接,例如全排列问题,直接穷举可选的数就行了,有的产生式需要稍加变换,例如,在骑士巡游问题中马有8种跳法,每一个结点就可以最多扩展出8个新结点,每个新结点都是由原来结点坐标加上一个增量得到的,所以可以用for语句枚举8个方向的坐标增量就可以了。产生式的好坏,也可以直接影响程序的效率。

        (3).扩展条件

         即满足什么条件结点才可以向下扩展产生新结点,也就是说满足什么条件才可以继续向下搜索。这个扩展条件往往是约束搜索树规模的重要一环,很多搜索问题的优化都在这一环节进行剪枝。

        (4). 目标状态

         确定正确的目标状态,即满足什么条件输出方案。

      一种情况是要搜索出所有目标结点或任意一个目标结点,一种是要搜索出最优的目标结点,如果需要输出具体方案,还要使用数组记录下来每一步的搜索过程。

        (5). 状态的保存和恢复

         如果扩展子结点的过程需要用全局变量或变量形参保存结点状态,则子程序返回调用处后必须恢复其值。

    4、应用举例

    1、找零钱(money.pas

    问题描述:

    2n个人排队购一件价为0.5元的商品,其中一半人拿一张1元人民币,另一半人拿一张0.5元的人民币,要使售货员在售货中,不发生找钱困难,问这2n个人应该如何排队?找出所有排队的方案。(售货员一开始就没有准备零钱)

    输入:

    输入文件money.in仅一个数据n

    输出:

    输出文件money.out若干行,每行一种排队方案,每种方案前加序号No.i,每种方案0表示持0.5元钞票的人,1表示持1元钞票的人

    样例:

    money.in

    3

    money.out

    NO.1:000111 

    No.2:001011 

    No.3:001101 

    No.4:010011 

    No.5:010101 

    问题分析:

    1、 结点状态定义:用一维数组b[k]记录排队状态,b[k]=0表示拿0.5元的,b[k]=1表示拿1元的,每一步的结点状态转换到下一状态,直接转换即可,即b[k]=i(i=0或1)。另外用数组d记录当前状态下0的个数d[0]和1的个数d[1]

    2、 搜索宽度:因为每个人手中的钞票不是0.5元就是1元,只有两种情况,所以搜索宽度为2

    3、 子结点扩展条件:当前结点向下扩展,需满足的条件是,前面所有人手持的0.5元的个数要大于等于1元的个数,并且0.5元的个数要小于等于n

    4、 目标结点状态:前k个人已经排好队,即k>2*n,并且d[0]=d[1]

    5、 恢复递归前的状态:由于使用了全局变量数组d,当在递归前改变d[i]的值时,即inc(d[i]),递归后要恢复d[i]的值,即dec(d[i])。

     1 program money;
     2 const max=20;
     3 var
     4   b:array[1..2*max] of 0..1;
     5   d:array[0..1] of integer;
     6   total,n:integer;
     7 procedure print;
     8   var i:integer;
     9   begin
    10     inc(total);
    11     write('No.',total,':');
    12     for i:=1 to 2*n do
    13       write(b[i]:2);
    14     writeln;
    15   end;
    16 procedure dfs(k:integer);
    17   var i:integer;
    18   begin
    19     if (d[0]=d[1]) and (k>2*n) then begin print;exit;end;
    20     for i:=0 to 1 do
    21       if (d[0]>=d[1]) and (d[0]<=n) then
    22         begin
    23           b[k]:=i;
    24           inc(d[i]);
    25           dfs(k+1);
    26           dec(d[i]);
    27         end;
    28   end;
    29 begin
    30   readln(n);
    31   total:=0;  d[0]:=0;  d[1]:=0;
    32   dfs(1);
    33 end.

    2、最小拉丁方阵

    提交文件名:LATIN.PAS 

    问题描述: 

    输入 N,求 阶最小的拉丁方阵 (2 ≤ ≤ 9)阶拉丁方阵为每一行、每一列都是数字1N,且每个数字只出现一次。最小拉丁方阵是将方阵的一行一行数连接在一起,组成为一个数,则这个数是最小的。 

    输入输出示例: 

    N = 3 

    1 2 3 

    2 3 1 

    3 1 2

    N = 5 

    1 2 3 4 5 

    2 1 4 5 3 

    3 4 5 1 2 

    4 5 2 3 1 

    5 3 1 2 4 

    问题分析:

        枚举每一个格子可能放的数,搜索深度为n*n,搜索宽度为n,设置两个二维的布尔数组b[i,j]c[i,j],记录行和列中出现过的数字,b[i,j]=true表示第i行的数j可以使用,c[i,j]=true表示第i列的数j可以使用。用数组a[ij]记录结果,由于采用了二维数组记录结果,所以在枚举n*n个格子的时候需要把一维数组转成二维。

     1 var
     2   a : array[1..9,1..9] of byte;
     3   b,c : array[1..9,1..9] of boolean;
     4   n : integer;
     5 
     6 procedure printout;
     7 var
     8   i,j : integer;
     9 begin
    10   for i := 1 to n do begin
    11     for j := 1 to n do write(a[i,j]:3);
    12     writeln;
    13   end;
    14   readln;
    15   halt;
    16 end;
    17 
    18 procedure solve(s,y,x : byte);
    19 var
    20   i : integer;
    21 begin
    22   if s > n*n
    23     then  printout
    24     else for i := 1 to n do
    25            if b[y,i] and c[x,i] then begin
    26              a[y,x] := i; b[y,i] := false; c[x,i] := false;
    27              if s mod n = 0
    28                then solve(s+1,y+1,1)
    29                else solve(s+1,y,x+1);
    30              b[y,i] := true; c[x,i] := true;
    31            end;
    32 end;
    33 
    34 begin
    35   repeat
    36     write(' Input N : ');
    37     readln(n);
    38   until n in [1..9];
    39   fillchar(b,sizeof(b),true);
    40   fillchar(c,sizeof(c),true);
    41   solve(1,1,1);
    42 end.

    例3、电子老鼠闯迷宫

    图中有阴影的部分表示墙,无阴影的部分表示通路。老鼠在迷宫中可以沿上下左右4个方向摸索前进。如下图12×12方格图,找出一条自入口(2,9)到出口(11,8)的最短路径。

    问题分析:

    1.结点定义:以方格为结点,根结点为入口方格,从根结点出发,可向四个方向行走,进入下一状态(即下一格)。

    2.目标状态:方格坐标(x,y=出口坐标(118

    3.搜索范围:上下左右四个方向存到数组d中依次去搜索。

    4.约束条件:设数组map[xy]表示地图,则map[x,y]=1表示墙,map[x,y]=0表示路,若当前方格坐标为x,y,则约束条件为map[x+d[i].x,y+d[i].y]=0。另外我们把访问过的格子置为非0,防止老鼠原地打转。

    5.恢复递归前状态:设step记录步数,即每走一格step1,所以回溯到递归前状态时要将step1,也可以把step定义到过程的形式参数中(注意在形式参数中它相当于一个局部变量),每次回溯后自动恢复原值。另外还需要把递归前所在的格子恢复为0

    6.将每次探寻得到的路径步数step进行筛选,留下最小的一个min

    7.可以定义过程try(x,y,step:integer;),其中x,y表示要扩展访问的方格,step来记录步数

     1 program mouse;
     2   const
     3     dx:array[1..4] of integer=(-1,0,0,1);
     4     dy:array[1..4] of integer=(0,-1,1,0);
     5     map:array[1..12,1..12] of integer=((1,1,1,1,1,1,1,1,1,1,1,1),(1,0,0,0,0,0,0,1,0,1,1,1),
     6                                        (1,0,1,0,1,1,0,0,0,0,0,1),(1,0,1,0,1,1,0,1,1,1,0,1),
     7                                        (1,0,1,0,0,0,0,0,0,0,0,1),(1,0,1,1,1,1,1,1,1,1,1,1),
     8                                        (1,0,0,0,1,0,1,0,0,0,0,1),(1,0,1,1,1,0,0,0,1,1,1,1),
     9                                        (1,0,0,0,0,0,1,0,0,0,0,1),(1,1,1,0,1,1,1,1,0,1,0,1),
    10                                        (1,1,1,1,1,1,1,0,0,1,1,1),(1,1,1,1,1,1,1,1,1,1,1,1));
    11   type zb=record
    12             x,y:integer;
    13           end;
    14   var
    15     i,j,min:integer;
    16     lu,minlu:array[1..150] of zb;
    17   procedure try(x,y,step:integer);
    18     var i,j:integer;
    19     begin
    20           for i:=1 to 4 do
    21              if (map[x+dx[i],y+dy[i]]=0) then
    22                 begin
    23                   map[x+dx[i],y+dy[i]]:=1;
    24                   lu[step].x:=x+dx[i];
    25                   lu[step].y:=y+dy[i];
    26                   if (x+dx[i]=11) and (y+dy[i]=8) then
    27                     begin if step<min then begin min:=step;minlu:=lu;end;end
    28                   else
    29                     try(x+dx[i],y+dy[i],step+1);
    30                   map[x+dx[i],y+dy[i]]:=0;
    31                 end;
    32     end;
    33   begin
    34     min:=maxint;
    35     map[2,9]:=3;
    36     try(2,9,1);
    37     writeln(min);
    38     write('(2,9)');
    39     for i:=1 to min do
    40       begin
    41         write('(',minlu[i].x,',',minlu[i].y,')');
    42         map[minlu[i].x,minlu[i].y]:=3;
    43       end;
    44         writeln;
    45     for i:=1 to 12 do
    46       begin
    47         for j:=1 to 12 do
    48           begin
    49           if map[i,j]=1 then write(char(219));
    50           if map[i,j]=0 then write(' ');
    51           if map[i,j]=3 then write('*');
    52           end;
    53         writeln;
    54       end;
    55   end.
  • 相关阅读:
    前端性能优化:使用异步加载,延迟加载依赖
    超酷的图片标题悬浮特效
    JQuery插件:动态列和无间隙网格布局Mason.js
    如何高效地产生多个不重复的随机数?
    Endnote专题之--output style相关问题
    机器学习中的相似性度量(Similarity Measurement)
    C语言生成服从均匀分布, 瑞利分布, 莱斯分布, 高斯分布的随机数
    MathType中如何批量修改公式字体和大小
    LaTeX 算法代码排版 --latex2e范例总结
    Latex论文写作-Texsdudio 快捷键总结
  • 原文地址:https://www.cnblogs.com/vacation/p/5179457.html
Copyright © 2020-2023  润新知