回溯法可以看成蛮力法的升级版。他从解决问题每一步的所有可能选项里系统的选择出一个可行的解决方案。回溯法非常适合有多个步骤组成的问题,并且每个步骤都有多个选项。当我们在某一步选择了其中一个选项时,就进入下一步,然后又面临新的选项。我们就这样重复选择,直至达到最终的状态。
用回溯法解决的问题的所有选项可以形象地用树状结构表示。在某一步有n个可能的选项,那么该步骤可以看成是树状结构中的一个节点,每个选项看成树中节点连接线,经过这些连接线到达该节点的n个子节点。树的叶节点对应着终结状态。如果在叶节点的状态满足题目的约束条件,那么我们找到了一个可行的解决方案。
如果在叶节点的状态不满足约束条件,那么只好回溯到他的上一个节点再次尝试其他的选项。如果上一个节点所有可能的选项都已经试过,并且不能满足约束条件的终结状态,则再次回溯到上一个节点。如果所有的节点的所有选项都已经尝试过仍然不能到达满足约束条件的终结过程,则问题无解。
-------摘自《剑指offer》
要我来说就两个字:深度。为什么是深度?好比在二叉树中按照递归方式查找元素,只要左子树不为空就一直走左边,左边下面的节点不满足,那么退到双亲节点,继续找右边的节点。
下面举一个例子说明:
题目:矩阵中的路径
描述:设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一格开始,每一步可以在矩阵中向左,右,上,下移动一格。如果一条路径经过了矩阵中的某一格,那么该路径不能再次进入该格子。
代码
class Solution {
public:
//rows表示矩阵长,cols表示宽
bool hasPath(char* matrix, int rows, int cols, char* str)
{
vector<vector<int> >arr(rows, vector<int>(cols, 0));
//矩阵中每一个节点都要被当做开头
for (int i = 0; i < rows; ++i) {
for (int j = 0; j < cols; ++j) {
if (fun(arr, matrix, i, j, str, 0,rows, cols)) {
return true;
}
}
}
return false;
}
//i , j 表示此时的横纵坐标, arr是一个与矩阵相同规格的二维数组,用来标记格子是否已进入
bool fun(vector<vector<int> >& arr, char* ma, int i, int j, char* s, int k, int row, int col)
{
bool istrue = false;
bool isall = false;
//字符串到‘\0’说明匹配上了,则返回true
if (*(s + k) == '\0') {
return true;
}
if (i >= 0 && i < row && j >= 0 && j < col && arr[i][j] == 0 && ma[col * i + j] == *(s + k)) {
arr[i][j] = 1; //进入该格子,标记为1,此后不能进入
++k;
istrue = true;
isall = fun(arr, ma, i - 1, j, s, k, row, col)
|| fun(arr, ma, i + 1, j, s, k, row, col)
|| fun(arr, ma, i, j - 1, s, k, row, col)
|| fun(arr, ma, i, j + 1, s, k, row, col);
}
if (istrue == true) { //退出该格子,恢复成0,表示后面可以进入
arr[i][j] = 0;
}
return isall;
}
};