欢迎光临
我们一直在努力

只会递归DFS远远不够!

前言

前端面试中,算法题越来越卷。DFS(深度优先搜索)作为树/图遍历的基石,几乎逢面必考。但很多人停留在"会写递归版 DFS 就行"的阶段,一遇到爆栈——就懵了。

本文从 DFS 的核心思想出发,逐层递进到迭代,把理论和实战串成一条线。

一、DFS 到底是什么?

一句话:沿着一条分支一路走到底,撞了南墙再回头探索另一条分支。

eaaeafdf5d6b15d9381b04423b0c27ad.jpg 用下面这棵树举例:

eaaeafdf5d6b15d9381b04423b0c27ad.jpg

A
/ \\
B C
/ \\ \\
D E F

先序遍历(根 → 左 → 右)的访问顺序是:A → B → D → E → C → F

你看,A 出发一路往左下探到 D(到底了),回溯到 B 再探 E(到底了),再回溯到 A 探 C,最后到底 F。这就是 DFS 的核心节奏——纵深优先,不撞南墙不回头。

DFS vs BFS 一张表对比

特点DFSBFS
核心思想 纵深优先,一条路走到黑 层层推进,逐层扩散
数据结构 栈(递归调用栈 or 显式栈) 队列
典型场景 路径搜索、拓扑排序、连通性 最短路径、层序遍历
内存特点 深度大时栈可能爆炸 宽度大时队列可能爆炸

二、递归实现:DFS 最自然的写法

递归和 DFS 是天生一对——函数的层层调用本身就是栈。

function dfs(root, res = []) {
if (!root) {
return; // 退出条件:走到叶子节点下面了
}
res.push(root.val); // 处理当前节点(先序)
dfs(root.left, res); // 递归左子树
dfs(root.right, res); // 递归右子树
return res; // 返回结果
}

这段代码只有 8 行,但信息量不小:

  • if (!root) 是退出条件——对应"撞了南墙",到了空节点就该回头了
  • 先 push 再递归左右——这是先序遍历,调整顺序可以得到中序/后序
  • 递归本质是系统帮你维护调用栈——后面我们会手动模拟这个栈
  • 递归版简洁优雅,缺点是深度过大时可能爆栈(Stack Overflow)。

    三、迭代实现:手动用栈模拟递归

    搞懂迭代版 DFS 是面试的分水岭——面试官想看你是否真的理解"栈"这个核心。

    function dfsPreOrderInter(root) {
    if (!root) {
    return;
    }
    const stack = [root]; // 手动维护一个栈
    const res = [];
    while (stack.length) {
    const node = stack.pop(); // 弹出栈顶,处理当前节点
    res.push(node.val);
    // 注意:先 push right 再 push left,因为栈是后进先出
    if (node.right) {
    stack.push(node.right);
    }
    if (node.left) {
    stack.push(node.left);
    }
    }
    return res;
    }

    关键点来了:为什么先 push 右子节点?

    因为栈是 LIFO(后进先出)。我们希望先访问左子节点,所以左子节点必须后入栈,这样它才会先被 pop 出来。这个细节搞反了整棵树的遍历顺序就反了,面试现场翻车的十有八九栽在这里。

    把递归版和迭代版放一起看,核心逻辑完全一致:

    递归版迭代版
    函数调用栈 手动维护数组栈
    隐式回溯 显式 pop/push
    简洁但有爆栈风险 稍复杂但完全可控
    赞(0)
    未经允许不得转载:171主机测评 » 只会递归DFS远远不够!
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址