二叉树的前中后序遍历(迭代法)

目录

题目链接:

题目:

解题思路:

代码:

前序遍历:

中序遍历:

后序遍历:

总结:


题目链接:

144. 二叉树的前序遍历 - 力扣(LeetCode)

94. 二叉树的中序遍历 - 力扣(LeetCode)

145. 二叉树的后序遍历 - 力扣(LeetCode)

题目:


给你二叉树的根节点 root,返回它节点值的 前序 遍历。

示例 1:

输入:root = [1,null,2,3]
输出:[1,2,3]

(解释:二叉树结构为根节点是 1,1 的右子节点是 2,2 的左子节点是 3,前序遍历顺序为根 - 左 - 右,所以遍历结果为 1、2、3)

二叉树的中序遍历
简单 相关标签 相关企业 Ax
给定一个二叉树的根节点 root,返回 它的 中序 遍历。
示例 1:
(图为二叉树结构:节点 1 的右子节点是 2,节点 2 的左子节点是 3)
输入: root = [1,null,2,3]
输出: [1,3,2]

示例 2:
输入: root = []
输出: []

二叉树的后序遍历
简单 相关标签 相关企业 Ax
给你一棵二叉树的根节点 root,返回其节点值的后序遍历。
示例 1:
输入: root = [1,null,2,3]
输出: [3,2,1]
解释:
(下方有对应二叉树结构示意图,根节点是 1,1 的右子节点是 2,2 的左子节点是 3)
 

解题思路:

使用栈和集合 即可,还有统一迭代法的形式(后续有)

前序是中左右,但是进栈是中右左

那同理可知后序是左右中,那进栈中左右,再reverse即可

代码:

前序遍历:

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> res=new ArrayList<>();if(root==null) return res;Stack<TreeNode> st=new Stack<>();st.push(root);while(!st.empty()){TreeNode head=st.pop();res.add(head.val);if(head.right!=null) st.push(head.right);if(head.left!=null) st.push(head.left);}return res;}
}

二叉树前序遍历的迭代实现:基于栈的深度解析
二叉树的前序遍历是树结构操作中的基础算法,其 "根→左→右" 的访问顺序在很多场景中都有广泛应用。除了直观的递归实现外,迭代方式通过手动维护栈结构来模拟遍历过程,能有效避免递归可能带来的栈溢出问题。本文将详细解析一个基于栈的二叉树前序遍历迭代实现,帮助读者深入理解其原理、执行流程及优势。
前序遍历的核心概念
前序遍历(Preorder Traversal)是二叉树的三种基本遍历方式之一,其核心规则是:对于任意节点,先访问该节点本身,再遍历其左子树,最后遍历其右子树,即 "根→左→右" 的访问顺序。每个节点被访问且仅被访问一次。

例如,对于二叉树 [1,null,2,3](根节点为 1,1 的右子节点为 2,2 的左子节点为 3),其前序遍历结果为 [1,2,3],这是按照节点 1→左子树(空)→右子树(节点 2→左子树节点 3→右子树(空))的顺序访问的结果。
迭代实现的核心思路
递归实现利用函数调用栈自然地维护了遍历顺序,而迭代实现则需要手动创建栈来模拟这一过程。前序遍历的迭代实现核心思路是:

使用栈存储待访问的节点
每次弹出栈顶节点并访问(加入结果列表)
按照 "右子节点→左子节点" 的顺序将弹出节点的子节点入栈(因为栈是后进先出结构,这样才能保证左子节点先被访问)
重复步骤 2-3,直到栈为空

这种方式通过栈的后进先出(LIFO)特性,完美模拟了前序遍历 "根→左→右" 的访问顺序。
完整代码实现
下面是二叉树前序遍历的迭代实现代码:

二叉树前序遍历的迭代实现
V1
创建时间:19:56
代码逐行深度解析
1. 二叉树节点定义
代码开头定义了二叉树节点的结构:

java
运行
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */

这个类定义了二叉树的基本构成单元,包含:

val:节点存储的整数 value
left:指向左子节点的引用
right:指向右子节点的引用
三个构造方法:分别用于创建不同状态的节点

所有二叉树操作都基于这个节点结构,理解它是掌握树遍历的基础。
2. 初始化结果列表
java
运行
List<Integer> res = new ArrayList<>();

创建一个 ArrayList 实例 res 用于存储遍历过程中收集的节点值。选择 ArrayList 是因为它在添加元素和随机访问时性能优良,适合存储有序的遍历结果。
3. 处理空树情况
java
运行
if (root == null) return res;

这是一个重要的边界条件处理。当输入的根节点为 null 时(表示空树),直接返回空的结果列表,避免后续不必要的操作。
4. 初始化栈并推入根节点
java
运行
Stack<TreeNode> st = new Stack<>();
st.push(root);

创建一个 Stack 实例 st,用于存储待访问的节点。栈的特性(后进先出)是实现前序遍历的关键。
将根节点 root 推入栈中,作为遍历的起始点。
5. 栈循环处理逻辑
java
运行
while (!st.empty()) {
    // 弹出栈顶节点并访问
    TreeNode head = st.pop();
    res.add(head.val);
    
    // 右子节点先入栈
    if (head.right != null) {
        st.push(head.right);
    }
    
    // 左子节点后入栈
    if (head.left != null) {
        st.push(head.left);
    }
}

这个循环是迭代实现的核心,只要栈不为空,就持续处理节点,直到所有节点都被访问。循环内部包含三个关键步骤:
5.1 弹出栈顶节点并访问
java
运行
TreeNode head = st.pop();
res.add(head.val);

