4.3 宽度优先搜索

宽度优先搜索(又称广度优先搜索)与深度优先搜索同为搜索,但策略完全不同。

深度优先问题就是一般所说的递归、回溯问题,每次沿一个节点展开其下层一个节,然后再下层一个节点,直到找到一个结果为止再返回上层。

宽度优先搜索是每次把当前层每个节点对应的下一层所有节点全部展开,再判断是否有目标状态出现,如果有则这就是最少步数的答案;否则再把展开的新一层的下一层全部展开,直到展开的节点中出现目标状态为止。

宽度优先搜索问题中的每个节点要存放哪些数据是很重要的,一般用record型自定义型数据类型存放。一般至少要存放的数据有:父节点、当前状态数据。父节点是用来存放其父节点,以便在找到结果时打印答案。另外,宽度搜索中要判重(一般通过一个逻辑数组)。

 

广度优先搜索基本框架:

Procedure Bfs;

Begin 

把所有结点标为“未访问”

将起点入队,并标记为“已访问”

  repeat

for 检查所有与队首结点相连的结点 do

 begin

if 新结点合法 且 新结点没有被访问过 then

 begin

将新结点放入队列

计算新结点的最短路径值(=到达它的结点的路径值+1)

如果该新结点就是目标点,则输出最短路径值并退出

      end;

end;

队首出队  (inc(head))

队列为空   (head>tail)

输出无解信息。(有解的已经在上面直接退出了)

End;

 

另外,如果题目没有保证初始状态一定不等于目标状态,需要在运行BFS之前特判一下是否发生初始状态直接和目标状态相等的情况。

 

深搜和广搜的区别:

深搜并不能保证第一次遇到目标点就是最短路径,因此要搜索所有可能的路径,因此要回溯,标记做了之后还要取消掉,因此同一个点可能被访问很多很多次。而广搜由于它的由近及远的结点扩展顺序,结点总是以最短路径被访问。一个结点如果第二次被访问,第二次的路径肯定不会比第一次的短,因此就没有必要再从这个结点向周围扩展――第一次访问这个结点的时候已经扩展过了,第二次再扩展只会得到更差的解。因此做过的标记不必去掉。因此同一个点至多只可能被访问一次。每访问一个结点,与它相连的边就被检查一次。因此最坏情况下,所有边都被检查一次,因此时间复杂度为O(E)。

 

例9:有两个无刻度标志的水壶,分别可装x升和y升(x,y为整数,x、y<=100)的水。设另有一水缸,可用来向水壶灌水或倒出水,两水壶间,水也可以相互倾灌。已知x升为满壶,y升为空壶。问如何通过倒水或灌水操作用最少步数能在y升壶中量出z(z<=100)升的水来。

输入样例1:

8 5 3
输出样例1: 
step 0:8 0
step 1:3 5
step 2:3 0
step 3:0 3
 
type
 atype=record
   father,a,b:integer;
  end;
 btype=array [0..100,0..100] of boolean;
var x,y,z:integer;
    data:array [1..10000] of atype;
    bool:^btype;
procedure bfs;
 var i,j,k,L:integer; open,closed:integer;
 function min(a,b:integer):integer;
  begin
   if a<b then min:=a else min:=b;
  end;
 procedure out;
  var i,j:integer; d:array [1..10000] of integer;
  begin
   j:=0; i:=closed;
   repeat
    inc(j); d[j]:=i; i:=data[i].father;
   until i=0;
   for i:=j downto 1 do
    writeln('step ',j-i,':',data[d[i]].a,' ',data[d[i]].b);
   close(input);close(output);
   halt;
  end;
 procedure evaluate(i,j:integer);
  begin
   bool^[i,j]:=false; inc(closed);
   data[closed].a:=i; data[closed].b:=j;
   data[closed].father:=open;
   if j=z then out;
  end;
 begin
  new(bool);
  fillchar(bool^,sizeof(bool^),true);
  open:=0; closed:=1; bool^[x,0]:=false;
  data[1].a:=x; data[1].b:=0; data[1].father:=0;
  if (z=x) or (z=0) then out;
  repeat
   inc(open); i:=data[open].a; j:=data[open].b;
   k:=min(i,y-j);
   L:=min(j,x-i);
   if (i>0) and (j<y) and (bool^[i-k,j+k]) then evaluate(i-k,j+k);
   if (j>0) and (i<x) and (bool^[i+L,j-L]) then evaluate(i+L,j-L);
   if (i>0) and (bool^[0,j]) then evaluate(0,j);
   if (j>0) and (bool^[i,0]) then evaluate(i,0);
   if (i<x) and (bool^[x,j]) then evaluate(x,j);
   if (j<y) and (bool^[i,y]) then evaluate(i,y);
  until open>=closed;
  writeln('No answer!');
  close(input); close(output);
 end;
