广度优先搜索(一)

深度优先搜索回顾

搜索算法的实现,从树的遍历角度讲,有深度优先和广度优先两种。深度优先我们在前边已经介绍过,我们先来简单回顾一下:
     如算法名称那样,深度优先搜索所遵循的搜索策略是尽可能“深”地搜索树。在深度优先搜索中,对于当前发现的结点,如果它还存在以此结点为起点而未探测到的边,就沿此边继续搜索下去,若当结点的所有边都己被探寻过.将回溯到当前结点的父结点,继续上述的搜索过程直到所有结点都被探寻为止。
    深度优先搜索在树的遍历中也称作树的先序遍历。对于树而言,深度优先搜索的思路可以描述为:
    (1)将根结点置为出发结点。
    (2)访问该出发结点.
    (3)依次将出发结点的子结点置为新的出发结点.进行深度优先遍历(执行(2))。
    (4)退回上一层的出发结点。

深度优先搜索图示(以八数码问题为例)

广度优先搜索的过程

广度优先搜索算法(又称宽度优先搜索)是最简便的图的搜索算法之一,这一算法也是很多重要的图的算法的原型。Dijkstra单源最短路径算法和Prim最小生成树算法都采用了和宽度优先搜索类似的思想。
        广度优先算法的核心思想是:从初始节点开始,应用算符生成第一层节点,检查目标节点是否在这些后继节点中,若没有,再用产生式规则将所有第一层的节点逐一扩展,得到第二层节点,并逐一检查第二层节点中是否包含目标节点。若没有,再用算符逐一扩展第二层的所有节点……,如此依次扩展,检查下去,直到发现目标节点为止。即
       ⒈从图中的某一顶点V0开始,先访问V0;
       ⒉访问所有与V0相邻接的顶点V1,V2,......,Vt;
       ⒊依次访问与V1,V2,......,Vt相邻接的所有未曾访问过的顶点;
       ⒋循此以往,直至所有的顶点都被访问过为止。
       这种搜索的次序体现沿层次向横向扩长的趋势,所以称之为广度优先搜索。

广度优先搜索算法描述1:

 1 Program Bfs;
 2 初始化,初始状态存入队列;
 3 队列首指针head:=0; 尾指针tail:=1 4 repeat
 5     指针head后移一位,指向待扩展结点;
 6     for I=1 to max do        //max为产生子结点的规则数
 7        if 子结点符合条件 then
 8           if 新结点与原已产生结点不重复 then
 9               begin
10                  tail指针增1,把新结点存入列尾;
11                  if新结点是目标结点 then 输出并退出;
12               end;
13 until(head>=tail);            //队列为空

广度优先搜索算法描述2:

 1 Program Bfs; 
 2 初始化,初始状态存入队列; 
 3 队列首指针head:=0; 尾指针tail:=1 4 repeat 
 5     指针head后移一位,指向待扩展结点; 
 6     for I=1 to max do        //max为产生子结点的规则数
 7        if 子结点符合条件 then 
 8     if 新结点是目标结点 then 输出
 9      else
10       if新结点与原已产生结点不重复 then 
11         tail 指针增1,把新结点存入队尾; 
12 until(head>=tail);   {队列空}

广度优先搜索注意事项:

        1、每生成一个子结点,就要提供指向它们父亲结点的指针。当解出现时候,通过逆向跟踪,找到从根结点到目标结点的一条路径。当然不要求输出路径,就没必要记父亲。
        2、生成的结点要与前面所有已经产生结点比较,以免出现重复结点,浪费时间,还有可能陷入死循环。
        3、如果目标结点的深度与“费用”(如:路径长度)成正比,那么,找到的第一个解即为最优解,这时,搜索速度比深度搜索要快些,在求最优解时往往采用广度优先搜索;如果结点的“费用”不与深度成正比时,第一次找到的解不一定是最优解。
        4、广度优先搜索的效率还有赖于目标结点所在位置情况,如果目标结点深度处于较深层时,需搜索的结点数基本上以指数增长。

广度优先搜索应用举例