弹出栈顶节点 head,这是当前要访问的节点
将节点值 head.val 加入结果列表 res,完成对该节点的访问

这一步对应前序遍历中的 "根" 操作,即先访问当前节点。
5.2 右子节点入栈
java
运行
if (head.right != null) {
    st.push(head.right);
}

将当前节点的右子节点推入栈中(如果存在)。注意这里右子节点先入栈,这是因为栈是后进先出的结构,后续左子节点入栈后会被先弹出访问,从而保证 "左→右" 的顺序。
5.3 左子节点入栈
java
运行
if (head.left != null) {
    st.push(head.left);
}

将当前节点的左子节点推入栈中(如果存在)。由于左子节点后于右子节点入栈,根据栈的后进先出特性,左子节点会先被弹出访问,这就保证了前序遍历 "根→左→右" 的顺序。
6. 返回遍历结果
java
运行
return res;

当栈为空时,所有节点都已被访问,结果列表 res 中已按前序遍历顺序存储了所有节点值,将其返回即可。
算法执行过程演示
为了直观理解迭代实现的执行流程,我们以示例 root = [1,null,2,3] 为例进行详细演示:

该二叉树的结构如下:

plaintext
    1
     \
      2
     /
    3

执行步骤分解:

初始化 res = [],检查 root 不为 null
创建栈 st,将 root(节点 1)入栈,st = [1]
进入循环(栈不为空):
弹出栈顶节点 1,res.add(1) → res = [1]
节点 1 的右子节点是 2,将 2 入栈 → st = [2]
节点 1 的左子节点是 null,不操作
栈现在为 [2]
继续循环(栈不为空):
弹出栈顶节点 2,res.add(2) → res = [1,2]
节点 2 的右子节点是 null,不操作
节点 2 的左子节点是 3,将 3 入栈 → st = [3]
栈现在为 [3]
继续循环(栈不为空):
弹出栈顶节点 3,res.add(3) → res = [1,2,3]
节点 3 的右子节点是 null,不操作
节点 3 的左子节点是 null,不操作
栈现在为空
循环结束,返回 res = [1,2,3],与预期结果一致

从执行过程可以清晰看到,通过 "根节点出栈访问→右子节点入栈→左子节点入栈" 的循环操作,完美实现了 "根→左→右" 的前序遍历顺序。栈的后进先出特性在这里起到了关键作用。
算法复杂度分析
时间复杂度
对于包含 n 个节点的二叉树,每个节点都会被推入栈一次并弹出栈一次,因此入栈和出栈操作的总次数为 O (n)。每个节点的值会被加入结果列表一次,也是 O (n) 操作。因此,整体时间复杂度为 O(n),其中 n 是二叉树的节点总数。
空间复杂度
空间复杂度主要来自两个方面:

栈的存储空间:在最坏情况下(二叉树退化为左斜树,即每个节点只有左子节点),栈需要存储所有节点,此时空间复杂度为 O(n);在平均情况下(平衡二叉树),栈的深度为 log (n),空间复杂度为 O(log n)。
结果列表:用于存储遍历结果的列表需要容纳所有 n 个节点的值,因此额外占用 O(n) 的空间。

综合来看,算法的整体空间复杂度为 O(n)。
迭代实现与递归实现的对比
前序遍历有递归和迭代两种主要实现方式,它们各有优缺点:

实现方式    核心思想    优点    缺点
递归实现    利用函数调用栈,按照 "根→左→右" 顺序递归访问    代码简洁直观,易于理解和编写    对于深度极大的树可能导致栈溢出;函数调用有一定性能开销
迭代实现    手动维护栈,模拟递归过程    避免栈溢出风险;性能开销较小    代码相对复杂;需要手动管理栈的操作

递归实现的代码通常更简洁:

java
运行
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    preorder(res, root);
    return res;
}

private void preorder(List<Integer> res, TreeNode root) {
    if (root == null) return;
    res.add(root.val);
    preorder(res, root.left);
    preorder(res, root.right);
}

但迭代实现通过手动控制栈,避免了递归调用可能带来的栈溢出问题,在处理大型二叉树时更为稳健。
算法的优化与扩展
空间优化思考
当前实现的空间复杂度为 O (n),在某些场景下可以进一步优化。有一种 Morris 遍历算法可以实现 O (1) 空间复杂度的前序遍历,其核心思想是利用树的空指针建立临时链接,避免使用栈或递归。但 Morris 算法实现较为复杂,可读性较差,通常在对空间有严格要求的场景下使用。
扩展到其他遍历方式
基于栈的迭代思想可以扩展到中序和后序遍历,只需调整节点访问和入栈的顺序:

中序遍历:先将左子树全部入栈,弹出节点时访问,再处理右子树
后序遍历:可以通过两个栈实现,或通过标记节点是否已访问来实现

例如,后序遍历的迭代实现(双栈法):

java
运行
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    Stack<TreeNode> s1 = new Stack<>();
    Stack<TreeNode> s2 = new Stack<>();
    s1.push(root);
    
    while (!s1.isEmpty()) {
        TreeNode node = s1.pop();
        s2.push(node);
        if (node.left != null) s1.push(node.left);
        if (node.right != null) s1.push(node.right);
    }
    
    while (!s2.isEmpty()) {
        res.add(s2.pop().val);
    }
    return res;
}
实际应用场景
前序遍历的迭代实现在实际开发中有广泛应用:

