Java 二叉树非递归遍历核心实现

news2025/4/5 16:53:57

在这里插入图片描述

非递归遍历的核心是用栈模拟递归的调用过程,通过手动维护栈来替代系统栈,实现前序、中序和后序遍历。以下是三种遍历的代码实现与关键逻辑分析:


一、二叉树遍历

1.1、前序遍历(根 → 左 → 右)

核心逻辑:访问根节点后,先压右子节点再压左子节点(利用栈的 LIFO 特性)。
步骤

  1. 根节点入栈。
  2. 循环弹出栈顶元素并访问。
  3. 若存在右子节点,入栈;若存在左子节点,入栈。
public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    final LinkedList<TreeNode> stack = new LinkedList<>();
    stack.push(root);
    
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        res.add(node.val);  // 访问根节点
        if (node.right != null) stack.push(node.right);  // 右子先入栈
        if (node.left != null) stack.push(node.left);     // 左子后入栈
    }
    return res;
}

1.2、中序遍历(左 → 根 → 右)

核心逻辑:先遍历到最左叶子节点,再回溯处理中间节点和右子树。
步骤

  1. 持续压左子节点入栈,直到左子为空。
  2. 弹出栈顶元素访问,并将当前指针转向右子节点。
public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    final LinkedList<TreeNode> stack = new LinkedList<>();
    TreeNode cur = root;
    
    while (cur != null || !stack.isEmpty()) {
        // 压左子树到栈底
        while (cur != null) {
            stack.push(cur);
            cur = cur.left;
        }
        cur = stack.pop();
        res.add(cur.val);     // 访问当前节点(左子处理完)
        cur = cur.right;      // 转向右子树
    }
    return res;
}

1.3、后序遍历(左 → 右 → 根)

核心逻辑:需确保左右子树均已处理后再访问根节点,常用双栈法标记法

方法1:双栈法(反向输出根右左)

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    final LinkedList<TreeNode> stack1 = new LinkedList<>();
    final LinkedList<TreeNode> stack2 = new LinkedList<>();
    stack1.push(root);
    
    while (!stack1.isEmpty()) {
        TreeNode node = stack1.pop();
        stack2.push(node);        // 辅助栈存储逆序
        if (node.left != null) stack1.push(node.left);  // 左先入栈1
        if (node.right != null) stack1.push(node.right); // 右后入栈1
    }
    // 逆序输出即为后序
    while (!stack2.isEmpty()) {
        res.add(stack2.pop().val);
    }
    return res;
}

方法2:单栈标记法(记录访问状态)

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    final LinkedList<Pair<TreeNode, Boolean>> stack = new LinkedList<>();
    stack.push(new Pair<>(root, false));
    
    while (!stack.isEmpty()) {
        Pair<TreeNode, Boolean> pair = stack.pop();
        TreeNode node = pair.getKey();
        boolean visited = pair.getValue();
        if (node == null) continue;
        if (visited) {
            res.add(node.val);
        } else {
            stack.push(new Pair<>(node, true));   // 根标记为已访问
            stack.push(new Pair<>(node.right, false));
            stack.push(new Pair<>(node.left, false));

			/* 中序
            stack.push(new Pair<>(node.right, false));
            stack.push(new Pair<>(node, true));   // 根标记为已访问
            stack.push(new Pair<>(node.left, false));
			*/
			
			/* 先序
            stack.push(new Pair<>(node.right, false));
            stack.push(new Pair<>(node.left, false));
            stack.push(new Pair<>(node, true));   // 根标记为已访问
            */
        }
    }
    return res;
}

class Pair<k,B>{
    public Pair(k key, B value) {
        this.key = key;
        this.value = value;
    }

    k key;
    B value;

    public k getKey() {
        return key;
    }

    public void setKey(k key) {
        this.key = key;
    }

    public B getValue() {
        return value;
    }

    public void setValue(B value) {
        this.value = value;
    }
}


二、关键对比与总结

遍历方式栈操作特点时间复杂度空间复杂度
前序根 → 右 → 左入栈,出栈顺序根 → 左 → 右O(n)O(n)
中序持续压左子,回溯时处理根和右子O(n)O(n)
后序双栈反转根右左为左右根,或标记访问状态O(n)O(n)

