面试痛点:DFS遍历写不出来,基础算法丢分

前端面试中,二叉树的深度优先搜索(DFS)是高频考点,尤其是前序遍历。很多同学要么只会背概念,要么手写代码时漏洞百出,明明是基础题却拿不到分。

这篇文章我会用递归迭代两种方式,手把手实现二叉树DFS前序遍历,代码可直接复制运行,还会标注踩坑点,看完就能上手写,面试遇到直接稳过。

二、先搞懂:DFS前序遍历到底是什么?

DFS(深度优先搜索)的核心是:沿着一条分支遍历到底,回溯后再探索其他分支。

前序遍历的访问顺序是:根节点 → 左子树 → 右子树

举个实战场景例子:

复制代码
    A
   / \
  B   C
 /
D

前序遍历结果:A → B → D → C

这个场景在前端中很常见,比如:

  • 解析树形组件的节点数据
  • 遍历菜单树结构,生成面包屑导航
  • 面试中手写算法题(高频中的高频)

三、实现方式1:递归(简单易写,面试优先写)

递归是实现DFS最直观的方式,逻辑简单,代码量少,面试时先写这种,不容易出错。

3.1 完整可运行代码

javascript 复制代码
// 先定义二叉树节点结构(实战中别漏了这一步)
class TreeNode {
  constructor(val) {
    this.val = val;
    this.left = null;
    this.right = null;
  }
}

// 构建示例树(对应上面的A-B-D-C结构)
const root = new TreeNode('A');
root.left = new TreeNode('B');
root.right = new TreeNode('C');
root.left.left = new TreeNode('D');

// DFS前序遍历 - 递归实现
function dfs(root, res = []) {
  // 递归出口:节点为空时返回结果数组
  if (!root) return res;
  // 1. 访问根节点
  res.push(root.val);
  // 2. 递归遍历左子树
  dfs(root.left, res);
  // 3. 递归遍历右子树
  dfs(root.right, res);
  return res; // 返回最终结果
}

// 测试运行
console.log(dfs(root)); // 输出:['A', 'B', 'D', 'C']

3.2 踩坑提醒(重点!)

  1. 递归出口不能漏 :如果没写if (!root) return res,会导致无限递归,浏览器直接卡死;
  2. res参数默认值 :用res = []避免每次递归都新建数组,保证结果存在同一个数组里;
  3. 顺序不能乱:前序遍历必须是「根→左→右」,如果先递归右子树,结果就错了。

四、实现方式2:迭代(进阶写法,体现算法功底)

递归的本质是调用栈,面试中面试官常会追问:"能不能用迭代实现?"。迭代方式需要手动模拟栈结构,更能体现你的算法基础。

4.1 完整可运行代码

javascript 复制代码
// 复用上面的TreeNode和root节点结构

// DFS前序遍历 - 迭代实现(栈模拟)
function dfsPreOrderIter(root) {
  // 边界处理:根节点为空直接返回空数组
  if (!root) return [];
  const res = []; // 存储遍历结果
  const stack = [root]; // 栈:初始化放入根节点

  // 栈不为空时循环
  while (stack.length) {
    // 1. 弹出栈顶节点(先处理根节点)
    const node = stack.pop();
    res.push(node.val);

    // 2. 先压入右子树(栈是后进先出,保证左子树先被处理)
    if (node.right) {
      stack.push(node.right);
    }
    // 3. 再压入左子树
    if (node.left) {
      stack.push(node.left);
    }
  }
  return res;
}

// 测试运行
console.log(dfsPreOrderIter(root)); // 输出:['A', 'B', 'D', 'C']

4.2 踩坑提醒(重点!)

  1. 栈的入栈顺序:必须先压右子树,再压左子树!因为栈是"后进先出",这样弹出时才会先处理左子树,符合前序遍历规则;
  2. 边界判断 :处理node.rightnode.left时,要先判断是否存在,避免压入null;
  3. 循环终止条件 :以stack.length为判断依据,栈空则遍历完成。

五、实战总结:两种方式怎么选?

实现方式 优点 缺点 适用场景
递归 代码简洁、易理解 递归深度过大会栈溢出 面试快速手写、简单树形结构
迭代 性能更优、无栈溢出风险 代码稍复杂、需要理解栈逻辑 进阶面试题、大数据量树形遍历

核心记住:

  • 前序遍历顺序:根 → 左 → 右
  • 递归靠系统栈,迭代靠手动栈;
  • 迭代时栈的入栈顺序是「右→左」,出栈才是「左→右」。