树的序列化与反序列化:前序遍历常用于将树结构转换为线性序列(序列化),以及从序列重建树(反序列化)。
路径搜索:在从根节点到目标节点的路径搜索中,前序遍历可以及早发现目标节点,适合深度优先搜索(DFS)场景。
表达式树的前缀表达式生成:前序遍历表达式树可以得到前缀表达式(波兰式),这种表达式无需括号即可明确运算顺序,适合计算机求值。
语法分析:在编译器的语法分析阶段,前序遍历抽象语法树(AST)可用于生成中间代码。
文件系统遍历:文件系统通常组织为树结构,前序遍历可用于实现深度优先的文件搜索。
总结
本文详细解析了二叉树前序遍历的迭代实现,该实现通过手动维护栈结构,模拟了 "根→左→右" 的访问顺序。核心思路是:弹出栈顶节点并访问,然后按 "右子节点→左子节点" 的顺序将子节点入栈,利用栈的后进先出特性保证遍历顺序。

迭代实现的关键要点:

利用栈存储待访问的节点
弹出节点时立即访问(加入结果列表)
右子节点先入栈,左子节点后入栈,确保左子节点先被访问
循环处理直到栈为空

与递归实现相比,迭代实现避免了函数调用栈可能带来的栈溢出问题,在处理大型二叉树时更为稳健。理解这种实现方式不仅有助于掌握前序遍历算法,还能加深对栈数据结构和树遍历本质的理解。

在实际应用中,应根据具体场景选择合适的实现方式:对于代码简洁性要求高且树深度可控的场景,递归实现更合适;对于可能处理极深树的场景,迭代实现更为可靠。掌握前序遍历的迭代实现,对于深入学习数据结构和算法具有重要意义。

中序遍历:

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res=new ArrayList<>();if(root==null){return res;}Stack<TreeNode> st=new Stack<>();while(root!=null||!st.empty()){if(root!=null){st.push(root);root=root.left;}else{root=st.pop();res.add(root.val);root=root.right;}}return res;}
}

二叉树中序遍历的迭代实现:基于栈的深度解析
二叉树的中序遍历是树结构操作中最常用的遍历方式之一,其 "左→根→右" 的访问顺序在二叉搜索树(BST)中具有特殊意义 —— 能得到一个递增的有序序列。除了递归实现外,迭代方式通过手动维护栈结构来模拟遍历过程,能有效避免递归可能带来的栈溢出问题。本文将详细解析一个基于栈的二叉树中序遍历迭代实现,帮助读者深入理解其原理、执行流程及优势。

中序遍历的核心概念
中序遍历(Inorder Traversal)是二叉树的三种基本遍历方式之一,其核心规则是:对于任意节点,先遍历其左子树,再访问该节点本身,最后遍历其右子树,即 "左→根→右" 的访问顺序。每个节点被访问且仅被访问一次。

例如,对于二叉树 [1,null,2,3](根节点为 1,1 的右子节点为 2,2 的左子节点为 3),其中序遍历结果为 [1,3,2],这是按照左子树(1 的左子树为空)→节点 1→右子树(2 的左子树 3→节点 2→2 的右子树为空)的顺序访问的结果。

迭代实现的核心思路
递归实现利用函数调用栈自然地维护了遍历顺序,而迭代实现则需要手动创建栈来模拟这一过程。中序遍历的迭代实现核心思路是:

使用栈存储待访问的节点
先将当前节点的所有左子节点依次入栈,直到没有左子节点为止
弹出栈顶节点并访问(加入结果列表)
将当前节点指向弹出节点的右子节点,重复步骤 2-3
当当前节点为 null 且栈为空时,遍历结束
这种方式通过栈的后进先出(LIFO)特性,完美模拟了中序遍历 "左→根→右" 的访问顺序。

完整代码实现
下面是二叉树中序遍历的迭代实现代码:

二叉树中序遍历的迭代实现

V1

创建时间:20:04

代码逐行深度解析
1. 二叉树节点定义
代码开头定义了二叉树节点的结构:

java

运行

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */

这个类定义了二叉树的基本构成单元,包含:

val:节点存储的整数 value
left:指向左子节点的引用
right:指向右子节点的引用
三个构造方法:分别用于创建不同状态的节点
所有二叉树操作都基于这个节点结构,理解它是掌握树遍历的基础。

2. 初始化结果列表
java

运行

List<Integer> res = new ArrayList<>();
创建一个 ArrayList 实例 res 用于存储遍历过程中收集的节点值。选择 ArrayList 是因为它在添加元素和随机访问时性能优良,适合存储有序的遍历结果。

3. 处理空树情况
java

运行

if (root == null) {
    return res;
}
这是一个重要的边界条件处理。当输入的根节点为 null 时(表示空树),直接返回空的结果列表,避免后续不必要的操作。

4. 初始化栈
java

运行

Stack<TreeNode> st = new Stack<>();
创建一个 Stack 实例 st,用于存储待访问的节点。栈的特性(后进先出)是实现中序遍历的关键。与前序遍历不同,中序遍历的栈初始化时并不立即推入根节点,而是在循环中逐步处理。

5. 核心循环逻辑
java

运行

while (root != null || !st.empty()) {
    // 左子树全部入栈
    if (root != null) {
        st.push(root);
        root = root.left;
    } else {
        // 弹出栈顶节点并访问
        root = st.pop();
        res.add(root.val);
        
        // 转向右子树
        root = root.right;
    }
}

这个循环是迭代实现的核心,循环条件是 " 当前节点不为空 或 栈不为空 ",确保所有节点都能被处理。循环内部包含两个主要分支:

5.1 左子树入栈分支
java

运行

if (root != null) {
    st.push(root);
    root = root.left;
}

当当前节点 root 不为空时,执行以下操作:

将当前节点 root 推入栈中(暂不访问,因为中序遍历需要先处理左子树)
将 root 指向其左子节点 root.left
这个过程会持续进行,直到 root 变为 null(即到达左子树的最左端)。这一步确保了我们先处理所有左子节点,符合中序遍历 "左" 的要求。

5.2 访问节点并处理右子树分支
java

运行

else {
    // 弹出栈顶节点并访问
    root = st.pop();
    res.add(root.val);
    
    // 转向右子树
    root = root.right;
}

当当前节点 root 为 null 时(表示左子树已处理完毕),执行以下操作:

从栈中弹出栈顶节点,这是当前需要访问的节点(左子树已处理完毕)
将节点值 root.val 加入结果列表 res,完成对该节点的访问(符合中序遍历 "根" 的要求)
将 root 指向其右子节点 root.right,准备处理右子树(符合中序遍历 "右" 的要求)
6. 返回遍历结果
java

运行

return res;
当循环结束时(当前节点为 null 且栈为空),所有节点都已被访问,结果列表 res 中已按中序遍历顺序存储了所有节点值,将其返回即可。

算法执行过程演示
为了直观理解迭代实现的执行流程,我们以示例 root = [1,null,2,3] 为例进行详细演示:

该二叉树的结构如下:

plaintext

    1
     \
      2
     /
    3
执行步骤分解:


执行左子树入栈分支:将 1 入栈 → st = [1],root 指向 1 的左子节点(null)

执行访问节点分支:弹出栈顶节点 1 → st = [],res.add(1) → res = [1]
root 指向 1 的右子节点(节点 2)

执行左子树入栈分支:将 2 入栈 → st = [2],root 指向 2 的左子节点(节点 3)

执行左子树入栈分支:将 3 入栈 → st = [2,3],root 指向 3 的左子节点(null)

执行访问节点分支:弹出栈顶节点 3 → st = [2],res.add(3) → res = [1,3]
root 指向 3 的右子节点(null)

执行访问节点分支:弹出栈顶节点 2 → st = [],res.add(2) → res = [1,3,2]
root 指向 2 的右子节点(null)


从执行过程可以清晰看到,通过 "左子树全部入栈→弹出节点访问→转向右子树" 的循环操作,完美实现了 "左→根→右" 的中序遍历顺序。栈在这里起到了暂存节点的作用,确保了节点按正确顺序被访问。

算法复杂度分析
时间复杂度
对于包含 n 个节点的二叉树,每个节点都会被推入栈一次并弹出栈一次,因此入栈和出栈操作的总次数为 O (n)。每个节点的值会被加入结果列表一次,也是 O (n) 操作。因此,整体时间复杂度为 O(n),其中 n 是二叉树的节点总数。

空间复杂度
空间复杂度主要来自两个方面:

综合来看,算法的整体空间复杂度为 O(n)。

迭代实现与递归实现的对比
中序遍历有递归和迭代两种主要实现方式,它们各有优缺点:

实现方式    核心思想    优点    缺点
递归实现    利用函数调用栈,按照 "左→根→右" 顺序递归访问    代码简洁直观,易于理解和编写    对于深度极大的树可能导致栈溢出;函数调用有一定性能开销
迭代实现    手动维护栈,模拟递归过程    避免栈溢出风险;性能开销较小    代码相对复杂;需要手动管理栈的操作
递归实现的代码通常更简洁:

java

运行

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    inorder(res, root);
    return res;
}

private void inorder(List<Integer> res, TreeNode root) {
    if (root == null) return;
    inorder(res, root.left);
    res.add(root.val);
    inorder(res, root.right);
}

但迭代实现通过手动控制栈,避免了递归调用可能带来的栈溢出问题,在处理大型二叉树时更为稳健。

算法的优化与扩展
空间优化思考
当前实现的空间复杂度为 O (n),在某些场景下可以进一步优化。Morris 遍历算法可以实现 O (1) 空间复杂度的中序遍历,其核心思想是利用树的空指针建立临时链接(线索),避免使用栈或递归。但 Morris 算法实现较为复杂,可读性较差,通常在对空间有严格要求的场景下使用。

扩展到其他遍历方式
基于栈的迭代思想可以扩展到前序和后序遍历,只需调整节点访问和入栈的顺序:

前序遍历:访问节点后,先将右子节点入栈,再将左子节点入栈
后序遍历:可以通过两个栈实现,或通过标记节点是否已访问来实现
例如,前序遍历的迭代实现:

java

运行

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    Stack<TreeNode> st = new Stack<>();
    st.push(root);
    
    while (!st.empty()) {
        TreeNode node = st.pop();
        res.add(node.val);
        if (node.right != null) st.push(node.right);
        if (node.left != null) st.push(node.left);
    }
    return res;
}
实际应用场景
中序遍历的迭代实现在实际开发中有广泛应用:


总结
本文详细解析了二叉树中序遍历的迭代实现,该实现通过手动维护栈结构,模拟了 "左→根→右" 的访问顺序。核心思路是:先将所有左子节点入栈,弹出节点时访问,然后转向右子树,利用栈的后进先出特性保证遍历顺序。

迭代实现的关键要点:

循环条件为 "当前节点不为空或栈不为空"
左子树全部入栈后再开始访问节点
访问节点后转向其右子树继续处理
栈在这里起到暂存节点的作用,确保按正确顺序访问
与递归实现相比,迭代实现避免了函数调用栈可能带来的栈溢出问题,在处理大型二叉树时更为稳健。理解这种实现方式不仅有助于掌握中序遍历算法,还能加深对栈数据结构和树遍历本质的理解。

