JavaScript深度优先遍历和广度优先遍历

深度与广度遍历
本文深入探讨了图遍历的两种核心算法:深度优先遍历(DFS)和广度优先遍历(BFS)。通过递归实现深度优先遍历,确保了从每个节点开始探索其所有子节点;而广度优先遍历则使用队列,按层次顺序访问所有节点,确保了‘先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问’的原则。

深度优先遍历DFS 
假设初始状态是图中所有顶点均未被访问,则从某个顶点v出发,首先访问该顶点然后依次从它的各个未被访问的邻接点出发深度优先搜索遍历图,直至图中所有和v有路径相通的顶点都被访问到。若此时尚有其他顶点未被访问到,则另选一个未被访问的顶点作起始点,重复上述过程,直至图中所有顶点都被访问到为止。

// 用递归进行深度优先遍历
function deepFirstSearch(node) {
    const nodeList = [];
    
    if (node !== null) {
        nodeList.push(node);
        const childNodes = node.children;
        Array.from(childNodes).forEach((childNode) => {
            nodeList = nodeList.concat(deepTraversal(childNode));
        })
    }

    return nodeList;
}

广度优先遍历 BFS
从图中某顶点v出发,在访问了v之后依次访问v的各个未曾访问过的邻接点,然后分别从这些邻接点出发依次访问它们的邻接点,并使得“先被访问的顶点的邻接点先于后被访问的顶点的邻接点被访问,直至图中所有已被访问的顶点的邻接点都被访问到。 如果此时图中尚有顶点未被访问,则需要另选一个未曾被访问过的顶点作为新的起始点,重复上述过程,直至图中所有顶点都被访问到为止。

// 用队列进行广度优先遍历
function breadthFirstSearch (node) {
    const queue = [];
    const nodeList = [];
    
    if (node !== null) {
        queue.push(node);
        while(queue.length > 0) {
            const item = queue.shift();
            nodeList.push(item);

            Array.from(item.children).forEach((child) => {
                queue.push(child);
            });
        }
    }

    return nodeList;
}

 

