数据结构和算法之DFS+回溯
介绍
回溯就是对一个问题的所有的可能结果进行搜索的一种方法,通过深度优先遍历的思想来实现。本质上回溯算法就是一种遍历算法,它会搜索得到一个问题的所有解,时间复杂度很高。
对于所有可能的情况,我们一种一种的去尝试(一般按照顺序来枚举,这样保证每一个情况都能搜索到),如果是我们想要的结果就放到结果集合中。另外,在满足某些条件的时候可以进行剪枝。一般都是将这种问题转换成一个树形结构来进行分析。既然是树形结构,就要知道
- 每个节点中是什么:可以根据节点中的值来判断是否要加index,来表示下一次从哪里开始遍历
- 每条路径上是什么:使用了什么
- 如何产生树的分支:满足什么条件的时候可以直接剪枝,一些搜索可以直接排除,就不用画这个分支了,一般重复问题要考虑下。
- 树结构有几层:到什么时候搜索截止,dfs中的i如何变化,是i+n,还是保持i不变
- 遍历这个节点之后是继续还是回退:回溯,这个点遍历结束要回到上一个分叉点,那状态就要重置,需要设定为和上一次一样。
- 记录该节点是否访问过:visit记录下这个节点后面还需要用到吗。
做题的时候:
对于树形图,思考清楚:题目需要的解是在叶子节点、非叶子节点还是根节点到叶子节点的路径。
模板:
void dfs(...)//参数用来表示状态,根据需要添加。一般什么变化就把什么设置成参数
{
if(到达终点状态)
{
...//根据题意添加
return;
}
if(越界或者是不合法状态)
...//
return;
if(特殊状态)//剪枝
...//
return ;
for(扩展方式) //统计所有的可能结果,行参数
{
//如果有边界条件的话就if,没有就直接向着答案写
//visit记录下访问过的东西
if(扩展方式所达到状态合法) //所有可能结果中找满足条件的,
{
修改操作;//根据题意来添加
标记;
dfs(...); //列参数
(还原标记);
(不是覆盖结果的话就回溯修改的操作);vector添加和一般数组覆盖。
//是否还原标记根据题意
//如果加上(还原标记)就是 回溯法
}
}
经典例题
组合
不强调元素顺序,也就是12和21是一样的,有这个东西就行,不考虑你在哪个位置。
排列
强调元素顺序
分割
子集
组合、分割、子集、排列问题,都可以抽象成树结构来进行分析。
矩形问题
矩阵问题一般也可以抽象成树结构问题来解决,一般就是树的分支就是小A能走的方向。当然,也是有一些限制,可以提前进行剪枝操作。这种题目仔细理解好,感觉就是套路,主要是细节问题。但是基本上设置visit数组是标准解法,同时要考虑从哪里开始进行搜索,是矩阵中某一点,还是固定一个点,还是说矩阵中所有点都有可能,根据题意判断好然后进行搜索。
有几个点需要思考清楚的:
- **起点和终点:**起点是固定的还是每一个元素都有可能是起点
- **状态:**每一步的相邻状态有哪些,每个状态需要记录什么信息
- **标记:**是否需要标记元素的状态,如何将这些状态标记为已经访问
题目: - 迷宫
- 单词搜索
- 被围绕的区域
- 岛屿数量
其实做题的时候分清楚一维还是二维,弄清楚从哪里开始深搜,一维其实就是递归,牢记递归三要素。另外,参数是引用还是普通参数,或者是全局变量的区别。