二叉树之深度优先遍历与广度优先遍历

本文详细介绍了如何通过前序和中序遍历构造二叉树的方法,并实现了二叉树的深度优先与广度优先遍历。通过具体示例展示了算法的实现过程。
  • 通过二叉树的前序和中序遍历确定二叉树:就是找到中序遍历中二叉树的根结点的下标,然后根据左右两子树进行递归
  • 二叉树的深度优先遍历和广度优先遍历:就是利用栈和队列进行存取元素
/*
 * @Author:Beer
 * @Date:2019-10-06
 * @Description:
 * 通过二叉树的前序和中序遍历确定二叉树:就是找到中序遍历中二叉树的根结点的下标,然后根据左右两子树进行递归
 * 二叉树的深度优先遍历和广度优先遍历:就是利用栈和队列进行存取元素
 */

import java.util.*;

class TreeNode {
    TreeNode left;
    TreeNode right;
    int val;

    public TreeNode(int val) {
        this.val = val;
    }
}

public class TestLeverTree {
    public static void main(String[] args) {
        int[] pre = {1, 2, 4, 7, 3, 5, 6, 8};
        int[] ino = {4, 7, 2, 1, 5, 3, 8, 6};
        //构造二叉树
        TreeNode root = buildTree(pre, 0, pre.length - 1, ino, 0, ino.length - 1);
        //进行二叉树的层次遍历
        levelTree(root);
        System.out.println();
        //非递归版本的深度优先遍历
        depthThree(root);
    }

    /**
     * 通过二叉树的前序、中序构造二叉树
     *
     * @param pre
     * @param ps
     * @param pe
     * @param ino
     * @param is
     * @param ie
     */
    public static TreeNode buildTree(int[] pre, int ps, int pe, int[] ino, int is, int ie) {
        if (ps > pe) {
            return null;
        }
        int val = pre[ps];
        int index = is;
        while (is < ie && ino[index] != val) {
            index++;
        }
        if (index > ie) {
            throw new RuntimeException("Illegal input");
        }
        TreeNode node = new TreeNode(val);
        // 递归构建当前根结点的左子树,左子树的元素个数:index-is+1个
        // 左子树对应的前序遍历的位置在[ps+1, ps+index-is]
        // 左子树对应的中序遍历的位置在[is, index-1]
        node.left = buildTree(pre, ps + 1, ps + index - is, ino, is, index - 1);
        // 递归构建当前根结点的右子树,右子树的元素个数:ie-index个
        // 右子树对应的前序遍历的位置在[ps+index-is+1, pe]
        // 右子树对应的中序遍历的位置在[index+1, ie]
        node.right = buildTree(pre, ps + index - is + 1, pe, ino, index + 1, ie);
        return node;
    }

    /**
     * 二叉树的层次遍历
     *
     * @param root
     */
    public static void levelTree(TreeNode root) {
        Queue<TreeNode> queue = new LinkedList<>();
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode node = queue.poll();
            System.out.print(node.val);
            if (node.left != null) {
                queue.add(node.left);
            }
            if (node.right != null) {
                queue.add(node.right);
            }

        }
    }

    /**
     * 二叉树的深度优先遍历
     * @param root
     */
    public static void depthThree(TreeNode root) {
        Stack<TreeNode> stack = new Stack<>();
        stack.add(root);
        while (!stack.isEmpty()) {
            TreeNode node = stack.pop();
            System.out.print(node.val);
            if (node.right != null) {
                stack.add(node.right);
            }
            if (node.left != null) {
                stack.add(node.left);
            }
        }
    }
}

结果:
12345678
12473568

<think>我们正在讨论二叉树深度优先遍历(DFS)和广度优先遍历(BFS)。用户已经提供了两个引用,分别是Java和JavaScript的广度优先遍历实现。现在用户想了解两种遍历的原理、实现方法和特点。 首先,我们需要明确两种遍历的基本概念: 1. 深度优先遍历(DFS): - 原理:尽可能深地遍历树的分支。当节点v的所在边都已被探寻过,搜索将回溯到发现节点v的那条边的起始节点。在二叉树中,DFS有三种常见的顺序:先序(根-左-右)、中序(左-根-右)和后序(左-右-根)。 - 实现方法:通常使用递归或栈(非递归)来实现。 - 特点:不需要记住所有的节点,所以空间复杂度较低(通常为树的高度O(h),h为树的高度)。但可能进入很深的层次,如果树很深可能会造成栈溢出。 2. 广度优先遍历(BFS): - 原理:从根节点开始,逐层遍历,先访问离根节点最近的节点。也称为层次遍历。 - 实现方法:使用队列(FIFO)辅助实现。首先将根节点入队,然后出队并访问,接着将其左右子节点(如果存在)入队,重复直到队列为空。 - 特点:需要保存每一层的节点,所以空间复杂度较高(最坏情况下,即平衡二叉树时,空间复杂度为O(n))。它保证了按照层次的顺序访问节点,适合寻找最短路径(在树中就是从根到叶子的最短路径,但实际上树中路径通常用深度优先,广度优先在图中更常用于最短路径)。 根据用户提供的引用,我们看到广度优先遍历的实现(Java和JavaScript)都使用了队列。现在,我们还需要给出深度优先遍历的代码示例(以先序遍历为例),并分析两种遍历的时间复杂度和空间复杂度。 注意:用户提供的广度优先遍历代码中,Java版本使用了LinkedList作为队列,但注意它的push和pop方法实际上是栈的操作(LIFO),这会导致错误。正确的广度优先遍历应该使用队列的入队(add或offer)和出队(poll)操作。在Java中,LinkedList实现了Queue接口,我们可以用add(入队)和poll(出队)。同样,JavaScript版本中,用户自定义了队列(假设Queue类有enQueue、deQueue等方法),但代码中使用了front(获取队头)和deQueue(出队)以及enQueue(入队),这是正确的。 因此,在回答中,我们需要指出Java代码中的错误并给出修正。同时,给出深度优先遍历的递归和非递归实现。 下面构建回答: 1. 原理部分:分别解释DFS和BFS。 2. 实现方法:给出DFS(先序)的递归和非递归代码(Java和JavaScript),以及BFS的代码(修正后的Java和已有的JavaScript)。 3. 特点:比较两者的时间复杂度和空间复杂度,以及使用场景。 注意:用户引用中的Java代码有误,我们修正如下(将push改为add,pop改为poll,因为队列是先进先出,push和pop是栈的操作): 原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、付费专栏及课程。

余额充值