写给前端开发的深度优先搜索

395 阅读3分钟

「这是我参与2022首次更文挑战的第10天,活动详情查看:2022首次更文挑战」。

深度优先搜索

深度优先搜索,英文名 Depth first search,简称 DFS。

广度优先搜索和深度优先搜索

如果理解了广度优先搜索,再来理解深度优先搜索,那就再容易也不过了,不了解广度优先搜索的同学可以去看看这篇文章。

写给前端开发的广度优先搜索

先贴两张图来对比一下这两种算法:

广度优先

image.png

深度优先

image.png

很明显,它们都是为了遍历整棵树(图、状态集),广度优先是一层一层地去遍历,而深度优先采用的是“不撞南墙不回头”的策略,坚持向当前道路的最深处挖掘。

下面这张图体现了两者的查找顺序:

image.png

广度优先是循规蹈矩地一层一层地去查找。

深度优先是先把一条路查到底,然后再返回,看之前的路有没有分支,有就走分支,分支走完了又回去,最后把所有的路走完。

二叉树的先序遍历

先序遍历:根结点 -> 左子树 -> 右子树

这道经典的面试题,其实就是深度优先搜索。

image.png

const preorder = (root) => {
  if (!root) {                     // 递归边界,根结点为空
    return false
  }
  console.log(root.val)            // 输出当前遍历的结点值

  preorder(root.left)              // 递归遍历左子树
  preorder(root.right)             // 递归遍历右子树
}

image.png

深度优先搜索遍历 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
}

打开淘宝网,把代码扔进去。

image.png

遍历成功!先把 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 深度优先搜索初体验

二叉树的最大深度

题目描述:给定一个二叉树,找出其最大深度。
二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

image.png

这就是典型的深度优先搜索

如果我们知道了左子树和右子树的最大深度 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 表示二叉树的高度。

小结

如果有人问你,什么是深度优先搜索?

你就跟他说,不撞南墙不回头!

另外,算法不是玄学,两星期前,我就是一个货真价实的算法初学者。这两星期,通过了解数据结构或算法诞生的前因后果,再结合编码实践,慢慢地熟悉了一些算法。

你一定也可以!

往期算法相关文章

写给前端开发的链表介绍(js)

写给前端开发的算法简介

写给前端开发的树简介(js)

写给算法初学者的分治法和快速排序(js)

写给前端开发的散列表介绍

写给前端开发的广度优先搜索(js)