【Java数据结构】二叉树的前中后序遍历(递归和非递归)

news2024/12/25 9:33:57

二叉树的遍历

  • 递归做法
    • 前序遍历
    • 中序遍历
    • 后序遍历
  • 非递归
    • 前序遍历
    • 中序遍历
    • 后序遍历

二叉树遍历是二叉树的一种重要操作 必须要掌握

二叉树的遍历可以用递归和非递归两种做法来实现

递归做法

前序遍历

前序遍历的遍历方式是先根节点 在左节点 在右节点
在这里插入图片描述
述这棵树前序遍历的结果是:A B D E C F G

递归的思想就是把问题拆分成一个个小问题来解决

public void preOrder(treeNode root) {
        if (root == null) return;
        System.out.print(root.val + " ");
        preOrder(root.left);
        preOrder(root.right);
        //前序遍历 先打印根节点 在打印左节点 在打印右节点
        //将大问题拆分成小问题 递归解决
    }

treeNode是一个内部类 具体实现

public static class treeNode {
        treeNode left;
        //左节点
        treeNode right;
        //右节点
        int val;
        //值
        public treeNode(int val) {
            this.val = val;
        }
    }

中序遍历

中序遍历的顺序是先左 在根 在右

**递归实现的代码都十分相似 **

public void inOrder(treeNode root) {
        if (root == null) return;
        inOrder(root.left);
        System.out.print(root.val + " ");
        inOrder(root.right);
		//和前序遍历相比 只是递归的顺序改变了
		//和遍历的顺序一样
		//先左 在根 在右
    }

后序遍历

后序遍历的顺序是先左 在右 在根

public void postOrder(treeNode root) {
        if (root == null) return;
        inOrder(root.left);
        inOrder(root.right);
        System.out.print(root.val + " ");
		//只是顺序发生了改变
    }

非递归

前序遍历

我们不采用递归的方式 但是要模拟递归的思路

非递归实现 我们要借用栈这个数据结构
在这里插入图片描述
例如这颗树 我们用它举例

模拟递归 我们第一步肯定是先遍历左树 直到遍历到叶子节点
因为我们是前序遍历 所以每向下遍历一次就记录下来这个节点的值(可以是打印 也可以放在其他集合中)然后把这个节点放在栈中

在这里插入图片描述
此时我们的cur指针指向了D节点的左子节点位置 此时发现cur现在为空 说明我们已经到达左树的最底部此时我们要看D节点有没有右子节点 因为我们的顺序是(根左右)

但是我们的cur已经指向了D节点的左子树,而且二叉树是单向的 我们怎么能找到D节点呢?

此时栈就起到了作用 因为我们每打印一次就把这个节点压入栈中 此时栈顶元素是D节点 我们可以弹出栈顶元素D 然后把弹出来的元素赋值给cur 在使cur指向D的右树 即

cur = stack.pop();
cur = cur.right;

然后在执行上述的操作遍历左树
根据上面的思想我们可以写出大体逻辑