在实际应用中,应根据具体场景选择合适的实现方式:对于代码简洁性要求高且树深度可控的场景,递归实现更合适;对于可能处理极深树的场景,迭代实现更为可靠。掌握中序遍历的迭代实现,对于深入学习数据结构和算法具有重要意义。

return Collections.reverse(res); Line 34: error: incompatible types: void cannot be converted to List<Integer> return Collections.reverse(res); ^

这个错误的原因是 Collections.reverse(res) 方法的返回值类型是 void(没有返回值),而你的方法声明需要返回一个 List<Integer> 类型的结果,因此出现了 " 无法将 void 转换为 List<Integer>" 的类型不匹配错误。

Collections.reverse() 方法的作用是原地反转列表中的元素(直接修改原列表),而不是返回一个新的反转后的列表。所以正确的用法应该是先调用反转方法,再返回原列表:

java

运行

// 错误写法
return Collections.reverse(res);

// 正确写法
Collections.reverse(res);  // 先原地反转列表
return res;                // 再返回反转后的列表

例如,在后序遍历的迭代实现中,如果你想通过反转前序遍历结果来得到后序遍历结果,正确的代码应该是:

java

运行

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    
    // 先进行类似前序遍历的操作(根→右→左)
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        res.add(node.val);
        if (node.left != null) stack.push(node.left);  // 左子节点后入栈
        if (node.right != null) stack.push(node.right); // 右子节点先入栈
    }
    
    // 反转结果得到后序遍历(左→右→根)
    Collections.reverse(res);
    return res;  // 返回反转后的列表
}

总结:Collections.reverse() 是一个修改原对象的方法,没有返回值,因此不能直接作为返回语句使用,需要分开写。

后序遍历:

/*** Definition for a binary tree node.* public class TreeNode {*     int val;*     TreeNode left;*     TreeNode right;*     TreeNode() {}*     TreeNode(int val) { this.val = val; }*     TreeNode(int val, TreeNode left, TreeNode right) {*         this.val = val;*         this.left = left;*         this.right = right;*     }* }*/
class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> res=new ArrayList<>();if(root==null){return res;}Stack<TreeNode> st=new Stack<>();st.push(root);while(!st.empty()){TreeNode node=st.pop();res.add(node.val);if(node.left!=null){st.push(node.left);}if(node.right!=null){st.push(node.right);}}Collections.reverse(res);return res;}
}

二叉树后序遍历的迭代实现:基于栈和反转的深度解析
二叉树的后序遍历是树结构操作中的重要遍历方式,其 "左→右→根" 的访问顺序在删除操作、表达式求值等场景中具有特殊价值。后序遍历的迭代实现相对复杂,但有一种巧妙的方法可以通过修改前序遍历并反转结果来高效实现。本文将详细解析这种基于栈和反转的后序遍历迭代实现,帮助读者深入理解其原理、执行流程及优势。
后序遍历的核心概念
后序遍历(Postorder Traversal)是二叉树的三种基本遍历方式之一,其核心规则是:对于任意节点,先遍历其左子树,再遍历其右子树,最后访问该节点本身,即 "左→右→根" 的访问顺序。每个节点被访问且仅被访问一次。

例如,对于二叉树 [1,null,2,3](根节点为 1,1 的右子节点为 2,2 的左子节点为 3),其后序遍历结果为 [3,2,1],这是按照左子树(3)→右子树(无)→节点 2→右子树(已处理)→节点 1 的顺序访问的结果。
基于反转的实现思路
后序遍历的直接迭代实现需要判断节点的访问状态,逻辑较为复杂。而一种巧妙的替代方案是:

执行一种 modified 前序遍历,访问顺序为 "根→右→左"
将遍历结果反转,得到的就是 "左→右→根" 的后序遍历结果

这种方法的原理是:

标准前序遍历:根→左→右
modified 前序遍历(交换左右子节点入栈顺序):根→右→左
反转 modified 前序遍历结果:左→右→根(即后序遍历)

这种思路将复杂的后序遍历转化为相对简单的前序遍历加反转操作,大大简化了代码实现。
完整代码实现
下面是基于这种思路的二叉树后序遍历迭代实现代码:

二叉树后序遍历的迭代实现(基于栈和反转)
V1
创建时间:20:24
代码逐行深度解析
1. 二叉树节点定义
代码开头定义了二叉树节点的结构:

java
运行
/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */

这个类定义了二叉树的基本构成单元,包含:

val:节点存储的整数 value
left:指向左子节点的引用
right:指向右子节点的引用
三个构造方法:分别用于创建不同状态的节点

所有二叉树操作都基于这个节点结构,理解它是掌握树遍历的基础。
2. 初始化结果列表
java
运行
List<Integer> res = new ArrayList<>();

创建一个 ArrayList 实例 res 用于存储遍历过程中收集的节点值。选择 ArrayList 是因为它在添加元素和支持反转操作时性能优良。
3. 处理空树情况
java
运行
if (root == null) {
    return res;
}

这是一个重要的边界条件处理。当输入的根节点为 null 时(表示空树),直接返回空的结果列表,避免后续不必要的操作。
4. 初始化栈并推入根节点
java
运行
Stack<TreeNode> st = new Stack<>();
st.push(root);

创建一个 Stack 实例 st,用于存储待访问的节点。栈的后进先出特性是实现 modified 前序遍历的关键。
将根节点 root 推入栈中,作为遍历的起始点。
5. 栈循环处理逻辑(modified 前序遍历)
java
运行
while (!st.empty()) {
    // 弹出栈顶节点并访问
    TreeNode node = st.pop();
    res.add(node.val);
    
    // 左子节点先入栈
    if (node.left != null) {
        st.push(node.left);
    }
    
    // 右子节点后入栈
    if (node.right != null) {
        st.push(node.right);
    }
}