优化建议

  • 统一写法:通过标记法(如 Pair<节点, 是否已访问>)可统一三种遍历,仅调整入栈顺序。
  • 避免大栈深度:对于极不平衡的树(如链状结构),递归可能导致栈溢出,非递归更安全。

适用场景

  • 前序:快速复制树结构(先创建父节点)。
  • 中序:二叉搜索树的有序输出。
  • 后序:释放子树内存(先处理子节点再父节点)。

三、常见问题

  1. 为什么后序遍历比前序/中序复杂?
    后序需确保左右子树均处理完才能访问根节点,需额外机制(如辅助栈或标记状态)保证顺序。

  2. 非递归和递归的性能差异?
    递归代码简洁但隐含函数调用栈开销;非递归手动管理栈,空间复杂度相同,但常数因子更优。

  3. 如何处理层次遍历?
    使用队列(BFS),而非栈(DFS)。每次处理一层节点,按层加入结果列表。

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

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

相关文章

【力扣hot100题】(052)课程表

什么人一学期要上2000节课啊jpg 看了非常久都没思路&#xff0c;主要是数据结构还没复习到图论&#xff0c;根本没思路怎么储存一个图…… 唯一记得的就是两种存储方法&#xff0c;一种是二维数组法&#xff0c;记录每一条边的有无&#xff0c;一种是只记录有的边&#xff0c…

SpringBoot配置文件多环境开发

目录 一、设置临时属性的几种方法 1.启动jar包时&#xff0c;设置临时属性 ​2.idea配置临时属性 3.启动类中创建数组指定临时属性 二、多环境开发 1.包含模式 2.分组模式 三、配置文件的优先级 1.bootstrap 文件优先&#xff1a; 2.特定配置文件优先 3.文件夹位置优…

RSA和ECC在密钥长度相同的情况下哪个更安全?

​现在常见的SSL证书&#xff0c;如&#xff1a;iTrustSSL都支持RSA和ECC的加密算法&#xff0c;正常情况下RAS和ECC算法该如何选择呢&#xff1f;实际上在密钥长度相同的情况下&#xff0c;ECC&#xff08;椭圆曲线密码学&#xff09;通常比RSA&#xff08;Rivest-Shamir-Adle…

Dive into Deep Learning - 2.4. Calculus (微积分)

Dive into Deep Learning - 2.4. Calculus {微积分} 1. Derivatives and Differentiation (导数和微分)1.1. Visualization Utilities 2. Chain Rule (链式法则)3. DiscussionReferences 2.4. Calculus https://d2l.ai/chapter_preliminaries/calculus.html For a long time, …

9.进程信号

信号量 信号量是什么&#xff1f; ​ 本质是一个计数器&#xff0c;通常用来表示公共资源中&#xff0c;资源数量多少的问题。 ​ 公共资源&#xff1a;可以被多个进程同时访问的资源。 访问没有保护的公共资源会导致数据不一致问题 什么是数据不一致问题 ​ 由于公共资源…

python爬虫:小程序逆向(需要的工具前期准备)

前置知识点 1. wxapkg文件 如何查看小程序包文件 打开wechat的设置&#xff1a; .wxapkg概述 .wxapkg是小程序的包文件格式&#xff0c;且其具有独特的结构和加密方式。它不仅包含了小程序的源代码&#xff0c;还包括了图像和其他资源文件&#xff0c;这些内容在普通的文件…

PGSQL 对象创建函数生成工具

文章目录 代码结果 代码 <!DOCTYPE html> <html lang"zh"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>PGSQL 函数生成器</tit…

查询当前用户的购物车和清空购物车

业务需求&#xff1a; 在小程序用户端购物车页面能查到当前用户的所有菜品或者套餐 代码实现 controller层 GetMapping("/list")public Result<List<ShoppingCart>> list(){List<ShoppingCart> list shoppingCartService.shopShoppingCart();r…

八、重学C++—动态多态(运行期)

