「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。
深度优先搜索
深度优先搜索,英文名 Depth first search,简称 DFS。
广度优先搜索和深度优先搜索
如果理解了广度优先搜索,再来理解深度优先搜索,那就再容易也不过了,不了解广度优先搜索的同学可以去看看这篇文章。
先贴两张图来对比一下这两种算法:
广度优先
深度优先
很明显,它们都是为了遍历整棵树(图、状态集),广度优先是一层一层地去遍历,而深度优先采用的是“不撞南墙不回头”的策略,坚持向当前道路的最深处挖掘。
下面这张图体现了两者的查找顺序:
广度优先是循规蹈矩地一层一层地去查找。
深度优先是先把一条路查到底,然后再返回,看之前的路有没有分支,有就走分支,分支走完了又回去,最后把所有的路走完。
二叉树的先序遍历
先序遍历:根结点 -> 左子树 -> 右子树
这道经典的面试题,其实就是深度优先搜索。
const preorder = (root) => {
if (!root) { // 递归边界,根结点为空
return false
}
console.log(root.val) // 输出当前遍历的结点值
preorder(root.left) // 递归遍历左子树
preorder(root.right) // 递归遍历右子树
}
深度优先搜索遍历 Dom 树
又是一道经典面试题,和广度优先搜索用队列来暂存元素不同的是,深度优先搜索用栈,利用的是栈先进后出的特性。
function dfs (root) {
const res = []
if (root) {
const stack = [] // 定义一个栈,暂存元素
stack.push(root)
while (stack.length) {
const top = stack.pop() // 弹出栈顶元素
res.push(top)
const children = top.children
for (let i = children.length - 1; i >= 0; i--) { // 这里要小心,children里的元素一定是逆序进栈,才能利用到栈先进后出的特性
stack.push(children[i])
}
}
}
return res
}
打开淘宝网,把代码扔进去。
遍历成功!先把 head 里的元素遍历完了,再去遍历 body 里的元素。
也可以使用递归来处理
const dfs = (root) => {
const res = [] // 定义结果数组,数据存在结果数组里
const fn = (root) => { // 定义递归函数
if (!root) {
return false
}
res.push(root)
const children = root.children
for (let i = 0, len = children.length; i < len; i++) {
fn(children[i]) // 递归调用 children 里的每一个元素
}
}
fn(root) // 调用递归函数
return res
}
其实,使用递归来处理,代码更容易理解一些,但是不用递归,性能会好很多。
这一点看什么场合,如果是日常工作场合,怎么方便怎么写,因为 V8 引擎都很快。
面试场合非要讨论时间复杂度问题,再去深究。
leetcode 深度优先搜索初体验
题目描述:给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。
这就是典型的深度优先搜索
如果我们知道了左子树和右子树的最大深度 l 和 r,那么该二叉树的最大深度即为
max(l,r) + 1
而左子树和右子树的最大深度又可以以同样的方式进行计算。因此我们可以用「深度优先搜索」的方法来计算二叉树的最大深度。
代码实现
const maxDepth = function (root) {
if (!root) { // 根结点为空,直接返回 0
return 0
} else {
const l = maxDepth(root.left) // 递归拿到左子树和右子树的最大深度,取 max,最后加上根结点的1层
const r = maxDepth(root.right)
return Math.max(l, r) + 1
}
}
时间复杂度:O(n)
空间复杂度:O(height),其中 height 表示二叉树的高度。
小结
如果有人问你,什么是深度优先搜索?
你就跟他说,不撞南墙不回头!
另外,算法不是玄学,两星期前,我就是一个货真价实的算法初学者。这两星期,通过了解数据结构或算法诞生的前因后果,再结合编码实践,慢慢地熟悉了一些算法。
你一定也可以!
往期算法相关文章