DFS与BFS
搜索的两种实现形式分别是\(bfs\)和\(dfs\).
\(DFS\):会一直沿着某一分支不断向下,直至边界才返回,所以DFS适用于深度较深,分支较少的搜索树
\(BFS\):会一层一层的遍历搜索树,所以BFS适用于深度较浅,分支较多的搜索树。
注意事项
- 在搜索的时候我们应该不重不漏的遍历每个从状态,
- 如果搜索的状态构成一张图,那我们需要用数组\(vis\)记录状态的遍历情况
- 弄清楚 层次 和 分支,减小搜索树的规模。
- 减小搜索时的常数(比如利用位运算),将代码模块化。
搜索的优化
剪枝
剪枝,顾名思义,就是“剪去”搜索树上的“枝条”
即我们在搜索的时候发现某条分支已不可行了,那么就可以直接回溯了,以此来减少搜索树的规模。
主要有以下五种常见的剪枝方法:
-
优化搜索顺序
即我们可以将搜索的对象进行排序来产生不同规模的搜索树
-
排除等效冗余
当搜索树是变成一张DAG时,很显然一个状态节点可能被访问多次,这时我们只搜索一次即可
-
可行性剪枝
当前状态无法到达递归边界,直接返回
-
最优性剪枝
发现当前代价已经劣于答案,再优的策略都无法更新答案,直接返回
-
记忆化
记录状态的答案
迭代加深
上文提到过,BFS适用于深度较浅,分支较多的搜索树。
因为使用dfs时我们可能会在深层子树上浪费很多时间。
这时如果使用dfs,就要用到迭代加深了。
它的大致实现方法如下:每次限定搜索的深度,使得每次只扩展固定的深度,直到找到答案为止。
其实迭代加深可以近似于bfs,但代码实现却要简单的很多。
折半搜索
将搜索对象分成两半,分别搜索。
再将两组的答案合并。
搜索复杂度一般是指数级的,这样做的话,复杂度能开个根号。
以上为DFS的一些变形和优化
双端队列\(BFS\)
\(dijkstra\)解决单源最短路的时间复杂度是\(O((m+n)log n)\)
但当边权只有\(01\)两种取值时,我们用双端队列bfs可以做到\(O(n+m)\)。
流程简述:对于边权是\(0\)的边,从队头入队;对于边权是\(1\)的边,从队尾入队。
这样就保障了队列的两段性(节点只属于两个层次)和单调性(层次在前的节点先出队)。
优先队列\(BFS\)
类似于\(dijkstra\),取出当前代价最小的进行扩展。
因为该状态一定最优,所以它无法在被其他状态更新,所以元素第一次被取出就是最优答案了。
双向\(BFS\)
假如目标状态给定,那么我们从起点和终点分别进行BFS,直到有状态重合,
此时类似于折半搜索,直接合并答案即可。
接下来是估价函数在搜索中的应用
\(A\)*
A*本质上是对优先队列BFS的进一步优化。
我们将堆中的比较的键值“当前代价”改成“当前代价+未来估价”
而这个未来估价不能大于未来实际代价
所以我们便可以设计一个估价函数来计算未来估价。
这个函数必须满足三角形不等式,此时就不会将节点重复加入优先队列。
\(IDA\)*
简单说就是迭代加深+A*,
即当前代价+估价>深度限制,返回。
\(Dancing~Links\)
还没学,鸽了。
习题
大概每个内容刷几道就够了
参考资料
李煜东《算法竞赛进阶指南》搜索篇