这个循环实现了 "根→右→左" 的 modified 前序遍历,只要栈不为空,就持续处理节点,直到所有节点都被访问。循环内部包含三个关键步骤:
5.1 弹出栈顶节点并访问
java
运行
TreeNode node = st.pop();
res.add(node.val);

弹出栈顶节点 node,这是当前要访问的节点
将节点值 node.val 加入结果列表 res,完成对该节点的访问

这一步对应 modified 前序遍历中的 "根" 操作,即先访问当前节点。
5.2 左子节点入栈
java
运行
if (node.left != null) {
    st.push(node.left);
}

将当前节点的左子节点推入栈中(如果存在)。注意这里左子节点先入栈,这与标准前序遍历的入栈顺序相反。
5.3 右子节点入栈
java
运行
if (node.right != null) {
    st.push(node.right);
}

将当前节点的右子节点推入栈中(如果存在)。由于右子节点后于左子节点入栈,根据栈的后进先出特性,右子节点会先被弹出访问,这就保证了 modified 前序遍历 "根→右→左" 的顺序。
6. 反转结果列表
java
运行
Collections.reverse(res);

调用 Collections.reverse() 方法对结果列表进行原地反转。这一步是将 modified 前序遍历的 "根→右→左" 结果转换为后序遍历的 "左→右→根" 结果的关键。

需要注意的是,Collections.reverse() 方法的返回值为 void(无返回值),它直接修改原列表,因此不能直接将其作为返回值使用(如错误写法 return Collections.reverse(res);)。
7. 返回遍历结果
java
运行
return res;

当反转完成后,结果列表 res 中已按后序遍历顺序存储了所有节点值,将其返回即可。
算法执行过程演示
为了直观理解这种实现的执行流程,我们以示例 root = [1,null,2,3] 为例进行详细演示:

该二叉树的结构如下:

plaintext
    1
     \
      2
     /
    3

执行步骤分解:

初始化 res = [],检查 root(节点 1)不为 null
创建栈 st,将 root(节点 1)入栈,st = [1]
进入循环(栈不为空):
弹出栈顶节点 1,res.add(1) → res = [1]
节点 1 的左子节点是 null,不操作
节点 1 的右子节点是 2,将 2 入栈 → st = [2]
栈现在为 [2]
继续循环(栈不为空):
弹出栈顶节点 2,res.add(2) → res = [1,2]
节点 2 的左子节点是 3,将 3 入栈 → st = [3]
节点 2 的右子节点是 null,不操作
栈现在为 [3]
继续循环(栈不为空):
弹出栈顶节点 3,res.add(3) → res = [1,2,3]
节点 3 的左子节点是 null,不操作
节点 3 的右子节点是 null,不操作
栈现在为空
循环结束,执行 Collections.reverse(res) → res = [3,2,1]
返回 res = [3,2,1],与预期结果一致

从执行过程可以清晰看到:

modified 前序遍历得到 [1,2,3](根→右→左顺序)
反转后得到 [3,2,1](左→右→根顺序),即后序遍历结果

这种通过修改前序遍历顺序并反转结果的方法,巧妙地实现了后序遍历,大大简化了代码逻辑。
算法复杂度分析
时间复杂度
对于包含 n 个节点的二叉树:

每个节点会被推入栈一次并弹出栈一次,入栈和出栈操作的总次数为 O (n)
每个节点的值会被加入结果列表一次,是 O (n) 操作
Collections.reverse() 方法需要遍历列表一次,是 O (n) 操作

因此,整体时间复杂度为 O(n),其中 n 是二叉树的节点总数。
空间复杂度
空间复杂度主要来自三个方面:

栈的存储空间:在最坏情况下(二叉树退化为右斜树,即每个节点只有右子节点),栈需要存储所有节点,此时空间复杂度为 O(n);在平均情况下(平衡二叉树),栈的深度为 log (n),空间复杂度为 O(log n)。
结果列表:用于存储遍历结果的列表需要容纳所有 n 个节点的值,因此额外占用 O(n) 的空间。
反转操作:Collections.reverse() 方法是原地反转,不需要额外的空间。

综合来看,算法的整体空间复杂度为 O(n)。
与其他后序遍历实现的对比
后序遍历有多种迭代实现方式,主要包括:

实现方式    核心思想    优点    缺点
基于反转的实现    修改前序遍历为 "根→右→左",再反转结果    代码简洁,易于理解和实现    需要额外的反转操作;修改原列表
单栈标记法    使用栈存储节点,通过标记判断节点是否已访问    无需反转,直接得到结果    逻辑复杂,需要额外标记
双栈法    使用两个栈,第一个栈按 "根→左→右" 入栈,第二个栈存储弹出元素    思路清晰,无需标记    需要额外的栈空间

单栈标记法的示例代码:

java
运行
public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    Stack<Object[]> stack = new Stack<>();
    stack.push(new Object[]{root, false});
    
    while (!stack.isEmpty()) {
        Object[] entry = stack.pop();
        TreeNode node = (TreeNode) entry[0];
        boolean visited = (boolean) entry[1];
        
        if (visited) {
            res.add(node.val);
        } else {
            stack.push(new Object[]{node, true});
            if (node.right != null) stack.push(new Object[]{node.right, false});
            if (node.left != null) stack.push(new Object[]{node.left, false});
        }
    }
    return res;
}