public void preOrderNor(TreeNode root) {
        if(root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        //创建一个栈来存放元素
        TreeNode cur = root;
        //定义指针

    
        //这个循环内就是我们刚才的思路
            while (cur != null) {
                stack.push(cur);
                System.out.print(cur.val + " ");
                cur = cur.left;
            }//这个循环就是一直遍历左数直到左子树被遍历完
            TreeNode top = stack.pop();
            cur = top.right;
            //弹出栈顶元素 cur赋值栈的右子树
        
    }

但是我们可以看到 我们还没有分析出来最外层循环的出口 即什么时候这棵二叉树就完成了遍历?

首先我们要思考一个问题 当cur等于D的右子节点的时候 cur此时需不需要入栈? 答案是肯定需要的 那么 我们入栈的代码应该怎么写呢?

因为我们入栈后 还需要继续遍历左子树直到遍历完全部的左节点 所以这里肯定是一个循环 我们需要在这个循环的外部在嵌套一层循环 那么我们循环的条件就可以写成cur!=null 因为只有!=null时 内层的循环才可以执行 也就是才可以入栈

public void preOrderNor(TreeNode root) {
        if(root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;

        while (cur != null) {
            while (cur != null) {
                stack.push(cur);
                System.out.print(cur.val + " ");
                cur = cur.left;
            }
            //cur == null
            TreeNode top = stack.pop();
            cur = top.right;
        }
    }

**这段代码还有一个问题 我们外层的循环条件是cur!=null 但是我们在cur获取到D的右子节点时 此时cur又为空了 此时循环条件不满足 循环就结束了 但是我们还没有遍历完这棵二叉树 我们还有什么依据来继续循环呢? **

此时就要用到我们的栈 此时栈中还有元素 说明二叉树还没有遍历完 所以循环条件还要加栈不为空才可以

public void preOrderNor(TreeNode root) {
        if(root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;

        while (cur != null || !stack.empty()) {
            while (cur != null) {
                stack.push(cur);
                System.out.print(cur.val + " ");
                cur = cur.left;
            }
            //cur == null
            TreeNode top = stack.pop();
            cur = top.right;
        }
    }

我们加了这个条件之后 此时进入循环 cur = top.right 此时栈顶元素是B节点 cur此时指向B节点的右子节点 此时整棵树就可以串起来 遍历结束了

此时我们的二叉树的前序非递归遍历就结束了

中序遍历

中序遍历的思路和前序遍历基本一致 只需要改变打印的位置即可
前序遍历我们因为要先打印根节点 所以在找左子树的最后一个节点的位置时 每次循环都打印 这样就可以先打印出来根节点

中序遍历需要先打印左节点 在打印根节点 所以需要们在最后到了底部的时候再去打印节点即可 其他代码不需要改变

public void preOrderNor(TreeNode root) {
        if(root == null) return;
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;

        while (cur != null || !stack.empty()) {
            while (cur != null) {
                stack.push(cur);
                cur = cur.left;
            }
            //cur == null
            TreeNode top = stack.pop();
            System.out.print(cur.val + " ");
            cur = top.right;
        }
    }

后序遍历

后续遍历的情况比上面两种情况就更复杂
在上面的代码的主体框架下 要对更多情况进行考虑

首先不能再遍历左子树的时候就进行打印操作 不管是到树的底部打印还是每遍历一次就打印一次 因为后序遍历是先左 后右 在根节点

我们再走到左子树的最底部的左节点时 也就是下图的情况
在这里插入图片描述
cur指向D节点的左子节点 此时我们还要判断他有没有右子节点 因为右子节点的打印优先级高于根节点 如果右节点也为空 此时在打印D

且我们还需要注意的一点是 前序和中序遍历时都只需要判断左树是否为空 所以压入栈的元素只需要出一次栈即可完成目的
但是后序遍历我们不仅要判断左树是否为空 还需要判断右树是否为空 如果在判断左树时就把栈顶元素弹出 在判断右树是否为空是 就没有元素判断 所以在判断左树是否为空时 只需要peek元素 在判断完右树的元素时 在弹出元素

代码大致如下

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ret = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        while(cur != null || !stack.empty()){
            while(cur != null){
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.peek();
            if(top.right == null){
                ret.add(top.val);
                stack.pop();
            }else{
                cur = top.right;
            }
        }
        return ret;
    }
}
//这里没有打印元素 而是将元素放进一个集合中 和打印思路相同

此时代码还有一点点小问题

根据上面的思路写出代码 我们根据代码遍历一次
当cur = D 时 此时cur不为空 cur = D的左节点 此时cur为空 peek出栈顶元素D 检查D的右树是否为空 发现是空 此时将D的值放入一个集合 把D弹出栈

遍历到这步还没有问题 当我们在进入循环cur还是= D 又进行了刚才 的操作 我们发现在这里出现了死循环 只能在D这个节点处遍历

根本问题就是 我们已经打印过D了 所以D不需要进入判断 所以判断条件不仅是top.
right == null 在这个节点打印过的时候 也应该进入else

代码如下

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ret = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode cur = root;
        TreeNode prev = null;
        while(cur != null || !stack.empty()){
            while(cur != null){
                stack.push(cur);
                cur = cur.left;
            }
            TreeNode top = stack.peek();
            if(top.right == null || top.right == prev){
                prev = top;
                ret.add(top.val);
                stack.pop();
            }else{
                cur = top.right;
            }
        }
        return ret;
    }
}

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

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

相关文章

