Depth-first search(DFS) 深度优先搜索
文章内容需要读者有 树或图 数据结构的基本认识 和 递归 算法实践经验. 为了方便理解,请读者按照内容顺序阅读。
算法思想概述
dfs是什么? dfs是一种基于树或图的算法,(当然树是一种特殊的图), 它的核心思想就是“一条路走到黑, 走不通, 退回上一个分叉口, 换另一条路.”
[!tip]
想像一下, 有一个迷宫, 迷宫的每个分叉口只有左边和右边两条路可以选.
现在, 我们自己就在这个迷宫里, 最简单、直观的方法就是每次都先走一边, 走过的路做好标记. 如果碰到死胡同, 我们就返回上一个路口. 这样一直走, 直到找到出口.
dfs来源
为什么需要DFS? 它最初是为了满足对树和图的遍历的需求而设计出的.
[!tip]
什么是遍历呢? 我们听过数组的遍历, 遍历数组就是访问数组中的每一个元素, 那图的遍历就是要访问图里的每一个节点.
树和图等数据结构可以看作对现实问题的一种抽象, 所以一切能抽象成树或图的问题, 都可以使用dfs来解决.
dfs实现
如何用代码实现dfs?
大家有没有注意到dfs思想中有一个重复的步骤, 让我们回到之前的迷宫, 我们每到一个分叉口是不是都要选择向左或向右走? 那么每一个分叉口和我们在这个分叉口会作出的决策就构成了一个场景.
对于每一个场景来说, 所在的交叉口是不同的(可能位于起点, 中间的某个分岔口, 或者终点), 但决策的策略是相同的(要么往左要么往右). 并且, 每一个场景, 除了迷宫起点, 其他场景的发生都是由上一个场景的决策所引起的.
如果我们将各种场景抽象出来一个场景 Class, 会发现这种场景在我们走迷宫的过程中重复了很多次(被实例化了很多次). 因此, 如果我们想要用计算机来模拟这种过程, 那么递归是最合适不过的了. 所以, 实现dfs的最简单、直观的方法就是递归. 而栈实现本质上还是递归, 只不过是用栈来模拟了递归, 相当于已经给你提供了锤子, 你不用, 你想自己造一个锤子用.
回溯&剪枝
再介绍两个在dfs中常见的概念: 回溯和剪枝
- 回溯: 需要结合具体的递归函数的代码实现来理解. 还用之前走迷宫的例子, 回溯就是当你发现路走不通了, 就返回上一个交叉口.
- 剪枝: 本质上来讲, 是一种优化搜索速度的思想, 根据已有信息来推断哪些路是注定走不通的, 那么就不去实际走了. 反映到搜索树上, 就好像有一条路径被剪去了, 所以叫剪枝. 打个比方, 还是走迷宫, 摆在你面前的有两条路, 一条宽敞明亮, 另一条荒草凄凄, 如果你更喜欢宽敞明亮, 那以后就不走另一条. 你可能会有疑问, 那要是另一条路才是对的, 岂不是永远找不到出口了? 是这样的, 如果你的剪枝策略是错的, 就可能找不到答案了. 所以设计正确的剪枝策略很重要
dfs代码实现
接下来我讲具体讲一讲dfs算法的代码实现
既然是基于树或图的, 那么首先肯定就要有一个用来储存树或图的数据结构
其次, 就是设计我们的递归函数, 更明确一点就是要把我们重复的场景抽象出来, 并用代码模拟
递归和栈实现在模拟场景的代码是一样的, 主要区别在于实现递归逻辑的代码不同, 这也导致了时空复杂度的不同.我们不做过多研究
例题
待更新…给我发私信催更哈哈哈