【例1】图4表示的是从城市A到城市H的交通图。从图中可以看出,从城市A到城市H要经过若干个城市。现要找出一条经过城市最少的一条路线。

【算法分析】
看到这图很容易想到用邻接距阵来表示,0表示能走,1表示不能走。如图。

 

首先想到的是用队列的思想。a数组是存储扩展结点的队列,a[i].city记录经过的城市,a[i].pre记录前趋城市,这样就可以倒推出最短线路。具体过程如下:

(1) 将城市A入队,队首为0、队尾为1。

(2) 将队首所指的城市所有可直通的城市入队(如果这个城市在队列中出现过就不入队,可用一个集合来判断),将入队城市的pre指向队首的位置。然后将队首加1,得到新的队首城市。重复以上步骤,直到搜到城市H时,搜索结束。利用pre可倒推出最少城市线路。

【参考程序】

 

 1 Program Ex8_1;
 2 const ju:array[1..8,1..8] of 0..1=((1,0,0,0,1,0,1,1),
 3                           (0,1,1,1,1,0,1,1),
 4                                (0,1,1,0,0,1,1,1),
 5                              (0,1,0,1,1,1,0,1),
 6                             (1,1,0,1,1,1,0,0),
 7                            (0,0,1,1,1,1,1,0),
 8                                    (1,1,1,0,0,1,1,0),
 9                             (1,1,1,1,0,0,0,1));
