搜索與回溯算法(一)


簡單深度優先搜索問題

搜索與回溯是計算機解題中常用的算法,很多問題無法根據某種確定的計算法則來求解,可以利用搜索與回溯的技術求解。回溯是搜索算法中的一種控制策略。它的基本思想是:為了求得問題的解,先選擇某一種可能情況向前探索,在探索過程中,一旦發現原來的選擇是錯誤的,就退回一步重新選擇,繼續向前探索,如此反復進行,直至得到解或證明無解。
       如迷宮問題:進入迷宮后,先隨意選擇一個前進方向,一步步向前試探前進,如果碰到死胡同,說明前進方向已無路可走,這時,首先看其它方向是否還有路可走,如果有路可走,則沿該方向再向前試探;如果已無路可走,則返回一步,再看其它方向是否還有路可走;如果有路可走,則沿該方向再向前試探。按此原則不斷搜索回溯再搜索,直到找到新的出路或從原路返回入口處無解為止。


遞歸回溯法算法框架

 1 procedure Search(k:integer);
 2 begin
 3    for i:=1 to 算符種數 Do
 4      if  滿足條件 then 
 5        begin
 6           保存結果
 7           if  到目的地 then 輸出解
 8             else Search(k+1);
 9           恢復:保存結果之前的狀態{回溯一步}
10        end;
11 end;
遞歸回溯法算法框架[一]
 1 procedure Search(k:integer);
 2 begin
 3   if  到目的地 then 輸出解
 4     else
 5        for i:=1 to 算符種數 Do
 6          if  滿足條件 then 
 7            begin
 8               保存結果
 9               Search(k+1,參數表);
10               恢復:保存結果之前的狀態
11           end;
12 end;
遞歸回溯法算法框架[二]
 1 procedure Search(k:integer);
 2 begin
 3   if  到目的地 then
 4      begin
 5         輸出解;
 6         exit;
 7       end;
 8   for i:=1 to 算符種數 Do
 9        if  滿足條件 then 
10            begin
11               保存結果
12               Search(k+1,參數表);
13               恢復:保存結果之前的狀態    
14           end;
15 end;
遞歸回溯法算法框架[三]

 

【例1】求從1~4中任意挑出3個數的所有組合方案。

1 For i:=1 to 4 do
2   For j:=i+1 to 4 do
3      For k:=j+1 to 4 do writeln(i,j,k);
直接枚舉
1 Procedure DFS(k:integer);
2   Var i:integer;
3   Begin
4       If k>3 then begin print(Ans);exit;end;
5       For i:=1 to 4 do
6         if i>Ans[k-1] then 
7           begin Ans[k]:=I; DFS(k+1);end;
8   End;
深搜

【例2】求從1~N中任意挑出m個數的所有組合方案。

1 Procedure DFS(k:integer);
2   Var i:integer;
3   Begin
4      If k>M then begin print(Ans);exit;end;
5    For i:=1 to N do
6       if i>Ans[k-1] then 
7           begin Ans[k]:=I; DFS(k+1);end;
8 End;
深搜
 1 type arr=array[0..50] of integer;
 2 var  n,m,i:integer;
 3        ans:arr;
 4 procedure print(a:arr);
 5   begin
 6     for i:=1 to m do write(ans[i]); writeln;
 7   end;
 8 procedure dfs(k:integer);
 9   var i:integer;
10   begin
11     if k>m then begin print(ans);exit;end;
12     for i:=ans[k-1]+1 to n do 
13        begin  ans[k]:=I; dfs(k+1);  end;
14   end;
15 begin
16   readln(n,m);
17   dfs(1);
18 end.
完整程序

【例3】求1,2,3三個數的全排列並輸出。