<think>我们正在讨论二叉树的深度优先遍历DFS广度优先遍历BFS)。用户已经提供了两个引用,分别是JavaJavaScript广度优先遍历实现。现在用户想了解两种遍历的原理、实现方法特点。 首先,我们需要明确两种遍历的基本概念: 1. 深度优先遍历DFS): - 原理:尽可能深地遍历树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。在二叉树中,DFS有三种常见的顺序:先序(根-左-右)、中序(左-根-右)后序(左-右-根)。 - 实现方法:通常使用递归或栈(非递归)来实现。 - 特点:不需要记住所有的节点,所以空间复杂度较低(通常为树的高度O(h),h为树的高度)。但可能进入很深的层次,如果树很深可能会造成栈溢出。 2. 广度优先遍历BFS): - 原理:从根节点开始,逐层遍历,先访问离根节点最近的节点。也称为层次遍历。 - 实现方法:使用队列(FIFO)辅助实现。首先将根节点入队,然后出队并访问,接着将其左右子节点(如果存在)入队,重复直到队列为空。 - 特点:需要保存每一层的节点,所以空间复杂度较高(最坏情况下,即平衡二叉树时,空间复杂度为O(n))。它保证了按照层次的顺序访问节点,适合寻找最短路径(在树中就是从根到叶子的最短路径,但实际上树中路径通常用深度优先,广度优先在图中更常用于最短路径)。 根据用户提供的引用,我们看到广度优先遍历的实现(JavaJavaScript)都使用了队列。现在,我们还需要给出深度优先遍历的代码示例(以先序遍历为例),并分析两种遍历的时间复杂度空间复杂度。 注意:用户提供的广度优先遍历代码中,Java版本使用了LinkedList作为队列,但注意它的pushpop方法实际上是栈的操作(LIFO),这会导致错误。正确的广度优先遍历应该使用队列的入队(add或offer)出队(poll)操作。在Java中,LinkedList实现了Queue接口,我们可以用add(入队)poll(出队)。同样,JavaScript版本中,用户自定义了队列(假设Queue类有enQueue、deQueue等方法),但代码中使用了front(获取队头)deQueue(出队)以及enQueue(入队),这是正确的。 因此,在回答中,我们需要指出Java代码中的错误并给出修正。同时,给出深度优先遍历递归递归实现。 下面构建回答: 1. 原理部分:分别解释DFSBFS。 2. 实现方法:给出DFS(先序)的递归递归代码(JavaJavaScript),以及BFS的代码(修正后的Java已有的JavaScript)。 3. 特点:比较两者的时间复杂度空间复杂度,以及使用场景。 注意:用户引用中的Java代码有误,我们修正如下(将push改为add,pop改为poll,因为队列是先进先出,pushpop是栈的操作): 原Java代码: queue.push(root); // 应该用offer或add queue.pop(); // 应该用poll 修正后的Java广度优先遍历: LinkedList<TreeNode> queue = new LinkedList<>(); queue.offer(root); // 或者add while (!queue.isEmpty()) { TreeNode treeNode = queue.poll(); System.out.print(treeNode.val + " "); if (treeNode.leftNode != null) { queue.offer(treeNode.leftNode); } if (treeNode.rightNode != null) { queue.offer(treeNode.rightNode); } } 另外,深度优先遍历(先序)的递归实现(Java): public void preOrder(TreeNode root) { if (root == null) return; System.out.print(root.val + " "); preOrder(root.left); preOrder(root.right); } 非递归实现(使用栈,Java): public void preOrder(TreeNode root) { if (root == null) return; Stack<TreeNode> stack = new Stack<>(); stack.push(root); while (!stack.isEmpty()) { TreeNode node = stack.pop(); System.out.print(node.val + " "); // 注意:栈是后进先出,所以先压右孩子,再压左孩子 if (node.right != null) { stack.push(node.right); } if (node.left != null) { stack.push(node.left); } } } 同样,我们也可以给出JavaScript版本的深度优先遍历(先序)递归递归实现。 最后,总结特点: - DFS(深度优先): 时间复杂度:O(n) (每个节点访问一次) 空间复杂度:平均O(log n)(树的高度),最坏(链表状)O(n) 适合遍历整个树,尤其是需要深度探索的情况,如检查路径、回溯等。 - BFS广度优先): 时间复杂度:O(n) 空间复杂度:O(n)(最坏情况,即最后一层的节点数) 适合按层次遍历,或者寻找从根到某个节点的最短路径(在树中,由于路径唯一,最短路径即层次最小的路径)。 相关问题生成: 1. 深度优先遍历的中序后序遍历有什么应用场景? 2. 广度优先遍历在二叉树中除了层次遍历还有哪些应用? 3. 如何用广度优先遍历求解二叉树的最大宽度? 4. 深度优先遍历的非递归实现中,中序遍历后序遍历如何实现? 注意:在回答中,我们需要引用用户提供的引用[1][2](虽然它们有错误,但我们可以指出并修正),并在修正后的代码处引用[1](因为用户引用[1]是关于广度优先遍历的,我们修正了它)。 现在,我们按照要求组织答案,使用中文,并添加引用标识。 </think>### 二叉树深度优先遍历DFS)与广度优先遍历BFS)原理与实现 #### **一、核心原理对比** | **特性** | **深度优先遍历DFS)** | **广度优先遍历BFS)** | |------------------|---------------------------------------------|---------------------------------------| | **遍历顺序** | 优先访问深层节点(纵向深入) | 按层级访问节点(横向扩展) | | **数据结构** | 栈(递归或显式栈) | 队列(FIFO) | | **空间复杂度** | $O(h)$($h$为树高) | $O(w)$($w$为树最大宽度) | | **时间复杂度** | $O(n)$($n$为节点数) | $O(n)$ | | **适用场景** | 路径查找、回溯问题 | 最短路径、层级分析 | --- #### **二、实现方法与代码示例** 1. **深度优先遍历DFS)** - **递归实现(先序遍历)**: ```java // Java 递归实现 public void dfs(TreeNode root) { if (root == null) return; System.out.print(root.val + " "); // 访问当前节点 dfs(root.leftNode); // 遍历左子树 dfs(root.rightNode); // 遍历右子树 } ``` - **非递归实现(显式栈)**: ```javascript // JavaScript 栈实现 function depthFirst(root) { const stack = [root]; while (stack.length) { const node = stack.pop(); console.log(node.data); if (node.right) stack.push(node.right); // 右子节点先入栈 if (node.left) stack.push(node.left); // 左子节点后入栈 } } ``` 2. **广度优先遍历BFS)** - **队列实现(修正版)**: ```java // Java 队列实现(修正push/pop为队列操作) public void bfs(TreeNode root) { LinkedList<TreeNode> queue = new LinkedList<>(); queue.offer(root); // 入队(尾部添加) while (!queue.isEmpty()) { TreeNode node = queue.poll(); // 出队(头部移除) System.out.print(node.val + " "); if (node.leftNode != null) queue.offer(node.leftNode); if (node.rightNode != null) queue.offer(node.rightNode); } } ``` > 💡 **修正说明**:引用[1]原始代码误用`push/pop`(栈操作),正确应使用`offer/poll`(队列操作)[^1]。 ```javascript // JavaScript 队列实现(引用[2]) function breadthFirst(root) { const q = new Queue(); // 假设已实现队列类 q.enQueue(root); while (!q.isEmpty()) { const node = q.front(); console.log(node.data); q.deQueue(); if (node.left) q.enQueue(node.left); if (node.right) q.enQueue(node.right); } } ``` --- #### **三、核心特点分析** 1. **DFS 特点** - **优势**:空间效率高(只需存储单路径节点) - **劣势**:可能陷入深层分支,不保证最短路径 - **变体**:先序(根-左-右)、中序(左-根-右)、后序(左-右-根) 2. **BFS 特点** - **优势**:天然按层级访问,适合最短路径问题 - **劣势**:空间开销大(需存储整层节点) - **关键**:依赖队列的 **FIFO 特性**保证层级顺序[^1][^2] --- #### **四、应用场景对比** | **场景** | **推荐算法** | **原因** | |------------------------|--------------|------------------------------------| | 二叉树层级统计 | BFS | 直接按层遍历,无需回溯 | | 根节点到叶子的所有路径 | DFS | 递归天然保存路径状态 | | 二叉树镜像对称判断 | BFS | 同层节点对比更高效 | | 二叉树最大深度 | DFS | 递归深度即树高,空间复杂度$O(1)$ | > ⚠️ **陷阱**:BFS在极端倾斜树(如链表状)中空间复杂度退化为$O(n)$,而DFS仍为$O(h)$。 --- ### 相关问题 1. 深度优先遍历中,递归实现与非递归实现的性能差异有哪些? 2. 如何用广度优先遍历求解二叉树每层的最大值? 3. 在深度优先遍历中,中序遍历为什么适合二叉搜索树? 4. 如何处理超大规模树的遍历以避免栈溢出(DFS)或内存溢出(BFS)? [^1]: 引用自用户提供的Java广度优先遍历代码,已修正队列操作 [^2]: 引用自用户提供的JavaScript广度优先遍历代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值