搜索作为算法的开篇需要彻底吃透,因此搜索专题将dfs与bfs分开进行总结。
在练习dfs之前,必须要将递归吃透,自己能力还是有限,递归部分一直很困扰我,这次要花费大量时间,彻底将递归和dfs吃透弄懂!
可解问题
n皇后问题:八皇后问题,数独,迷宫
求子集问题:从n个数字中选出k个数字并满足x条件,如背包问题,全排列问题(特殊的求子集问题),求子序列问题
本人dfs相关题解文章
专题要点:
本章个人认为从递归参数,递归边界,递归体三方面讲解更加合适。
递归参数:首先在对问题分析理解无误的情况下,递归参数的选取一般和题意有直接关系,大多为行列和计数器,比如:迷宫问题的坐标以及步数,排列组合问题的数字下标和个数;找出合适的递归参数对编程和解题帮助极大。
递归边界:要根据递归参数得到边界return。边界会隐藏在题意里需要分析得到,如迷宫不能超出边界,不能撞墙,排列组合要满足要求的数字范围和个数。
递归边界的作用*:dfs过程中,对局部解进行测试,满足条件则继续拓展其他节点,不满足条件就到达边界return,return即为放弃该局部解,回溯到上一节点继续遍历
递归体:即递归的具体情况,如在某种条件下,对参数计算后,继续进行递归操作,如迷宫向四个方向行走,求子集,某节点放入或者不放入。
剪枝:即为边界,具体见下文
==回溯时的反向操作==:==由于理解不够透彻,导致这一步操作经常忘记==(个人称其为“反向操作”)。也是回溯法的关键一步,因为回溯是向上层返回,所以要将下层做的所有处理恢复如初,否则会对再次dfs递归时造成影响,得到错误答案。
具体如:1
2
3
4
5
6
7
8
9
10
11
12vis[tx][ty] = true;
dfs(tx, ty);
vis[tx][ty] = false;
//又或是
vec.push_back(tx);
dfs(tx, ty);
vec.pop_back();
//又或是
path[index] = tx;
dfs(tx, ty, index + 1);
//此处没有反向操作的原因是,由于数组赋值的缘故导致回溯之后,
//上层的结点加入path[]会覆盖之前存储的下层结点的值剪枝
可行性剪枝*:当搜索的位置超过题目所给的数据范围时,进行可行性剪枝
最优性剪枝*:当题目求最优解和可行解的时候,当前得到的结果比当前最优解要差,进行最优性剪枝;当已经找到一个可行解时,后面其余的全部剪枝
重复性剪枝*:对于某些特定的搜索方式,一个方案会被搜索很多次是没有必要的;例如求K个数字的和,我们只关注和而不关注这几个数字选出来的顺序,因此可通过从小到大顺序选择,来避免重复
奇偶性剪枝*:目前还没见到过类似题目。当题目中存在明显的奇偶关系,或者可对网格进行黑白染色(标记)时使用奇偶性剪枝,当不满足应有的奇偶性,甚至不需要dfs可直接判断输出
几点注意:
递归边界:if 判断位置的先后问题,会不会发生提前拦截的情况出现,导致程序运行出错。
递归边界:要注意考虑每一个参数所对应的边界限制。
递归体:对于含vis[]和使用vector,为了不影响后续搜索结果,要注意进入循环前后的反向操作(即回溯时修改了全局变量vis[]后,一定要及时把它们恢复原状)
1
2
3
4
5vis[x] = true;
ans.push_back(x);
dfs(x + 1);
ans.pop_back();
vis[x] = false;