一种视频算法插件流水线执行架构

目的 将视频接入进来以后&#xff0c;使用算法对图像进行处理并且输出 1 各种接入 2 解码 3 解码后图像算法 如矫正算法 4 共享输出 方式 使用动态库的方式进行扫描底层&#xff0c;每个动态库为一个插件&#xff0c;每个插件包含特定的函数&#xff0c;通过扫描的方式加载所…

Juniper 命令集合,分好类了,网工收好了哦!

Juniper是一家全球领先的网络设备制造商&#xff0c;其设备广泛应用于企业、运营商和数据中心等领域。下面是Juniper常用命令集合&#xff0c;以供参考。 基本命令 show interfaces&#xff1a;查看所有接口状态。show interface [interface-name]&#xff1a;查看指定接口的状…

Java_常用API

Java_常用API ​ API即Application Programming Interface&#xff0c;即应用程序接口。一般来说API就是软件组件之间信息交互的桥梁&#xff0c;通过它无需访问源码。API除了有应用程序接口的含义外&#xff0c;还特质API的说明文档&#xff0c;也称为帮助文档。 1.字符串的…

STM32F4_LCD液晶显示详解

目录 1. LCD简介 2. TFT_LCD简介 2.1 LCD屏显示原理 2.2 TFTLCD硬件分析 2.3 3.5寸 16位80并口驱动 2.4 NT35310驱动时序 2.5 TFTLCD驱动流程 2.6 显存指令 2.6.1 0xD3&#xff1a;读取LCD控制器的ID 2.6.2 0x36&#xff1a;控制扫描方向 2.6.3 0x2A&#xff1a;列地…

传统机器学习(七)支持向量机(2)sklearn中的svm