相比之下,基于反转的实现代码最为简洁,易于理解和记忆,是实际开发中常用的后序遍历实现方式。
实际应用场景
后序遍历的这种实现方式在实际开发中有广泛应用:

二叉树的删除操作:删除节点时需要先删除左右子节点,再删除当前节点,与后序遍历顺序一致。
计算二叉树的深度:需要先计算左右子树深度,再取最大值加 1,适合后序遍历。
表达式树求值:后序遍历表达式树可以得到后缀表达式(逆波兰式),适合计算机直接求值。
路径总和问题:需要遍历到叶子节点后再计算路径总和,后序遍历提供了天然的回溯机制。
资源释放:在释放树形结构的资源时,需要先释放子节点资源,再释放父节点资源,与后序遍历顺序一致。
总结
本文详细解析了二叉树后序遍历的一种巧妙迭代实现,该实现通过以下步骤完成:

执行 "根→右→左" 的 modified 前序遍历(通过调整左右子节点的入栈顺序实现)
反转遍历结果,得到 "左→右→根" 的后序遍历结果

这种方法的核心优势在于将复杂的后序遍历转化为相对简单的前序遍历加反转操作,大大简化了代码实现。代码的关键要点包括:

左子节点先入栈,右子节点后入栈,确保弹出顺序为 "根→右→左"
使用 Collections.reverse() 方法对结果进行原地反转
注意 Collections.reverse() 无返回值,需先调用再返回原列表

与其他后序遍历实现相比,这种方法代码简洁、易于理解和记忆,是实际开发中推荐的实现方式。理解这种实现不仅有助于掌握后序遍历算法,还能培养对不同遍历方式之间联系的洞察力,为解决更复杂的树结构问题奠定基础。

在实际应用中,这种实现方式特别适合对代码简洁性要求较高,且树的规模在可接受范围内的场景。掌握它对于深入学习数据结构和算法具有重要意义。

总结:

本文系统介绍了二叉树前序、中序和后序遍历的迭代实现方法。前序遍历采用栈结构,按;根→右→左 ;顺序入栈;中序遍历先全部左子树入栈再访问节点;后序遍历则通过修改前序遍历顺序为&quot;根→右→左 ;后反转结果。三种遍历时间复杂度均为O(n),空间复杂度最坏O(n)。与递归实现相比,迭代方法避免了栈溢出风险,处理大型树更稳定。每种遍历方式都配有详细代码解析、执行示例和复杂度分析,并讨论了实际应用场景,为掌握树结构操作提供了系统指导。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/95826.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/95826.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

redis的数据类型:string

文章目录String类型介绍redis采用的字符集json类型介绍String类型的命令set key value [EX seconds] [NX|XX]incr keyincr对操作的key对应的value类型有限制吗&#xff1f;incr key操作的返回值是什么&#xff1f;incr操作的key可以不存在吗&#xff1f;多个客户端同时针对同…

传统神经网络实现-----手写数字识别(MNIST)项目

完整代码&#xff1a;# import torch # print(torch.__version__)#1.X 1、验证安装的开发环境是否正确&#xff0c; MNIST包含70,000张手写数字图像: 60,000张用于训练&#xff0c;10,000张用于测试。 图像是灰度的&#xff0c;28x28像素的&#xff0c;并且居中的&#xff…

工业机器人标杆的数字化突围,珞石机器人如何以CRM实现业务重塑

在智能制造浪潮下&#xff0c;工业机器人行业正迎来快速增长。作为国内领先的机器人制造商&#xff0c;珞石机器人面对业务规模的迅速扩张&#xff0c;意识到传统的管理方式已无法满足企业发展需求&#xff0c;急需通过数字化升级破解管理难题。因此珞石机器人选择引入纷享销客…

NVIDIA GPU的指令集详细介绍

这是一个非常核心且深入的话题。GPU的指令集架构&#xff08;Instruction Set Architecture, ISA&#xff09;是理解GPU如何工作的关键&#xff0c;它直接体现了GPU为大规模并行计算而生的设计哲学。下面我将详细、全面地介绍GPU的指令集。 第一部分&#xff1a;核心哲学 —— …

Day 17: 3D点云深度学习专项 - 理论深度与面试精通之路

Day 17: 3D点云深度学习专项 - 理论深度与面试精通之路 🎯 学习目标:深度理解3D点云核心理论,获得该领域面试入场券 ⏰ 预计用时:6小时 (理论深度4h + 面试准备2h) 🎨 教学特色:理论优先 + 概念深度 + 面试导向 + 行业认知 🎯 今日学习大纲 1. 点云AI的理论基础:几何…

【经济学】量化模型TradingAgents 工具集成层与数据(财报+ 基本信息指标+基本面分析)+ChromaDB 客户端+财务情况记忆库

文章目录Toolkit 作用Toolkit 逐函数解析1. 获取默认配置2. update_config3. config4. __init__5. get_reddit_news6. get_finnhub_news7. get_reddit_stock_info8. get_chinese_social_sentiment9. get_finnhub_company_insider_sentiment10. get_YFin_data11. get_YFin_data_…

Uni-App + Vue onLoad与onLaunch执行顺序问题完整解决方案 – 3种实用方法详解

导读&#xff1a;在 Uni-app Vue 小程序应用开发中&#xff0c;你是否遇到过页面加载时全局数据还未准备好的问题&#xff1f;本文将深入分析onLoad生命周期钩子在onLaunch未完成时就执行的常见问题&#xff0c;并提供三种实用的解决方案。 &#x1f4cb; 问题描述 在 Vue 应…