10 type node=record                   //记录定义
11                  city: char;
12                 pre: integer;
13      end;
14 var  head,tail,i:integer;
15     a:array [1..100] of node; 
16    s:set of 'A'..'H';
17 procedure out(d:integer);          //输出过程
18 begin
19    write(a[d].city); 
20    repeat
21    d:=a[d].pre;   write('--',a[d].city);
22    until a[d].pre=0;
23    writeln;
24 end;
25 procedure doit;
26 begin
27    head:=0; tail:=1;
28    a[1].city:=‘A’;
29    a[1].pre:=0;
30    s:=[‘A’];
31    repeat
32    inc(head);             //队首加一,出队
33    for i:=1 to 8 do       //搜索可直通的城市
34   if (ju[ord(a[head].city)-64,i]=0andnot(chr(i+64in s))then             
35                                         //判断城市是否走过
36      begin
37           inc(tail);        //队尾加一,入队
38           a[tail].city:=chr(64+i);
39      a[tail].pre:=head;
40      s:=s+[a[tail].city];
41      if a[tail].city='H' then  begin out(tail);break;end;
42    end;
43  until head=tail;
44 end;

输出:

   H--F--A

【例2】一矩形阵列由数字0到9组成,数字1到9代表细胞,细胞的定义为沿细胞数字上下左右还是细胞数字则为同一细胞,求给定矩形阵列的细胞个数。如:

阵列 

4  10

0234500067

1034560500

2045600671

0000000089

有4个细胞。

【算法分析】

  ⑴从文件中读入m*n矩阵阵列,将其转换为boolean矩阵存入bz数组中;
  ⑵沿bz数组矩阵从上到下,从左到右,找到遇到的第一个细胞;
  ⑶将细胞的位置入队h,并沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为FLASE;

  ⑷将h队的队头出队,沿其上、下、左、右四个方向上的细胞位置入队,入队后的位置bz数组置为FLASE;
  ⑸重复4,直至h队空为止,则此时找出了一个细胞;
  ⑹重复2,直至矩阵找不到细胞;
  ⑺输出找到的细胞数。

 1 Program Ex8_2;
 2 const   dx:array[1..4] of -1..1=(-1,0,1,0);  //上、右、下、左
 3       dy:array[1..4] of -1..1=(0,1,0,-1);
 4 var  name,s:string;
 5    pic:array[1..50,1..79] of integer;
 6    bz:array[1..50,1..79] of boolean;
 7    m,n,i,j,num:integer;
 8    h:array[1..4000,1..2] of integer;
 9 procedure doit(p,q:integer);
10 var i,t,w,x,y:integer;
11 begin
12    inc(num);bz[p,q]:=false;
13    t:=1;w:=1;h[1,1]:=p;h[1,2]:=q;      //遇到的第一个细胞入队
14    repeat
15    for i:=1 to 4 do                 //沿细胞的上下左右四个方向搜索细胞
16    begin 
17      x:=h[t,1]+dx[i];y:=h[t,2]+dy[i];
18     if (x>0) and (x<=m) and (y>0) and (y<=n) and bz[x,y]
19     then begin inc(w);h[w,1]:=x;h[w,2]:=y;bz[x,y]:=false;end;
20         end;                     //本方向搜索到细胞就入队
21    inc(t);                        //队头指针加1
22     until t>w;                     //直至队空为止
23 end;
24 begin
25    fillchar(bz,sizeof(bz),true); num:=0;
26    readln(m,n);
27    for i:=1 to m do
28   begin 
29        readln(s);
30     for j:=1 to n do
31       begin 
32            pic[i,j]:=ord(s[j])-ord(‘0’);
33          if pic[i,j]=0 then bz[i,j]:=false; 
34       end;
35    end;
36    for i:=1 to m do
37      for j:=1 to n do 
38        if bz[i,j] then doit(i,j);             //在矩阵中寻找细胞
39    writeln('NUMBER of cells=',num);
40    readln;
41 end.

【例3】迷宫问题

       如下图所示,给出一个N*M的迷宫图和一个入口、一个出口。

       编一个程序,打印一条从迷宫入口到出口的路径。这里黑色方块的单元表示走不通(用-1表示),白色方块的单元表示可以走(用0表示)。只能往上、下、左、右四个方向走。如果无路则输出“no way.”。

【算法分析】

  只要输出一条路径即可,所以是一个经典的回溯算法问题,本例给出了回溯(深搜)程序和广搜程序。实现见参考程序。

【深搜参考程序】

 1 program EX8_4_1;
 2 const maxn=50;
 3 var   map:array [1..maxn,1..maxn] of integer;
 4       f:boolean;
 5       n,m,i,j,desx,desy,soux,souy,totstep:integer;
 6       route:array[1..maxn] of record
 7                                 x,y:integer;
 8                               end; 
 9 procedure  move(x,y,step:integer);
10 begin
11    map[x,y]:=step;             //走一步,作标记,设置为非0,标记为已走过
12    route[step].x := x;  route[step].y:= y;     //记路径
13    if (x=desx) and (y=desy)  then begin f:=true; totstep:=step; end
14    else
15     begin  
16       if (y<>m) and (map[x,y+1]=0) then  move (x,y+1,step+1);        //向右
17       if not f and (x<>n)and(map[x+1,y]=0) then move(x+1,y,step+1);  //往下
18       if not f and (y<>1)and(map[x,y-1]=0) then move(x,y-1, step+1); //往左 
19       if not f and (x<>1)and(map[x-1,y]=0) then move(x-1,y, step+1); //往上
20     end;
21 end;
22 BEGIN
23   readln(n,m);               //n行m列的迷宫
24   for i:=1  to  n  do        //读入迷宫,0表示通,-1表示不通
25     begin
26        for j:=1  to  m  do  read(map[i,j]);
27        readln;
28     end;
29   write('input the enter:');  
30   readln(soux,souy);              //入口
31   write('input the exit:');   
32   readln(desx,desy);              //出口
33   f:=false;                       //f=false表示无解;f=true表示找到了一个解
34   move(soux,souy,1);
35   if f then
36     for i:=1  to totstep do       //输出直迷宫的路径
37        write(route[i]:4);
38   else  writeln ('no way.');
39 END.

【广搜参考程序】

 1 program EX8_4_2;
 2 const maxn=50;
 3            u:array[1..4] of integer=(0,1,0,-1);     //右、下、左、上
 4            w:array[1..4] of integer=(1,0,-1,0);
 5 var map:array [1..maxn,1..maxn] of integer;
 6     f:boolean;
 7     n,m,i,j,desx,desy,soux,souy,head,tail,x,y:integer;
 8     route:array[1..maxn] of record
 9                               x,y,pre:integer;
10                             end;
11 procedure print(d:integer);
12 begin
13   if route[d].pre<>0 then print(route[d].pre);
14   write('(',route[d].x,',',route[d].y,')');
15 end;
16 BEGIN
17   readln(n,m);              //n行m列的迷宫
18   for i:=1  to  n  do       //读入迷宫,0表示通,-1表示不通
19     begin
20        for j:=1  to  m  do  read(map[i,j]);
21        readln;
22     end;
23   write('input the enter:');
24   readln(soux,souy);           //入口 
25   write('input the exit:'); 
26   readln(desx,desy);           //出口
27   head:=0;   tail:=1;   f:=false;   map[soux,souy]:=-1;
28   route[tail].x:=soux;     route[tail].y:=souy;    route[tail].pre:=0;
29   while head<>tail do          //队列不为空
30   begin               
31     inc(head);
32     for i:=1 to 4 do         //4个方向
33     begin
34       x:=route[head].x+u[i];   y:=route[head].y+w[i];
35       if (x>0)and(x<=n)and(y>0)and(y<=m)and(map[x,y]=0)     then    
36         begin                        //本方向上可以走
37           inc(tail);
38           route[tail].x:=x;  route[tail].y:=y; route[tail].pre:=head;   map[x,y]:=-1;
39           if (x=desx)and(y=desy) then            //扩展出的结点为目标结点          
40              begin
41                 f:=true;   print(tail);
42                 break;  
43              end;
44         end;
45     end;
46     if f then break;
47   end;
48   if not f then writeln('no way!');
49 END.

输入1:

输出1:

输入2:

输出2:

8  5

-1  -1 -1  -1 -1

 0  0  0  0  -1

-1  -1 -1  0  -1

-1  0  0  0  -1

-1  0  0  -1 -1

-1  0  0  0  -1

-1  -1 -1  0  -1

-1  0  0  0  -1

2 1

8 4

  -1  -1  -1  -1  -1

   1  2   3   4  -1

  -1  -1  -1   5  -1

  -1   0   7   6  -1

  -1   0   8  -1  -1

  -1   0   9  10  -1

  -1  -1  -1  11  -1

  -1   0   0  12  -1

8 5

-1  -1 -1 -1 -1

 0  0  0  0 -1

-1  -1 -1  0 -1

-1  0  0  0 -1

-1  0  0 -1 -1

-1  0  0  0 -1

-1  -1 -1 -1 -1

-1  0  0  0 -1

2 1

8 4

no way.

【例4】迷宫问题2

图 8所示的迷宫图,其中阴影部分表示不通,处于迷宫中的每个位置都可以有 8个方向探索可行路径前进,假设入口设在最左上角,出口位置设在最右下角,编写一个程序,找出一条从入口到出口的最短路径。如果不唯一,只要输出任一个即可。

【算法分析】

(1)对于这样的迷宫依然可以用一个邻接矩阵表示(0 表示通,1 表示不通),如图 9 所示;为了克服在移动过程考虑出界的问题,我们给这个邻接矩阵增加了一圈,即人为地加上一个边界,如图10所示:

(2)从(X,Y)探索8 个方向的表示如下:

8个方向表示可以用一个增量数组:

Type   node= record

              x,y : -1..1 ; 

            end;                   

var  F:array[1..8] of  node ;                   

(3)如何把探索的踪迹记录下来:用队列完成,其数据结构如下:

Type   sqtype=array[1..m*n]  of  record 

                                x,y : integer ;

                                 pre: 0..m*n  ;

                              end;

(4)如何防止重复探索:将迷宫中的0替换为-1,或者对已扩展过的点进行判重,队列中入队、出队情况如下表:

(5)输出:(1,1)-->(2,2)-->(3,3)-->(3,4)-->(4,5)-->(4,6)-->(5,7)-->(6,8)

 

posted @ 2016-02-05 19:58  ZJQCation  阅读(4121)  评论(0编辑  收藏  举报