Begin
 assign(input,'input.txt'); reset(input);
 assign(output,'output.txt'); rewrite(output);
 readln(x,y,z); bfs;
end.
修改后程序:
program aa;                
  type atype=record
         father,a,b:integer; 
        end;  
  var x,y,z,q:integer;
      data:array[-2..10000] of atype;   
      bool:array[-2..100,0..100] of boolean;      
  procedure bfs;
  var i,j,k,l:integer;
      f,r:integer;
    function min(a,b:integer):integer;
    begin
      if a<b then min:=a
      else min:=b;
    end;
    procedure out;     
    var i,j:integer;
        d:array[1..10000] of integer;
    begin
      j:=0;  i:=r;
      repeat            
        j:=j+1;
        d[j]:=i;
        i:=data[i].father;  
      until i=0;
      for i:=j downto 1 do  
        writeln('step',j-i:3,':',data[d[i]].a:5,data[d[i]].b:5);
编号
      halt;
    end;
  procedure fuzhi(i,j:integer); 
    begin
      bool[i,j]:=true; inc(r);
      data[r].a:=i; data[r].b:=j;
      data[r].father:=f;
      if j=z then out;           
    end;
  begin      
    if z>y then exit;                                 
    fillchar(bool,sizeof(bool),false);
    f:=0; r:=1; bool[x,0]:=true;
    data[1].a:=x; data[1].b:=0; data[1].father:=0;
    if (z=x)or(z=0) then out; 
    repeat
      inc(f); i:=data[f].a;  j:=data[f].b;
      k:=min(i,y-j); l:=min(j,x-i);    
      if (i>0)and(j<y)and not(bool[i-k,j+k]) then fuzhi(i-k,j+k);  
      if (j>0)and(i<x)and not(bool[i+l,j-l]) then fuzhi(i+l,j-l);
 
      if (j<y)and not(bool[i,y]) then fuzhi(i,y);       
      if (i<x)and not(bool[x,j]) then fuzhi(x,j);      
 
      if (i>0)and not(bool[0,j]) then fuzhi(0,j);     
      if (j>0)and not(bool[i,0]) then fuzhi(i,0);       
    until f>=r;
  end;
  begin
    readln(x,y,z);
    bfs;
    writeln('NO');
  end.
例10:砝码称重。给定n种砝码(每种个数不限)和一个整数M,求至少需要几个砝码才可以称出刚好M克。(n<=100, M<=1000)
输入:n, m,n个整数Wi表示砝码的质量。
输出:最少需要的砝码数或Impossible表示无法满足。
 
const maxn=100; maxm=1000;
var  n,m:integer; w:array [1..maxn] of integer;
    dist:array [0..maxm] of integer;  
    queue:array [1..maxm+1] of integer;
procedure init;
 var i:integer;
 begin
  read(n,m);
  for i:=1 to n do read(w[i]);
 end;
procedure solve;
 var cl,op,k,i:integer;
 begin
  cl:=0; op:=1;
  fillchar(dist,sizeof(dist),255);  -1
  dist[0]:=0;
  queue[1]:=0;
  while cl<op do
   begin
    inc(cl); k:=queue[cl];
    for i:=1 to n do
     if (k+w[i]<=m) and (dist[k+w[i]]= -1) then
      begin
       dist[k+w[i]]:=dist[k]+1;
       inc(op);
       queue[op]:=k+w[i];
      end;
   end;
  if dist[m]= -1 then writeln('Impossible') else writeln(dist[m]);
 end;
Begin
 init;
 solve;
End.
 
修改后程序:
program aa;
  var w:array[1..100] of integer;
      q,dis:array[0..1000] of integer;
      n,m:integer;                   
  procedure init;
  var i:integer;
  begin
    readln(n,m);
    for i:=1 to n do read(w[i]);
  end;
  procedure guang;
  var f,r,k,i:integer;
  begin
    fillchar(dis,sizeof(dis),0); 
    f:=0; r:=1;
    q[1]:=0;  
    while f<r do 
      begin
        inc(f); k:=q[f];   
        for i:=1 to n do