25、SSH远程部署到另一台机器

25、SSH远程部署到另一台机器 因为不是每一台服务器都有jenkins的&#xff0c;一般都是一台jenkins&#xff0c;部署很多机器 1、安装插件 Publish Over SSH2、配置另一台机器 # 生成秘钥 ssh-keygen -t dsa# 把公钥复制到要访问的机器 ssh-copy-id root目标机器的ip# 第一次要…

2025年金融专业人士职业认证发展路径分析

在金融行业数字化转型的背景下&#xff0c;专业认证作为提升个人能力的一种方式&#xff0c;受到越来越多从业者的关注。本文基于行业发展趋势&#xff0c;分析6个金融相关领域的专业资格认证&#xff0c;为职业发展提供参考。一、CDA数据分析师认证含金量CDA数据分析师是数据领…

日用百货新零售小程序设计与开发(代码+数据库+LW)

摘要 本文设计并开发了一款基于Java、Spring Boot和MySQL的日用百货新零售小程序&#xff0c;旨在通过数字化手段优化日用百货的销售与配送流程&#xff0c;满足用户便捷购物的需求。系统采用前后端分离架构&#xff0c;前端通过微信小程序实现用户交互&#xff0c;后端基于Sp…

【Git】查看差异 删除文件 忽略文件

- 第 122 篇 - Date: 2025 - 09 - 07 Author: 郑龙浩&#xff08;仟墨&#xff09; 文章目录查看差异 && 删除文件 && 忽略文件1 git diff 可以查看哪些&#xff1f;基本用法比较不同提交比较分支文件比较其他2 彻底删除文件3 忽略文件「1」应该忽略哪些文件&a…

HarmonyOS应用开发:三层工程架构

引言 在HarmonyOS应用开发过程中&#xff0c;随着项目规模的增长&#xff0c;代码的组织结构显得尤为重要。 DevEco Studio创建出的默认工程仅包含一个entry类型的模块&#xff0c;如果直接使用平级目录进行模块管理&#xff0c;工程逻辑结构较混乱且模块间的一栏关系不够清晰&…

phpMyAdmin文件包含漏洞复现:原理详解+环境搭建+渗透实战(windows CVE-2018-12613)

目录 一、CVE-2018-12613漏洞 1、漏洞简介 2、漏洞原理 &#xff08;1&#xff09;漏洞触发点与正常逻辑 &#xff08;2&#xff09;过滤逻辑缺陷与绕过方式 二、渗透准备 1、访问phpmyadmin靶场 2、登录phpmyadmin 3、获取session文件位置 三、渗透准备 1、读取敏感…

Jakarta EE(基于 JPA)在 IntelliJ IDEA 中开发简单留言板应用的实验指导

Jakarta EE&#xff08;基于 JPA&#xff09;在 IntelliJ IDEA 中开发简单留言板应用的实验指导摘要&#xff1a;Jakarta EE 并不仅限于使用 H2 数据库&#xff0c;它支持任何符合 JDBC 或 JPA 标准的数据库&#xff0c;例如 MySQL、PostgreSQL、Oracle 等。H2 通常用于开发测试…

Gitea:轻量级的自托管Git服务

欢迎光临我的个人博客查看最新文章&#xff1a;rivers blog 在当今的软件开发世界中&#xff0c;代码托管平台是必不可少的工具。而对于寻求自主控制和数据隐私的团队与开发者来说&#xff0c;Gitea提供了一个完美的解决方案。 1、 Gitea简介 Gitea&#xff08;发音为ɡɪˈti…

深度学习-----简单入门卷积神经网络CNN的全流程

&#xff08;一&#xff09;卷积神经网络&#xff08;CNN&#xff09;的核心思想传统全连接网络的缺陷图像平铺展开后&#xff0c;旋转或位置变化会导致输入差异大&#xff0c;难以识别举例&#xff1a;手写数字“8”在不同位置或旋转后的识别困难&#xff08;图像在计算机中是…

Scikit-learn Python机器学习 - 特征降维 压缩数据 - 特征选择 - 单变量特征选择 SelectKBest - 选择Top K个特征

锋哥原创的Scikit-learn Python机器学习视频教程&#xff1a; 2026版 Scikit-learn Python机器学习 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 课程介绍 本课程主要讲解基于Scikit-learn的Python机器学习知识&#xff0c;包括机器学习概述&#xff0c;特征工程(数据…

Datawhale AI夏令营复盘[特殊字符]:我如何用一个Prompt,在Coze Space上“画”出一个商业级网页?

文章摘要 本文详细记录了我在Datawhale AI夏令营期间&#xff0c;如何另辟蹊径&#xff0c;使用Coze&#xff08;扣子空间&#xff09;和精心设计的Prompt&#xff0c;从零开始构建一个专业的“智能SEO Agent”产品网页的完整过程。文章将完整展示我编写的“万字”级Prompt&…

SVN和Git两种版本管理系统对比

一、SVN&#xff08;Subversion&#xff09;简介SVN是一种集中式版本控制系统。它有一个中心仓库&#xff08;repository&#xff09;&#xff0c;所有的代码变更都记录在这个中心仓库中。每个开发者从中心仓库检出&#xff08;checkout&#xff09;代码到本地工作副本&#xf…

【机器学习】综合实训(一)

项目一 鸢尾花分类该项目需要下载scikit-learn库&#xff0c;下载指令如下&#xff1a;pip install scikit-learn快速入门示例&#xff1a;鸢尾花分类# 导入必要模块 from sklearn.datasets import load_iris from sklearn.model_selection import train_test_split from sklea…