传统机器学习(七)支持向量机(2)sklearn中的svm 2 sklearn中的svm 2.1 LinearSVC及SVC参数详解 2.1.1 SVC参数 class sklearn.svm.SVC(*,C1.0, kernelrbf, degree3, gammascale, coef00.0, shrinkingTrue, probabilityFalse, tol0.001, cache_size200, class_weightNone, ve…

【刷题记录】stack queue的题目练习

文章目录 1. 最小栈2. 逆波兰表达式3. 栈的压入弹出序列4. 栈实现队列5. 队列实现栈 1. 最小栈 题目链接&#xff1a;155. 最小栈 - 力扣&#xff08;LeetCode&#xff09; 题干&#xff1a; 设计一个支持 push &#xff0c;pop &#xff0c;top 操作&#xff0c;并能在常数时…

使用django_celery_beat在admin后台配置计划任务

一、依赖包的安装 django中使用celery做异步任务和计划任务最头疼的点就是包之间版本兼容性问题&#xff0c;项目一启动花花报错&#xff0c;大概率都是版本问题。每次都会花很大时间在版本兼容性问题上。本例使用如下版本&#xff1a; Django3.2 celery5.2.7 django-celery-b…

记一次 Windows10 内存压缩模块 崩溃分析

一&#xff1a;背景 1. 讲故事 在给各位朋友免费分析 .NET程序 各种故障的同时&#xff0c;往往也会收到各种其他类型的dump&#xff0c;比如&#xff1a;Windows 崩溃&#xff0c;C 崩溃&#xff0c;Mono 崩溃&#xff0c;真的是啥都有&#xff0c;由于基础知识的相对缺乏&a…

CASAIM高精度自动化三维扫描系统检测塑料件,自动检测形位公差

随着塑料工业的迅速发展&#xff0c;以及塑料制品在航空、航天、电子、机械、船舶和汽车等工业部门的推广应用&#xff0c;对塑料件的质量要求也越来越高。 为了检测塑料件的尺寸偏差以及测量关键部位的3D尺寸和形位公差&#xff0c;对影响总成零件精度的产品、工装、工艺进行精…

Spring手写模拟源码篇(你值得拥有)

概念篇 下面是本文章关于Spring底层原理的章节 Bean的创建的生命周期 类-》推断构造方法-》根据构造方法创建普通对象-》依赖注入&#xff08;Autowired等进行属性注入&#xff09;-》初始化前&#xff08;PostConstruct)->初始化&#xff08;InitializingBean)-》初始化后…

【Feign扩展】OpenFeign日志打印Http请求参数和响应数据

SpringBoot使用log4j2 在Spring Boot中所有的starter 都是基于spring-boot-starter-logging的&#xff0c;默认使用Logback。使用Log4j2的话&#xff0c;你需要排除 spring-boot-starter-logging 的依赖&#xff0c;并添加 spring-boot-starter-log4j2的依赖。 配置依赖 <…

transformer 网络概述

1. RNN存在的问题 RNN对并行计算并不友好&#xff0c;下一输出依赖于上一输入&#xff0c;难以实现并行高效计算RNN相比较与self-attension模块&#xff0c;缺少对部分变量权重的预估&#xff0c;输出的数据默认拥有一致的权重 2. self-attension self-attension是干嘛的&am…

Shell编程规范与变量使用(再也回不到故事开始的第一章了)

一、Shell编程概述 1.Shell脚本的概念 将要执行的命令按顺序保存到一个文本文件&#xff0c;给该文件可执行权限&#xff0c;可结合各种shell控制语句以完成更复杂的操作。 2.Shell脚本的应用场景 重复性操作 交互性任务 批量事务处理 服务运行状态监控 定时任务执行 … 3…

【MySQL高级】——SQL执行流程

一、MySQL 中的 SQL执行流程 1. 查询缓存 Server 如果在查询缓存中发现了这条 SQL 语句&#xff0c;就会直接将结果返回给客户端&#xff1b;如果没 有&#xff0c;就进入到解析器阶段。需要说明的是&#xff0c;因为查询缓存往往效率不高&#xff0c;所以在 MySQL8.0 之后就抛…

设计模式 -- 组合模式

前言 月是一轮明镜,晶莹剔透,代表着一张白纸(啥也不懂) 央是一片海洋,海乃百川,代表着一块海绵(吸纳万物) 泽是一柄利剑,千锤百炼,代表着千百锤炼(输入输出) 月央泽,学习的一种过程,从白纸->吸收各种知识->不断输入输出变成自己的内容 希望大家一起坚持这个过程,也同…

CKA证书题库-总结

CKA真题&#xff08;考题总结&#xff09; 文章目录 CKA真题&#xff08;考题总结&#xff09;证书个人考试总结申诉结果 CKA题目参考博主重点介绍 CKA模拟题库 注意事项考试概要考试注意事项&#xff1a; CKA题目答案设置自动补全方法一方法二 第⼀题&#xff1a;权限控制RBAC…

C语言编程技巧 --- C语言中左移右移与乘除法的比较

C语言中右移与除法的比较 最近在做项目的时候&#xff0c;遇到了一个有趣的现象。那就是&#xff0c;对于除2的整数次幂的操作而言&#xff0c;为了加快计算速度&#xff0c;一般情况下&#xff0c;会用右移&#xff08;>>&#xff09;来替代除法&#xff08;/&#xff0…

SparkSql(RDD、DataFrame、DataSet详解)idea实例+jdbc读取数据库并保存至数据库或本地

DataFrame 是什么 DataFrame 是一种以 RDD 为基础的分布式数据集&#xff0c;类似于传统数据库中 的二维表格。DataFrame 与 RDD 的主要区别在于&#xff0c;前者带有 schema 元信息&#xff0c;即 DataFrame 所表示的二维表数据集的每一列都带有名称和类型。这使得 Spark SQL …

QT Data Visualization 模块概述(数据三维显示的模块)

Data Visualization 是 Qt 提供的用于数据三维显示的模块。在 Ot 5.7 以前只有商业版才有此模块&#xff0c;而从Qt5.7 开始此模块在社区版本里也可以免费使用了。Data Visualization 用于数据的三维显示&#xff0c;包括三维柱状图、三维空间散点、三维曲面等。Data Visualiza…

KeepChatGPT插件-提效神器,解决ChatGPT报错!

KeepChatGPT插件-提效神器&#xff0c;解决ChatGPT报错&#xff01; 一、错误提示 最近⼏天&#xff0c;相信不少人在使用OpenAI的ChatGPT时都发现一个问题&#xff0c;就是官⽹报错越来越频繁了。 当你需⽤ChatGPT来处理⼀些⽐较琐碎的任务时&#xff0c;⼀旦你离开⻚⾯时间…