注意条件:能加入k, 且未加入过
         begin
当前所需个数为 上一个的 加一  
           inc(r); q[r]:=k+w[i];
         end;
      end;
未加入
    else writeln(dis[m]);
  end;
  begin
    init;
    guang;
  end.
例11:翻币问题。有N个硬币(6≤N≤8000)正面朝上排成一排,每次将5个硬币翻过来放在原来位置,直到最后全部翻成反面朝上为止。编写程序让计算机找出步数最少的翻法,并把翻币过程及次数打印出来(用O表示正面,*表示反面)。
 
输入样例:
     6
输出样例:
setp0:oooooo
step1:*****o
step2:oooo**
step3:***ooo
step4:oo****
step5:*ooooo
step6:******
 
var n:integer;
    a:array [1..8000,1..3] of integer;
    b:array [0..800] of boolean;
procedure calc;
 var r,m:integer; open,closed:integer;
 procedure out;
  var i,j,k,L,h:integer;
      d:array [1..5000] of integer;
      st:array [1..5000] of char;
  begin
   write('step 0:');
   for i:=1 to n do write('o');
   writeln;
   for i:=1 to n do st[i]:='o';
   j:=0; i:=closed;
   repeat
    j:=j+1; d[j]:=i; i:=a[i,1];
   until i=0;
   for i:=j-1 downto 1 do
    begin
     k:=a[d[i],3]; L:=5-k;
     for h:=1 to n do
      if st[h]='o' then
       begin
        if k>0 then begin dec(k); st[h]:='*'; end;
       end else
       begin
        if L>0 then begin dec(L); st[h]:='o'; end;
       end;
     write('step ',j-i,':');
     for h:=1 to n do write(st[h]);
     writeln;
    end;
   close(input); close(output);
   halt;
  end;
 begin
  fillchar(b,sizeof(b),true);
  b[n]:=false; open:=0; closed:=1;
  a[1,1]:=0; a[1,2]:=n; a[1,3]:=0;
  repeat
   inc(open);
   m:=a[open,2];
   for r:=0 to 5 do
    if (m>=r) and (n-m>=5-r) and (b[m-r+5-r]) then
     begin
      inc(closed);
      b[m-r+5-r]:=false;
      a[closed,1]:=open;
      a[closed,2]:=m-r+5-r;
      a[closed,3]:=r;
      if a[closed,2]=0 then out;
     end;
  until open>=closed;
  writeln('No answer!');
  close(input);close(output);
 end;
Begin
 assign(input,'input.txt'); reset(input);
 assign(output,'output.txt'); rewrite(output);
 read(n);
 calc;
end.
 
修改后程序:
program aa;
  var n:integer;                                     
为前驱, a[i,2]为正面朝上的个数,a[i,3]为反面朝上的个数
为 正面的个数为 i 的
  procedure calc;        
  var k,m:integer;           
      f,r:integer;              
    procedure out;                   
    var i,j,k,l,h:integer;
        d:array[1..5000] of integer;
        st:array[1..5000] of char;
    begin
      for i:=1 to n do st[i]:='o';
      j:=0; i:=r;
此循环将 结点 逆序存入 d数组
        inc(j);
        d[j]:=i; i:=a[i,1];
      until i=0;
逆序处理
        begin
          k:=a[d[i],3]; l:=5-k;  
          for h:=1 to n do     
当前为 正面的,取反
            begin
              if k>0 then
               begin
                 dec(k); st[h]:='*';  
               end;
            end
当前为 反面的,取反
            if l>0 then
             begin
               dec(l);st[h]:='o';     
             end;
            end;
          write('step',j-i:2,':');
          for h:=1 to n do write(st[h]);
          writeln;
        end;
      halt;
    end;
  begin
    fillchar(b,sizeof(b),false);
    b[n]:=true;
    f:=0; r:=1;
    a[1,1]:=0; a[1,2]:=n; a[1,3]:=0;
    repeat                                     
      inc(f);                                 
取正面朝上的                 
能翻的次数(即反面的个数) 
      if (m>=k)and(n-m>=5-k)and not (b[m-k+5-k]) then 
        begin                                
          inc(r); b[m-k+5-k]:=true;            
          a[r,1]:=f;                           
正面的个数:原来正-该次反+(该次正)
          a[r,3]:=k;
          if a[r,2]=0 then out;
        end;
    until f>=r;
    writeln('NO');
  end;
  begin
    readln(n);
    calc;
  end.