1 For i1:=1 to 3 do
2   For i2:=1 to 3 do
3     For i3:=1 to 3 do
4      If (i1<>i2) and (i2<>i3)and(i3<>i1) 
5        then writeln(i1,i2,i3);
直接枚舉
1 For i1:=1 to 3 do
2   For i2:=1 to 3 do
3     If i2<>i1 then
4       For i3:=1 to 3 do
5        If (i2<>i3) and (i1<>i3) then
6           writeln(i1,i2,i3);
剪枝枚舉
 1 S:=[];
 2     For i1:=1 to 3 do begin
 3       S:= S + [i1];
 4       For i2:=1 to 3 do
 5         If not (i2 in S) then begin
 6          S:= S + [i2];
 7          For i3:=1 to 3 do 
 8            If not(i3 in S) then writeln(i1,i2,i3);
 9          S:=S-[i2];
10         End;
11       S:=S-[i1];
12      End;
集合
 1 Procedure DFS(k:integer);
 2   Var i:integer;
 3   Begin
 4     If k>3 then begin print(Ans);exit;end;
 5     For i:=1 to 3 do 
 6       If not(i in S) then begin
 7         S:=S+[i];
 8         Ans[k]:=i;
 9         DFS(k+1);
10         S:=S-[i];
11       End; 
12    End;
深搜

【例4】求從1~N中任意挑出m個數的所有排列方案。

 1 Procedure DFS(k:integer);
 2   Var i:integer;
 3   Begin
 4      If k>M then begin print(Ans);exit;end;
 5      For i:=1 to N do 
 6        If not(i in S) then begin
 7          S:=S+[i];
 8          Ans[k]:=i;
 9          DFS(k+1);
10          S:=S-[i];
11        End; 
12   End;
解法一
 1 Procedure DFS(s:se;k:integer);
 2   Var i:integer;
 3   Begin
 4      If k>M then begin print(Ans);exit;end;
 5      For i:=1 to N do 
 6        If not(i in S) then begin
 7          Ans[k]:=i;
 8          DFS(s+[i],k+1);
 9         End; 
10   End;
解法二

由上邊的程序代碼,我們可以看出深搜算法其實也是枚舉,只不過采用了遞歸結構使枚舉的層數可以更多,代碼結構更清晰罷了。

注意:由於遞歸結構在pascal里使用的是系統棧,空間有限,所以盡量不要在子程序里定義太多變量甚至數組,否則遞歸層數多了容易造成系統棧溢出,建議能定義成全局變量的盡量用全局變量,如果使用了全局變量,注意遞歸后要恢復保存前的狀態

【例5】八皇后問題:要在國際象棋棋盤中放八個皇后,使任意兩個皇后都不能互相吃。(提示:皇后能吃同一行、同一列、同一對角線的任意棋子)

放置第i個(行)皇后的算法為:

 1 procedure Search(i);
 2 begin
 3   for 第i個皇后的位置=1 to 8 do;       //在本行的8列中去試
 4    if 本行本列允許放置皇后 then
 5     begin
 6      放置第i個皇后;
 7                   對放置皇后的位置進行標記;
 8      if i=8 then 輸出                 //已經放完個皇后
 9         else Search(i+1);          //放置第i+1個皇后
10      對放置皇后的位置釋放標記,嘗試下一個位置是否可行;
11     end12 end
View Code 

【算法分析】
        顯然問題的關鍵在於如何判定某個皇后所在的行、列、斜線上是否有別的皇后;

        可以從矩陣的特點上找到規律,如果在同一行,則行號相同;

                                               如果在同一列上,則列號相同;

                                               如果同在/ 斜線上的行列值之和相同;

                                               如果同在\ 斜線上的行列值之差相同;

從下圖可驗證:

考慮每行有且僅有一個皇后,設一維數組A[1..8]表示皇后的放置:第i行皇后放在第j列,用A[i]=j來表示,即下標是行數內容是列數。例如:A[3]=5就表示第3個皇后在第3行第5列上。

      判斷皇后是否安全,即檢查同一列、同一對角線是否已有皇后,建立標志數組b[1..8]控制同一列只能有一個皇后,若兩皇后在同一對角線上,則其行列坐標之和行列坐標之差相等,故亦可建立標志數組c[1..16]、d[-7..7]控制同一對角線上只能有一個皇后。
      如果斜線不分方向,則同一斜線上兩皇后的行號之差的絕對值與列號之差的絕對值相同。在這種方式下,要表示兩個皇后I和J不在同一列或斜線上的條件可以描述為:A[I]<>A[J] AND ABS(I-J)<>ABS(A[I]-A[J]){I和J分別表示兩個皇后的行號}

 1 program ex5_4;
 2 var  a:array[1..8] of integer;
 3     b:array[1..8] of boolean;
 4     c:array[1..16] of boolean;
 5     d:array[-7..7] of boolean;
 6     sum:integer;
 7 procedure print;
 8 var   i:integer;
 9 begin
10    inc(sum); writeln(' sum=',sum);    //方案數累加1
11    for i:=1 to 8 do write(a[i]:4);    //輸出一種方案
12 end;
13 procedure Search(t:integer);
14 var   j:integer;
15 begin
16     for j:=1 to 8 do                 //每個皇后都有8位置(列)可以試放
17        if b[j] and c[t+j] and d[t-j] then      //尋找放置皇后的位置
18         begin                            //放置皇后,建立相應標志值
19            a[t]:=j;                        //擺放皇后
20         b[j]:=false;                    //宣布占領第j列
21         c[t+j]:=false; d[t-j]:=false;   //占領兩個對角線
22       if t=8 then print                 //個皇后都放置好,輸出
23           else Search(t+1);                //繼續遞歸放置下一個皇后
24      b[j]:=true;              //遞歸返回即為回溯一步,當前皇后退出
25      c[t+j]:=true;  d[t-j]:=true;
26        end;
27 end;
28 BEGIN           
29  fillchar(b,sizeof(b),#1);        //數組b、c、d初始化,賦初值True
30  fillchar(c,sizeof(c),#1);
31  fillchar(d,sizeof(d),#1);
32  sum:=0;                     //用於統計方案數
33  Search(1);             //從第1個皇后開始放置
34 END.
參考程序

【例】任何一個大於1的自然數n,總可以拆分成若干個小於n的自然數之和。

當n=7共14種拆分方法:

7=1+1+1+1+1+1+1

7=1+1+1+1+1+2

7=1+1+1+1+3

7=1+1+1+2+2

7=1+1+1+4

7=1+1+2+3

7=1+1+5

7=1+2+2+2

7=1+2+4

7=1+3+3

7=1+6

7=2+2+3

7=2+5

7=3+4

total=14

 1 【參考程序】
 2 program ex5_3;
 3 var a:array[0..100]of integer;
 4       n,t,total:integer;
 5 procedure print(t:integer);
 6 var i:integer;
 7 begin
 8   write(n,'=');
 9   for i:=1 to t-1 do  write(a[i],'+');   
10       //輸出一種拆分方案
11   writeln(a[t]);
12   total:=total+1;                    
13       //方案數累加1
14 end;
15 procedure Search(s,t:integer);
16 var i:integer;
17 begin
18    for i:=1 to s do
19      if (a[t-1]<=i)and(i<n) then       
20    //當前數i要大於等於前1位數,
21       且不過n
22 begin
23         a[t]:=i;                     
24         //保存當前拆分的數i
25         s:=s-a[t];                   
26         //s減去數i, s的值將繼續拆分
27         if s=0 then print(t)          
28         //當s=0時,拆分結束輸出結果
29           else Search(s,t+1);    
30         //當s>0時,繼續遞歸
31         s:=s+a[t];                
32         //回溯:加上拆分的數,
33             以便產分所有可能的拆分
34       end;
35 end;
36 BEGIN
37   readln(n);
38   Search(n,1);                     
39       //將要拆分的數n傳遞給s
40   writeln('total=',total);             
41      //輸出拆分的方案數
42   readln;
43 END.

 


免責聲明!

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



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