回溯深搜與剪枝初步


回溯算法也稱試探法,一種系統的搜索問題的解的方法,是暴力搜尋法中的一種。回溯算法的基本思想是:從一條路往前走,能進則進。回溯算法解決問題的一般步驟:

  • 根據問題定義一個解空間,它包含問題的解
  • 利用適於搜索的方法組織解空間
  • 利用深度優先法搜索解空間,並且在搜索過程中用剪枝函數避免無效搜索

回溯法采用試錯的思想,它嘗試分步的去解決一個問題。在分步解決問題的過程中,當它通過嘗試發現現有的分步答案不能得到有效的正確的解答的時候,它將取消上一步甚至是上幾步的計算,再通過其它的可能的分步解答再次嘗試尋找問題的答案。回溯法通常用最簡單的遞歸方法來實現,在反復重復上述的步驟后可能出現兩種情況:

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

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

在最壞的情況下,回溯法會導致一次復雜度為指數時間的計算,因此通常只有對小規模輸入問題求解的時候采用回溯法,並且用剪枝函數來提速。

使用了剪枝技術的回溯法是一個即帶有系統性又帶有跳躍性的搜索算法。回溯法在用來求問題的所有解時,要回溯到根,且根節點的所有子樹都已被搜索遍才結束。而如果用來求問題的任一解時,只要搜索到問題的一個解就可以結束。簡單的介紹過后,還是看一道暴經典的習題吧,題目取自HDU_1010:Tempter of the Bone

Problem Description The doggie found a bone in an ancient maze, which fascinated him a lot. However, when he picked it up, the maze began to shake, and the doggie could feel the ground sinking. He realized that the bone was a trap, and he tried desperately to get out of this maze.

The maze was a rectangle with sizes N by M. There was a door in the maze. At the beginning, the door was closed and it would open at the T-th second for a short period of time (less than 1 second). Therefore the doggie had to arrive at the door on exactly the T-th second. In every second, he could move one block to one of the upper, lower, left and right neighboring blocks. Once he entered a block, the ground of this block would start to sink and disappear in the next second. He could not stay at one block for more than one second, nor could he move into a visited block. Can the poor doggie survive? Please help him.
 
Input The input consists of multiple test cases. The first line of each test case contains three integers N, M, and T (1 < N, M < 7; 0 < T < 50), which denote the sizes of the maze and the time at which the door will open, respectively. The next N lines give the maze layout, with each line containing M characters. A character is one of the following:

'X': a block of wall, which the doggie cannot enter; 
'S': the start point of the doggie; 
'D': the Door; or
'.': an empty block.

The input is terminated with three 0's. This test case is not to be processed.
 
Output For each test case, print in one line "YES" if the doggie can survive, or "NO" otherwise.
 
Sample Input 4 4 5
S.X.
..X.
..XD
....
3 4 5
S.X.
..X.
...D
0 0 0

Sample Output NO
YES

一看是迷宮問題,一般就提示我們回溯+DFS:

#include<stdio.h> // 62MS
#include<stdlib.h>
int des_row, des_col; int m, n, t; bool escape; int dir[4][2] = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}}; // dirction:left, up, right, down
int map[7][7]; void dfs(int row, int col, int step); int main(void) { int start_row, start_col; int wall; while(scanf("%d%d%d", &n, &m, &t)) { wall = 0; escape = false; // 每組測試數據都要重置escape的值,否則肯定WA
        if(n == 0 && m == 0 && t == 0) break; for(int row = 1; row <= n; row++) for(int col = 1; col <= m; col++) { scanf(" %c", &map[row][col]); // 注意%c前面的空格
                if(map[row][col] == 'S') { start_row = row; start_col = col; } if(map[row][col] == 'D') { des_row = row; des_col = col; } if(map[row][col] == 'X') wall++; } if(n*m - wall - 1 < t) // 定界剪枝,該枝不剪耗時546MS
 { printf("NO\n"); continue; } map[start_row][start_col] = 'X'; dfs(start_row, start_col, 0); if(escape) printf("YES\n"); else printf("NO\n"); } return 0; } void dfs(int row, int col, int step) // 因為回溯需要修改step所以它不能定義成全局變量,本函數的三個變量都需要保存在棧中
{ if(row == des_row && col == des_col && step == t) { escape = true; return; } if(escape) // 提高效率,缺少的話超時,根據題意只需要確定有無解即可,不必找全解
        return; if(row<1 || row>n || col<1 || col > m) // 出界
        return; int temp = (t-step) - ( abs(des_row-row) + abs(des_col-col) ); if(temp < 0 || (temp&1)) // 定界剪枝+奇偶剪枝(缺少超時),&的優先級大於||(奇偶剪枝可放在main函數中,只需一次判斷即可)
        return; for(int i = 0; i < 4; i++) { if(map[row+dir[i][0]][col+dir[i][1]] != 'X') { map[row+dir[i][0]][col+dir[i][1]] = 'X'; dfs(row+dir[i][0], col+dir[i][1], step+1); // 不要寫成step++
            map[row+dir[i][0]][col+dir[i][1]] = '.'; } } return; }
// 一點心得&牢騷: 
// 全不全局是個問題,一方面傳參數目要盡量少,另一方面又要避免全局變量過多引起混亂
// 考慮不周的話,DFS很容易就棧溢出或者數組下標越界,因此要考慮好深搜到什么時候終止
// 該代碼C++過了,G++ WA,害我反復修改提交了幾個小時.不得不噴一下,HDOJ怎么評測的:(

我們結合上述程序中定義的數據進行分析,最多max_step = n*m- wall - 1 步到達終點,如果t > max_step 問題顯然無解。最少min_step = abs(des_row-row) + abs(des_col-col) 步到達終點,如果t < min_step,問題同樣無解。

這樣我們限定了min_step ≤ t ≤ max_step。據此可以進行定界剪枝。

至於上述程序中的奇偶剪枝,意思就是偶數步才能到達的點,奇數步絕不可能到達,反之亦然。而偶數 - 奇數 = 奇數; 偶數 - 偶數 = 偶數; 奇數 - 奇數 = 偶數。再根據整數底層的二進制表示形式可知,奇數的最后一位必為1,這就是程序中temp&1 的由來

另外在說一點,關於出界判斷if(row<1 || row>n || col<1 || col > m) 也可以不用,方法就是在迷宮的最外圍全部設置成虛擬的牆。

All Rights Reserved.
Author:海峰:)
Copyright © xp_jiang. 
轉載請標明出處:http://www.cnblogs.com/xpjiang/p/4438300.html
以上.


免責聲明!

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



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