搜索與回溯算法(三)


本節學習要點:

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.


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM