回溯法

Posted KuoGavin

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回溯法相关的知识,希望对你有一定的参考价值。

文章目录

1. 什么是回溯法

  • 回溯法的概念:
      回溯法可以看成蛮力法的升级版,从解决问题每一步的所有可能选项里系统地选择出一个可行的解决方案。回溯法非常适合由多个步骤组成的问题,并且每个步骤都有多个选项。当在某一步选择了其中一个选项时,就进入下一步,然后又面临新选项。如上步骤进行多次选择,最终达到最终状态。

  换句话说,回溯就是通过不同的尝试来生成问题的解,有点类似于穷举,但是和穷举不同的是回溯会“剪枝”。(剪枝的意思也就是说对已经知道错误的结果没必要再枚举接下来的答案了,比如一个有序数列1,2,3,4,5,我要找和为 5 5 5的所有集合,从前往后搜索我选了 1 1 1,然后 2 2 2,然后选 3 3 3的时候发现和已经大于预期,那么 4 4 4, 5 5 5肯定也不行,这就是一种对搜索过程的优化。)

  回溯搜索是深度优先搜索(DFS)的一种。对于某一个搜索树来说(搜索树是起记录路径和状态判断的作用),回溯和DFS,其主要的区别是,回溯法在求解过程中不保留完整的树结构,而深度优先搜索则记下完整的搜索树。

  • 回溯法的做法:
      用回溯法解决的问题所有选项可以形象地用树状结构表示。在某一步有 n n n个可能的选项,那么该步骤可以看成是树状结构中的一个节点,下一步骤的中的每个选项看作是其子节点。树中的叶子节点对应着最终状态。如果在叶节点的状态满足题目的约束条件,那么该根路径就是一个可行解。
      如果到达叶子节点之后不满足题目的约束条件,那么只好回到(回溯)到其父节点的步骤当中,再选择另一选项(子节点)。如果上一节点的所有选项都已经尝试过均不能达到题目中约束条件的要求,则再次回到到该节点的父节点。若是从根节点开始的所有路径都不能满足题目的约束条件,则该问题无解。

2. 回溯的一般结构

对于回溯而言其是运用了递归这一算法结构的算法思想

dfs(当前状态)  
  
      if(当前状态为边界状态)  
        
        记录或输出  
        return;  
        
      for(i=0;i<n;i++)       //横向遍历解答树所有子节点  
        
           //扩展出一个子状态  
           修改了全局变量  
           if(子状态满足约束条件)  
              
              dfs(子状态)  
             
           恢复全局变量 //回溯部分  
        
  

3. 相关题目

以下代码均为c++:

请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。
路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左、右、上、下移动一格。
如果一条路径经过了矩阵的某一格,那么该路径不能再次进入该格子。
例如,在下面的3×4的矩阵中包含一条字符串“bfce”的路径(路径中的字母用加粗标出)。

[["a","b","c","e"],
["s","f","c","s"],
["a","d","e","e"]]

但矩阵中不包含字符串“abfb”的路径,
因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入这个格子。
class Solution 
public:
	bool exist(vector<vector<char>>& board, string word) 
        int rows = board.size(), cols = board[0].size();
        if(rows == 0 || cols == 0 || word.size() == 0) return false;
        int pathLen = 0;
        vector<vector<bool>> visited = vector<vector<bool>>(rows, vector<bool>(cols, false));
        for(int row = 0; row < rows; ++row) 
            for(int col = 0; col < cols; ++col) 
                if(hasPath(board, word, visited, pathLen, rows, cols, row, col))
                    return true;
            
        
        return false;
	

    bool hasPath
    (vector<vector<char>>& board, string word, vector<vector<bool>>& visited, int pathLen,
     int rows, int cols, int row, int col) 
        if(pathLen == word.size()) return true;
        bool hasRes = false;
        //在确保row,col位置在字符矩阵中的条件下
        //若是当前row,col位置的字符与欲匹配的字符相等且该字符未访问过
        if(row < rows && row >= 0 && col < cols && col >= 0
            && word[pathLen] == board[row][col]
            && !visited[row][col]) 
            	//记录匹配成功的信息,将字符串指针加1,访问标示更改
                pathLen++;
                visited[row][col] = true;
				
				//进行下一步的dfs步骤,查找是否有符合的字符串路径
                hasRes =
                    hasPath(board, word, visited, pathLen, rows, cols, row, col-1) ||
                    hasPath(board, word, visited, pathLen, rows, cols, row, col+1) || 
                    hasPath(board, word, visited, pathLen, rows, cols, row+1, col) ||
                    hasPath(board, word, visited, pathLen, rows, cols, row-1, col);
                    
	            //若是row,col位置接下来的步骤没有找到符合的字符串路径,则进行回溯
                if(!hasRes) 
                    pathLen--;
                    visited[row][col] = false;
                
            
        return hasRes;
     


;
地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。
一个机器人从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能移动到方格外),
也不能进入行坐标和列坐标的数位之和大于k的格子。
例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。
但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:
输入:m = 2, n = 3, k = 1
输出:3
示例 2:

输入:m = 3, n = 1, k = 0
输出:1

提示:
1 <= n,m <= 100
0 <= k <= 20
//version 1 未作优化的版本
class Solution 
public:
    int movingCount(int m, int n, int k) 
        if(k < 0 || m < 1 || n < 1) return 0;
        vector<vector<bool>> visited = vector<vector<bool>>(m, vector<bool>(n, false));
        int cnt = movingCountCore(m, n, k, 0, 0, visited);
        return cnt;
    
private:
    int movingCountCore
    (int rows, int cols, int thresold, int row, int col, vector<vector<bool>>& visited) 
        int cnt = 0;
        if(check(rows, cols, thresold, row, col, visited)) 
            visited[row][col] = true;
            cnt = 1 + movingCountCore(rows, cols, thresold, row, col+1, visited) 
                    + movingCountCore(rows, cols, thresold, row, col-1, visited) 
                    + movingCountCore(rows, cols, thresold, row-1, col, visited)
                    + movingCountCore(rows, cols, thresold, row+1, col, visited);
        
        return cnt;
    

    bool check(int rows, int cols, int thresold, int row, int col, vector<vector<bool>>& visited) 
        if(row < 0 || row > rows-1 || col < 0 || col > cols-1 || visited[row][col] ||
           getDigitSum(row) + getDigitSum(col) > thresold)
            return false;
        else 
            return true;
        // if(row >=0 && row < rows && col >= 0 && col < cols && !visited[row][col] 
        //     && getDigitSum(row) + getDigitSum(col) <= thresold) 
        //     return true;
        // return false;
    

    //三个版本共用
    int getDigitSum(int num) 
        int sum = 0;
        while(num) 
            sum += num % 10;
            num /= 10;
        
        return sum;
    
;
//version 2,不再考虑向上和向左的情况,简化版本
class Solution 
public:
    int movingCount(int m, int n, int k) 
        vector<vector<bool>> visited = vector<vector<bool>>(m, vector<bool>(n, false));
        return dfs(m, n, visited, k, 0, 0);
    
private:
    int dfs(int rows, int cols, vector<vector<bool>>& visited, int thresold, int row, int col) 
        if(row >= 0 && row < rows && col >= 0 && col < cols 
        && getDigitSum(row) + getDigitSum(col) <= thresold
        && !visited[row][col]) 
            visited[row][col] = true;
            return
                dfs(rows, cols, visited, thresold, row+1, col) +
                dfs(rows, cols, visited, thresold, row, col+1) + 1;
        
        return 0;
    
//bfs作为一种思路
class Solution 
public:
    int movingCount(int m, int n, int k) 
        vector<vector<bool>> visited(m, vector<bool>(n, false));
        int ret = 0;
        queue<vector<int>> q;
        q.push(0, 0) ;
        while(!q.empty()) 
            vector<int> axis = q.front();
            q.pop();
            int row = axis.at(0), col = axis.at(1);
            int sum_row = getDigitSum(row), sum_col = getDigitSum(col);
            if(row >= m || col >=n || sum_row+sum_col > k || visited[row][col]) continue;
            visited[row][col] = true;
            ret++;
            q.push(row+1, col);
            q.push(row, col+1);
        
        return ret;
    
    
;

以上是关于回溯法的主要内容,如果未能解决你的问题,请参考以下文章

剑指offer-回溯法-机器人的运动范围-python

递归回溯法求N皇后问题

noj算法 堡垒问题 回溯法

dp算法之方格取数

机器人的运动范围

Leetcode之深度优先搜索&回溯专题-980. 不同路径 III(Unique Paths III)