搜索_解题技巧
使用范围
DFS,深度优先搜索,寻找操作步骤字典序最小的解;白话文来说,就是能按序遍历所有情况
然而,DFS也是可以求最小解的:
- 用一个全局变量,记录最小值(打擂台)
- 迭代加深
BFS,广度优先搜索,找到步骤最小的解;白话文来说,就是递归/迭代次数最少的解
DFS
模板
1 | void dfs(int k){//k代表递归层数,或者说要填第几个空 |
模板仅供参考,毕竟千变万化(我本人也不按照模板设计),核心编程思路有两点:
递归函数体内,每次处理的是当前的选项;下一次的情况丢给下一次递归就好
若下一次递归的情况不符合题意怎么办呢?还往下递归吗?
当然,函数体开头直接写相关的
return
语句,等下一次递归执行时,不就自动处理掉了嘛根据题意判断是否要恢复现场。但基本上,都需要额外新建标记数组来记录解以及重复情况
如果拿不定主意,千万不要省标记数组!!!会吃大亏!
练习题
- P1019 字符串拼接+DFS
- P1036 选数+DFS
- P1088 全排列
- P1101 八个方向DFS
- P1149 数学+DFS
- P1157 选数+DFS
- P1162 正难则反覆盖+DFS
- P1219 经典的八皇后!
- P1363 如何处理地图无限大的搜索
- P1596 经典的flood-fill模型,DFS找连通块个数
- P1605 DFS枚举走迷宫的方案数
- P1706 经典的全排列!
- P2036 简单剪枝
- P2089 简单剪枝
- P2392 分类DFS
- P2404 经典的自然数拆分
- P2853 flood-fill找共同连通块
- P5318 图的遍历
- acwing-165. 小猫爬山 中等难度的分配问题+剪枝
- poj-3074 Sudoku 高难度状态压缩+剪枝
- poj-2676 Sudoku poj-3074的简化版
- P1120 高难度剪枝
- P1731 高难度剪枝
- P1074 剪枝+状态压缩
- P1092 剪枝
- P1312 消消乐的大模拟,考验码力
BFS
模板
1 | //Q是队列 |
我基本也不按模板来...核心编程思路也有两点:
与DFS不同,BFS的函数体内每次处理的是下一次的选项!所以下一次的状态是否塞入队列,当前就要决定好!
BFS为了避免死循环,也同样需要标记数组记录重复情况;比如该题
二次强调,除非确实认定逻辑上不需要标记数组,否则一定要加上!多个数组才多少空间啊....
练习题
- P1135 二维走地图
- P1443 二维走地图
- P1825 二维走地图
- P2895 二维走地图
- poj-3322 三维走地图
- acwing-173 flood-fill模型
- acwing-174 双重BFS,Dijkstra和SPFA的精髓题,绝世好题!
- acwing-188 二维走地图
- P2960 二维走地图,输入有坑
- poj-2044 思维题+二维走地图
进阶搜索
根据边权情况分类即可:
问题只计最少步数,等价于在边权都为1的图上求最短路。
使用普通的BFS,时间复杂度\(O\left( N \right)\)
每个状态只访问(入队)一次,第一次入队时即为该状态的最少步数。
问题每次扩展的代价可能是0或1,等价于在边权只有0和1的图上求最短路。
使用双端队列BFS,时间复杂度\(O\left( N \right)\)
每个状态被更新(入队)多次,只扩展一次,第一次出队时即为该状态的最小代价。
问题每次扩展的代价是任意数值,等价于一般的最短路问题。
使用优先队列BFS,时间复杂度\(O\left( N\log N \right)\)。
每个状态被更新(入队)多次,只扩展一次,第一次出队时即为该状态的最小代价。
使用迭代思想+普通的BFS(常用SPFA),时间复杂度\(O\left( N^2 \right)\)。
每个状态被更新(入队)、扩展(出队)多次,最终完成搜索后,记录数组中保存了最小代价。
优先队列BFS
使用
其实就是Dijkstra和SPFA
练习题
双端队列BFS
使用
只能用在边权0或1的图上求最短路,和dijkstra完全一样的写法,只不过二叉堆换成了双端队列
1 | memset(d, 0x3f, sizeof(d)), memset(vis, 0, sizeof(vis)); |
练习题
- acwing-175 模板题
- poj-3662 二分 + 双端队列BFS求最短路
双向BFS
使用
用以减少一半的搜索树深度,两个方向轮流做BFS,每次可以先选状态少的进行扩展;
注意!如果有无解情况,一定要先排除,否则复杂度会是普通BFS的两倍!
若要输出路径,反向BFS的方向数组千万记得取反!
正向方向数组为opa[4]={上,右,下,左}
反向方向数组应为opb[4]={下,左,上,右}
在反向BFS中,目标状态作为起点,中间状态作为终点;但客观上目标状态才是终点,中间状态是起点。
所以,在反向BFS的视角中,从目标状态朝中间状态向上移动;
客观上应该保存为从中间状态朝向目标状态向下移动!
如果双向BFS仍超时,只能考虑启发式搜索进行优化了。
练习题
- hdoj-3085
- P1032
- acwing-179. 八数码
- P1379 上面那道题的低配版
迭代加深
使用
因为DFS的性质,无法像BFS一样很方便地找最少步骤解,要么就只能开一个全局变量打擂台并剪枝;
迭代加深就是模拟BFS,判断答案在第几层,异曲同工之妙。
练习题
双向DFS
使用
减少一半的搜索树深度
练习题
- acwing-171 双向DFS模板题
- CF525E 高难度的记忆化搜索优化
- CF912E 优化双向DFS + 二分 + 双指针
- P4799 和上面那题类似
Astar
使用
设当前状态为now
,当前状态到目标状态的最小开销(理想值)为估价函数f(now)
,初始状态到当前状态的开销为d[now]
每次取出最小的f(now) + d[now]
进行扩展
与Dijkstra的大不相同!
除了目标状态之外,其余状态会重复出队入队,不能像Dijkstra那样只出队一次!
因为f(now) + d[now]
是由两个变量共同组成,收敛性不一致;
且由于本身用了Dijkstra的贪心思想,所以不能用在负边权的图上!
适用于规模巨大的优先队列BFS题,一定要用估价函数来加快搜索进程否则会TLE的那种
练习题
IDAstar
使用
迭代加深+估价函数构成,
每次计算初始状态到当前状态的开销 + 当前状态到目标状态的最小开销(理想值,即估价函数)
是否大于阈值,用以剪枝