上一章节&#xff1a; 七、重学C—静态多态&#xff08;编译期&#xff09;-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/146999362?spm1001.2014.3001.5502 本章节代码&#xff1a; cpp/dynamicPolymorphic.cpp CuiQingCheng/cppstudy - 码云 - 开源中…

饮食助力进行性核上性麻痹患者,提升生活质量

进行性核上性麻痹是一种少见的神经系统变性疾病&#xff0c;患者会出现姿势不稳、眼球运动障碍等症状。合理的饮食对于维持患者身体机能、延缓病情发展有重要意义。 高蛋白质食物是饮食结构的重要部分。像瘦肉、去皮禽肉、鱼类、豆类及其制品&#xff0c;还有低脂奶制品等&…

bun 版本管理工具 bum 安装与使用

在使用 node 的过程中&#xff0c;我们可能会因为版本更新或者不同项目的要求而频繁切换 node 版本&#xff0c;或者是希望使用更简单的方式安装不同版本的 node&#xff0c;这个时候我们一般会用到 nvm 或者类似的工具。 在我尝试使用 bun 的时候&#xff0c;安装前第一个想到…

木马学习记录

一句话木马是什么 一句话木马就是仅需要一行代码的木马&#xff0c;很简短且简单&#xff0c;木马的函数将会执行我们发送的命令 如何发送命令&#xff06;发送的命令如何执行? 有三种方式&#xff1a;GET&#xff0c;POST&#xff0c;COOKIE&#xff0c;一句话木马中用$_G…

决策树实战:用Python实现智能分类与预测

目录 一、环境准备 二、数据加载与探索 三、数据预处理 四、决策树模型构建 五、模型可视化&#xff08;生成决策树结构图&#xff09; 六、模型预测与评估 七、超参数调优&#xff08;网格搜索&#xff09; 八、关键知识点解析 九、完整项目开发流程 十、常见问题解…

Crond任务调度

今天我们来看看任务调度,假如我们正在睡觉,突然有个半夜两点的任务要你备份一下数据库,你怎么办&#xff1f;难道从被窝中爬起来吗&#xff1f;显然不合理,此时就需要我们定时任务调度程序了. 原理图&#xff1a; crontab 进行定时任务的调度 概述. 任务调度:是指系统在某个…

HTML5+CSS3+JS小实例:带滑动指示器的导航图标

实例:带滑动指示器的导航图标 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"> <head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, ini…

MINIQMT学习课程Day7

在上一篇&#xff0c;我们安装好xtquant&#xff0c;qmt以及python后&#xff0c;这一章&#xff0c;我们学习如何使用xtquant 本章学习&#xff0c;如何获取账号的资金使用状况。 首先&#xff0c;打开qmt&#xff0c;输入账号密码&#xff0c;选择独立交易。 进入交易界面&…

git clone 提示需要登录 github

我们在进行git的时候&#xff0c;可能会弹出让你登陆github的选项&#xff0c;这里我们介绍Token登陆的方法。 正常登陆你的Github 下拉找到 Developer settings按照如下步骤进行操作 填写相关信息&#xff0c;勾选对应选项 返回就能看到token已经被生成&#xff0c;可以使…

4.2-3 fiddler抓取手机接口

安卓&#xff1a; 长按手机连接的WiFi&#xff0c;点击修改网络 把代理改成手动&#xff0c;服务器主机选择自己电脑的IP地址&#xff0c;端口号为8888&#xff08;在dos窗口输入ipconfig查询IP地址&#xff0c;为ipv4&#xff09; 打开手机浏览器&#xff0c;输入http://自己…

Nacos注册中心AP模式核心源码分析(单机模式)

文章目录 概述一、客户端启动主线流程源码分析1.1、客户端与Spring Boot整合1.2、注册实例&#xff08;服务注册&#xff09;1.3、发送心跳1.4、拉取服务端实例列表&#xff08;服务发现&#xff09; 二、服务端接收请求主线流程源码分析2.1、接收注册请求2.1.1、初始化注册表2…

【进收藏夹吃灰】机器学习学习指南

博客标题URL【机器学习】线性回归&#xff08;506字&#xff09;https://blog.csdn.net/from__2025_03_16/article/details/146303423