搜索学习笔记

DFS与BFS

oi-wiki上的 BFSDFS

搜索的两种实现形式分别是\(bfs\)\(dfs\).

\(DFS\):会一直沿着某一分支不断向下,直至边界才返回,所以DFS适用于深度较深,分支较少的搜索树

\(BFS\):会一层一层的遍历搜索树,所以BFS适用于深度较浅,分支较多的搜索树。

注意事项

  1. 在搜索的时候我们应该不重不漏的遍历每个从状态,
  2. 如果搜索的状态构成一张图,那我们需要用数组\(vis\)记录状态的遍历情况
  3. 弄清楚 层次分支,减小搜索树的规模。
  4. 减小搜索时的常数(比如利用位运算),将代码模块化

搜索的优化

剪枝

剪枝,顾名思义,就是“剪去”搜索树上的“枝条”
即我们在搜索的时候发现某条分支已不可行了,那么就可以直接回溯了,以此来减少搜索树的规模。
主要有以下五种常见的剪枝方法:

  1. 优化搜索顺序

    即我们可以将搜索的对象进行排序来产生不同规模的搜索树

  2. 排除等效冗余

    当搜索树是变成一张DAG时,很显然一个状态节点可能被访问多次,这时我们只搜索一次即可

  3. 可行性剪枝

    当前状态无法到达递归边界,直接返回

  4. 最优性剪枝

    发现当前代价已经劣于答案,再优的策略都无法更新答案,直接返回

  5. 记忆化

    记录状态的答案

迭代加深

上文提到过,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\)

还没学,鸽了。

习题

大概每个内容刷几道就够了

参考资料

oi-wiki

李煜东《算法竞赛进阶指南》搜索篇

posted @ 2021-05-19 13:55  Isenthalpic  阅读(140)  评论(0编辑  收藏  举报