回溯算法


回溯算法 題目整理 part1 

回溯算法 題目整理 part2 

1、概念

回溯算法實際上一個類似枚舉的搜索嘗試過程,主要是在搜索嘗試過程中尋找問題的解,當發現已不滿足求解條件時,就“回溯”返回,嘗試別的路徑。

回溯法是一種選優搜索法,按選優條件向前搜索,以達到目標。但當探索到某一步時,發現原先選擇並不優或達不到目標,就退回一步重新選擇,這種走不通就退回再走的技術為回溯法,而滿足回溯條件的某個狀態的點稱為“回溯點”。

回溯法(英語:backtracking)也稱試探法,回溯法有“通用的解題方法”之稱。它可以系統的搜索一個問題的所有解或者任意解。

回溯法是一個既帶有系統性又帶有跳躍性的的搜索算法。它在包含問題的所有解的解空間樹中,按照深度優先的策略,從根結點

出發搜索解空間樹。算法搜索至解空間樹的任一結點時,總是先判斷該結點是否肯定不包含問題的解。如果肯定不包含,則跳過

對以該結點為根的子樹的系統搜索,逐層向其祖先結點回溯。否則,進入該子樹,繼續按深度優先的策略進行搜索。回溯法在用來

求問題的所有解時,要回溯到根,且根結點的所有子樹都已被搜索遍才結束。而回溯法在用來求問題的任一解時,只要搜索到問題

的一個解就可以結束。這種以深度優先的方式系統地搜索問題的解的算法稱為回溯法,它適用於解一些組合數較大的問題.

回溯法通常用最簡單的遞歸方法來實現,在反復重復上述的步驟后可能出現兩種情況:

1 .找到一個可能存在的正確的答案。

2. 在嘗試了所有可能的分步方法后宣告該問題沒有答案。

在最壞的情況下,回溯法會導致一次復雜度為指數時間的計算。

2、適用范圍

在包含問題的所有解的解空間樹中,按照深度優先搜索的策略,從根結點出發深度探索解空間樹。

 適用范圍:

1,問題的解用向量表示

  X = (x1, x2, ..., xn)

2,需要搜索一個或一組解

3,滿足約束條件的最優解

回溯法的基本思想

對於用回溯法求解的問題,首先要將問題進行適當的轉化,得出狀態空間樹。這棵樹的每條完整路徑都代表了一種解的可能。通過深度優先搜索

這棵樹,枚舉每種可能的解的情況;從而得出結果。但是,回溯法中通過構造約束函數,可以大大提升程序效率,因為在深度優先搜索的過程中,

不斷的將每個解(並不一定是完整的,事實上這也就是構造約束函數的意義所在)與約束函數進行對照從而刪除一些不可能的解,這樣就不必繼續

把解的剩余部分列出從而節省部分時間。

回溯法中,首先需要明確下面三個概念:

 1,約束函數:約束函數是根據題意定出的。通過描述合法解的一般特征用於去除不合法的解,從而避免繼續搜索出這個不合法解的剩余部分。因此,

       約束函數是對於任何狀態空間樹上的節點都有效、等價的。

2,狀態空間樹:剛剛已經提到,狀態空間樹是一個對所有解的圖形描述。樹上的每個子節點的解都只有一個部分與父節點不同。

3,擴展節點、活結點、死結點:所謂擴展節點,就是當前正在求出它的子節點的節點,在DFS中,只允許有一個擴展節點。活結點就是通過與約束函數

      的對照,節點本身和其父節點均滿足約束函數要求的節點;死結點反之。由此很容易知道死結點是不必求出其子節點的(沒有意義)。

3、用回溯法解題的一般步驟:

首先,要通過讀題完成下面三個步驟:


(1)描述解的形式,定義一個解空間,它包含問題的所有解,這一步主要明確問題的解空間樹。

(2)構造狀態空間樹。

(3)構造約束函數(用於殺死節點)。

然后就要通過DFS思想完成回溯,具體流程如下:

(1)設置初始化的方案(給變量賦初值,讀入已知數據等)。

(2)變換方式去試探,若全部試完則轉(7)。

(3)判斷此法是否成功(通過約束函數),不成功則轉(2)。

(4)試探成功則前進一步再試探。

(5)正確方案還未找到則轉(2)。

(6)已找到一種方案則記錄並打印。

(7)退回一步(回溯),若未退到頭則轉(2)。

(8)已退到頭則結束或打印無解。

 

總結起來就是: 

針對所給問題,確定問題的解空間 --> 確定結點的擴展搜索規則--> 以DFS方式搜索解空間,並在搜索過程中用剪枝函數避免無效搜索。

4、算法框架

(1)問題框架

設問題的解是一個n維向量(a1,a2,………,an),約束條件是ai(i=1,2,3,…..,n)之間滿足某種條件,記為f(ai)。

(2)非遞歸回溯框架

int a[n],
i;初始化數組a[];
i = 1;
while (i > 0(有路可走) and(未達到目標)) // 還未回溯到頭
{
    if (i > n) // 搜索到葉結點
    {搜索到一個解,輸出;
    } else // 處理第i個元素
    {
        a[i]第一個可能的值;
        while (a[i]在不滿足約束條件且在搜索空間內) {
            a[i]下一個可能的值;
        }
        if (a[i]在搜索空間內) {
            19 : 標識占用的資源;20 : i = i + 1; // 擴展下一個結點  21:          }  22:          else  23:         {
            清理所占的狀態空間; // 回溯25:               i = i –1; 
        }
    }

 

(3)遞歸的算法框架

回溯法是對解空間的深度優先搜索,在一般情況下使用遞歸函數來實現回溯法比較簡單,其中i為搜索的深度,框架如下:

int a[n];
    try(int i)
    {
        if(i>n)
           輸出結果;
         else
        {
           for(j = 下界; j <= 上界; j=j+1)  // 枚舉i所有可能的路徑
           {
              if(fun(j))                 // 滿足限界函數和約束條件
                {
                   a[i] = j;
                ...                         // 其他操作
                   try(i+1);
                 回溯前的清理工作(如a[i]置空值等);
                 }
            }
        }
   }

典型問題應用

素數環問題,hdoj1016:http://acm.hdu.edu.cn/showproblem.php?pid=1016

題目大意:

將從1到n這n個整數圍成一個圓環,若其中任意2個相鄰的數字相加,結果均為素數,那么這個環就成為素數環。

要求輸出:從整數1開始。

分析問題,可以構造解空間樹,比較順利的想到 DFS 、回溯。

代碼如下:

 1 #include<stdio.h>  
 2 #include <string.h>  
 3   
 4 #define M 40  
 5   
 6 int isPrime[M];////素數表,下標為素數的置為1,否則0  
 7 int vis[M>>1];// vis 標識 1-n,是否被選  
 8 int res[M>>1];// 存儲解向量  
 9   
10 int cnt;// 測試樣例個數  
11   
12 void prime()//求出1-40的所有素數  
13 {  
14     int i, j;  
15     for(i=1; i<M; ++i)  
16     {  
17         int ok = 1;  
18         for(j=2; j*j<=i; ++j)  
19         {  
20             if(i%j == 0)  
21             {  
22                 ok = 0;  
23                 break;  
24             }  
25         }  
26         if(ok)  
27             isPrime[i]=1;  
28   
29     }  
30 }  
31   
32 void dfs(int cur, int n)  
33 {  
34     int i;  
35     if(cur == n && isPrime[res[n-1] + res[0]])//別忘了測試邊界,最后一個和第一個數 構成的環  
36     {  
37         for(i=0; i<n-1; ++i)  
38             printf("%d ", res[i]);  
39         printf("%d\n", res[i]);  
40     }  
41     else  
42     {  
43         for(i=2; i<=n; i++)// 嘗試每個i, 1始終在排頭,因此從2開始計算  
44         {  
45             if(!vis[i] && isPrime[res[cur-1] + i])// i未用過且和前一個數和為素數  
46             {  
47                 res[cur] = i;  
48                 vis[i] = 1; // 設置標志  
49                 dfs(cur+1, n);  
50                 vis[i] = 0; // 回溯, 清除標識  
51             }  
52         }  
53     }  
54 }  
55   
56   
57 int main()  
58 {  
59     int n;  
60     //freopen("in.txt", "r", stdin);  
61     prime();  
62     cnt = 0;  
63     while(scanf("%d", &n) != EOF)  
64     {  
65         ++cnt;  
66         printf("Case %d:\n", cnt);  
67         memset(vis, 0, sizeof(vis));  
68         res[0] = 1;  
69         dfs(1, n);  
70         printf("\n");  
71     }  
72     return 0;  
73 }  

代碼中 prime()函數 求出1-40的所有素數,因為只要測試 1-19 因此 可以事前 把1-38的素數存儲到一個 素數表里。這樣計算時間更快。

 更多問題:http://www.cnblogs.com/jiangchen/p/5393848.html

 

 


免責聲明!

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



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