【二叉树遍历 Java版】二叉树的前中后序遍历and层次遍历

二叉树的前中后序遍历and层次遍历

  • 深度优先遍历
    • 题目链接
    • 递归
      • 前序遍历
      • 中序遍历
      • 后序遍历
    • 迭代
      • 前序遍历
      • 后序遍历
      • 中序遍历
    • 统一迭代
      • 前序遍历
      • 后序遍历
      • 中序遍历
  • 广度优先遍历
    • 102. 二叉树的层序遍历
    • 107. 二叉树的层序遍历 II
    • 637. 二叉树的层平均值
    • 199. 二叉树的右视图

深度优先遍历

深度优先遍历中,根据左右孩子节点和父节点的访问顺序,分为了前、中、后序遍历。 这里的前中后是指父节点相对左右孩子节点的位置:前序遍历即 父-左-右,中序遍历即 左-父-右, 后序遍历即 左-右-父。
下面一个例子:
在这里插入图片描述
所谓深度遍历,即一个节点下面有子树的话,先遍历左子树(如果有)再遍历右子树(如果有),子树遍历完了,加上这个节点(根据前中后序遍历实际情况考虑这个节点是什么时候访问),这棵树遍历完了,再往上走,回到其父节点……以此类推。

题目链接

144. 二叉树的前序遍历
94. 二叉树的中序遍历
145. 二叉树的后序遍历

递归

递归的代码非常简洁,一个函数自己调用自己,加上一个递归结束条件,即可。
前中后序的递归结束条件都是当前节点node为空。 注意,这里递归函数不需要返回值。

前序遍历

遇到一个节点,就遍历它,然后遍历左子树,然后遍历右子树,这就是前序遍历的顺序。

class Solution {
    static List<Integer> ans;
    public List<Integer> preorderTraversal(TreeNode root) {
        ans = new ArrayList<>();
        getNode(root);
        return ans;
    }
    public static void getNode(TreeNode root){
        if(root == null)
            return;
        ans.add(root.val); // 遍历节点
        getNode(root.left); // 遍历左子树
        getNode(root.right); // 遍历右子树
    }
}

中序遍历

遇到一个节点,先遍历其左子树,左子树遍历完了,再遍历这个节点,然后再遍历这个树的右子树,这就是中序遍历的顺序。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        getNode(root, ans);
        return ans;
    }

    public static void getNode(TreeNode root, List<Integer> ans){
        if(root == null)
            return;
        getNode(root.left,ans); // 先遍历左子树
        ans.add(root.val); // 再遍历当前节点
        getNode(root.right,ans); // 再遍历右子树
    }
}

后序遍历

后序遍历是左-右-中,即先遍历左子树、右子树,最后再遍历当前节点。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        getNode(root, ans);
        return ans;
    }
    public static void getNode(TreeNode root, List<Integer> ans){
        if(root == null)
            return;
        getNode(root.left,ans); // 遍历左子树      
        getNode(root.right,ans); // 遍历右子树
        ans.add(root.val);  // 遍历当前节点
    }
}

这里有个很有意思的点,前序遍历是中-左-右,前序遍历的过程中,如果交换左右子树的遍历顺序,变成中-右-左,然后再reverse反转一下,变成左-右-中,即为后序遍历了。
树的遍历(整体)是子树遍历(局部)组成的,最后整体反转,局部内也是反转的,即子树也是满足左-右-中的。

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        getNode(root, ans);
        Collections.reverse(ans); // 反转
        return ans;
    }

    public static void getNode(TreeNode root, List<Integer> ans){
        if(root == null)
            return;                 
        ans.add(root.val); // 遍历当前节点
        getNode(root.right,ans); // 遍历右子树
        getNode(root.left,ans);  // 遍历左子树
    }
}

迭代

所有递归调用的方法,都能转化成非递归版本,一般使用栈来保存中间状态遍历。在树的遍历中即用栈保存,在路径上但是还没有访问的节点。

前序遍历

前序遍历是遇到一个节点就访问,然后呢? 然后再遍历子树,左子树和右子树,在迭代版本中,就要保存左节点和右节点。注意我们用的栈,栈是先入后出,后入先出,我们要先遍历左子树,那么就要后入栈。栈不空就表示还有节点没有访问。

class Solution {    
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Stack<TreeNode> tree = new Stack<>();
        if(root == null)
            return ans;
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop();
            ans.add(node.val); // 访问当前节点
            if(node.right != null)  // 右孩子先入栈,后出栈
                tree.push(node.right);
            if(node.left != null)   // 左孩子后入栈,先出栈
                tree.push(node.left);
        }
        return ans;
    }    
}

后序遍历

前面提到,前序遍历中,左右子树遍历的顺序交换,遍历结果整体反转就能得到得到后序遍历的结果。

//遍历顺序类似前序,得到 中-右-左, 反转即可得到 左-右-中
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop();
            ans.add(node.val); // 访问当前节点
            if(node.left != null) // 左孩子先入栈,后访问
                tree.push(node.left);
            if(node.right != null) // 右孩子后入栈,先访问
                tree.push(node.right);
        }
        Collections.reverse(ans); // 反转
        return ans;
    }
}

中序遍历

中序遍历第一个节点是什么? 是整棵树最左边的孩子节点,或者整棵树最左边的没有左孩子的节点。那么就意味着,在找到这个节点之前,路上遇到的节点都要保存起来。 且先保存的全是整棵树的左边部分,访问完了根节点的时候,右边节点还没入栈呢~ 因此不能单纯用栈非空为遍历结束的条件。同时用一个当前阶段node来记录是否遍历结束。

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        TreeNode node = root.left; // 看左孩子
        while(node != null ||!tree.empty()){
        	// 一直向下,直到没有左孩子,才能遍历这个节点
            if(node != null){
                tree.push(node);// 路过的节点都入栈保存
                node = node.left;
            }else{
                node = tree.pop();
                ans.add(node.val); // 访问当前节点
                node = node.right; // 再访问右子树
            }
        }
        return ans;
    }   
}

统一迭代

上面迭代版本中,前序和后序代码很相似,但是中序非常不一样,还需要用node是否为空辅助判断遍历是否结束。 如果有一个前中后序统一的代码模板,会更方便记忆。
现在不管当前节点是否要访问,都把它放入栈里面,但是依旧要保持前中后序遍历中节点的顺序。 即前序:先存右孩子、再存左孩子、再存自己;中序:先存右孩子、再存自己、再存左孩子;后序:先存自己、再存右孩子、再存左孩子。因为入栈是先入后出,所有存的顺序是和遍历顺序相反的。
然后在当前节点后面存一个null,下次碰到null就知道下一个节点应该访问了。

前序遍历

入栈顺序是 右-左-中-null

class Solution {    
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<>();
        Stack<TreeNode> tree = new Stack<>();
        if(root == null)
            return ans;
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop(); // 栈顶节点出栈
            if(node != null){ // 不为空考虑左右孩子入栈     
                if(node.right != null)  // 右孩子先入栈,后出栈
                    tree.push(node.right);
                if(node.left != null)   // 左孩子后入栈,先出栈
                    tree.push(node.left);                
                tree.push(node); // 当前节点再入栈
                tree.push(null); // null标记当前节点
            }else{ // 栈顶是null说明前面一个就是要访问的
                node = tree.pop();
                ans.add(node.val);// 访问
            }           
        }
        return ans;
    }    
}

后序遍历

入栈顺序是 中-null-右-左

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.peek(); //注意当前节点不出栈
            if(node != null){
                tree.push(null);// 添加标志null节点
                if(node.right != null)
                    tree.push(node.right);
                if(node.left != null)
                    tree.push(node.left);                
            }else{
                tree.pop();
                node = tree.pop(); // 访问当前节点
                ans.add(node.val);
            }
        }
        return ans;
    }
}

中序遍历

入栈顺序是 右-中-null-左

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> ans = new ArrayList<Integer>();
        if(root == null)
            return ans;
        Stack<TreeNode> tree = new Stack<>();
        tree.push(root);
        while(!tree.empty()){
            TreeNode node = tree.pop();
            if(node != null){
                if(node.right != null) // 右孩子先入栈
                    tree.push(node.right);
                tree.push(node); // 当前节点入栈
                tree.push(null); // 添加标识
                
                if(node.left != null) // 左孩子入栈
                    tree.push(node.left);
            }else{
                node = tree.pop();
                ans.add(node.val);// 访问节点
            }
        }
        return ans;
    }   
}

广度优先遍历

广度优先遍历即为层次遍历。深度用栈,广度用队列,先入先出。
每一层的节点数量,为当前queue中元素的数量!

102. 二叉树的层序遍历

102. 二叉树的层序遍历

class Solution {
    public List<List<Integer>> ans = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
        if(root == null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            List<Integer> levelElements = new ArrayList<Integer>();
            int levelSize = queue.size(); //当前层的节点数量
            for(int i=0;i<levelSize;i++){
                TreeNode node = queue.poll();
                levelElements.add(node.val);
                // 出队后左右孩子依次入队
                if(node.left!= null)
                    queue.offer(node.left);
                if(node.right!= null)
                    queue.offer(node.right);
            }
            // 从上往下访问 直接将层的结果加入ans中
            ans.add(levelElements);
        }
        return ans;
    }
}

107. 二叉树的层序遍历 II

107. 二叉树的层序遍历 II
这个题目只是从最后一层输出,实际遍历的时候还是从上往下,只是每次把访问的那一层放在最前面。


class Solution {
    public List<List<Integer>> ans = new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        if(root == null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            List<Integer> levelElements = new ArrayList<Integer>();
            int levelSize = queue.size();
            for(int i=0;i<levelSize;i++){
                TreeNode node = queue.poll();
                levelElements.add(node.val);
                if(node.left!= null)
                    queue.offer(node.left);
                if(node.right!= null)
                    queue.offer(node.right);
            }
            // ❗注意要求从最后一层访问,实际还是从上往下访问,但是每一层的结果都加在最前面
            ans.add(0,levelElements);
        }
        return ans;
    }
}

637. 二叉树的层平均值

637. 二叉树的层平均值

只需要在层次访问的基础上,将各个节点的val求平均。

class Solution {
    public List<Double> ans = new ArrayList<Double>();

    public List<Double> averageOfLevels(TreeNode root) {
        if (root == null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while (!queue.isEmpty()) {
            int size = queue.size();
            TreeNode node = null;
            double sum = 0.0;
            for (int i = 0; i < size; i++) {
                node = queue.poll();
                if (node.left != null)
                    queue.offer(node.left);
                if (node.right != null)
                    queue.offer(node.right);
                // ❗求节点值的和
                sum += node.val;
            }
            // 将平均值加入ans
            ans.add(sum / size);
        }
        return ans;        
    }
}

199. 二叉树的右视图

199. 二叉树的右视图
从广度优先(即层次遍历)的角度,这个题就是访问每一层时,只将这个层最后一个元素放入ans中。 因此一个二叉树的右视图,看到的就是每一层最右边的元素。

class Solution {
    public List<Integer> ans = new ArrayList<Integer>();
    public List<Integer> rightSideView(TreeNode root) {
        if(root ==  null)
            return ans;
        Deque<TreeNode> queue = new LinkedList<TreeNode>();
        queue.offer(root);
        while(!queue.isEmpty()){
            int size = queue.size();
            TreeNode node = null;
            while(size > 0){
                node = queue.poll();
                if(node.left != null)
                    queue.offer(node.left);
                if(node.right != null)
                    queue.offer(node.right);
                // ❗只访问最后一个元素
                if(size == 1)
                    ans.add(node.val);
                size--;
            } 
        }
        return ans;        
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ShaderJoy ——一种可交互的翻页效果【GLSL】

效果视频 Shader 特效——可与鼠标交互的翻页效果 效果图 完整代码 #define pi 3.14159265359 #define radius .1#iChannel0 "file://./images/Woolly_3.png" #iChannel1 "file://./images/Woolly_4.png"void mainImage( out vec4 fragColor, in vec2 fra…

贝叶斯神经网络(Bayesian Neural Network)

最近在研究贝叶斯神经网络,一些概念一直搞不清楚,这里整理一下相关内容,方便以后查阅。 贝叶斯神经网络(Bayesian Neural Network) 贝叶斯神经网络(Bayesian Neural Network)1. BNN 的核心思想2. BNN 的优化目标3. BNN 的结构与特点4. BNN 的训练过程5. BNN 的优缺点6. …

Socket学习(一):控制台聊天demo

实现效果 客户端连接服务端后&#xff0c;可在控制台输入要发送的消息&#xff0c;服务端收到消息后自动回复消息并将消息转发给所有连接上的客户端&#xff1a; 服务端收到消息并回复 客户端1发送消息并接收服务端的回复 客户端2接收服务端转发的消息 源码 SocketServer…

word中文献引用[]符号的上下标格式修改

word中文献引用[]符号的上下标格式修改 百度网址 1、查找打开使用通配符&#xff0c;输入[[][0-9]{1,2}[]]&#xff0c;即可匹配所有的字[1],[12]这些字符&#xff0c;然后鼠标点击替换为的空白处&#xff0c;再点击特殊格式–>“字体”&#xff0c;选中上标&#xff0c;最…

3.若依前端项目拉取、部署、访问

因为默认RuoYi-Vue是使用的Vue2,所以需要另外去下载vue3来部署。 拉取代码 git clone https://gitee.com/ys-gitee/RuoYi-Vue3.git 安装node才能执行npm相关的命令 执行命令npm install 如果npm install比较慢的话&#xff0c;需要添加上国内镜像 npm install --registrhttp…

在线学习平台-项目技术点-前台

报错解决方法 1、P166-尚硅谷_在线教育_Nuxt整合错误_nuxt friendly-errors-CSDN博客 2、P168 3、P170 4、P173 npm remove axios npm install axios0.18.0 1、服务端渲染技术NUXT 1.1服务端渲染SSR 服务端渲染又称SSR (Server Side Render)是在服务端完成页面的内容&…

Java-02 深入浅出 MyBatis - MyBatis 快速入门(无 Spring) POM Mapper 核心文件 增删改查

点一下关注吧&#xff01;&#xff01;&#xff01;非常感谢&#xff01;&#xff01;持续更新&#xff01;&#xff01;&#xff01; 大数据篇正在更新&#xff01;https://blog.csdn.net/w776341482/category_12713819.html 目前已经更新到了&#xff1a; MyBatis&#xff…

Mysql进阶SQL优化

SQL优化在开发场景中必不可少的技能之一&#xff0c;它能最大限度的提升SQL查询性能&#xff0c;如果随意使用也会出现不可预料的结局。 1、为什么要优化SQL 我们先说说不优化SQL造成什么现象。常见问题是响应时间长&#xff0c;用户体验感低。数据库频繁争抢锁&#xff0c;浪…

SpringBoot使用外置的Servlet容器(详细步骤)

嵌入式Servlet容器&#xff1a;应用打成可执行的jar 优点&#xff1a;简单、便携&#xff1b; 缺点&#xff1a;默认不支持JSP、优化定制比较复杂.&#xff1b; 外置的Servlet容器&#xff1a;外面安装Tomcat---应用war包的方式打包&#xff1b; 操作步骤&#xff1a; 方式一&…

基于统计分析与随机森林的环境条件对生菜生长的影响研究

1.项目背景 随着现代农业的发展&#xff0c;对植物生长过程中环境因素的影响有了越来越多的关注&#xff0c;基于2023年8月3日至2023年9月19日期间记录的70个不同生菜样本的生长数据进行分析&#xff0c;可以更好地理解温度、湿度、pH值和总溶解固体&#xff08;TDS&#xff0…

Bitmap(BMP)图像信息分析主要说明带压缩的形式

文章目录 参考资料Bitmap图片结构Bitmap图片组成实例说明 参考资料 微软官方-位图存储 Bitmap图片结构 序号名称说明1Bitmap File HeaderBitmap文件头2Bitmap Info HeaderBitmap信息头3Color Palette Data调色板数据4Bitmap Image Data图像数据 说明 Bitmap文件头的大小为…

百度热力图数据日期如何选择

目录 1、看日历2、看天气 根据研究内容定&#xff0c;一般如果研究城市活力的话&#xff0c;通常会写“非重大节假日&#xff0c;非重大活动&#xff0c;非极端天气等”。南方晴天不多&#xff0c;有小雨或者中雨都可认为没有影响&#xff0c;要不然在南方很难找到完全一周没有…

基于ArcGIS Pro的SWAT模型在流域水循环、水生态模拟中的应用及案例分析;SWAT模型安装、运行到结果读取全流程指导

目前&#xff0c;流域水资源和水生态问题逐渐成为制约社会经济和环境可持续发展的重要因素。SWAT模型是一种基于物理机制的分布式流域水文与生态模拟模型&#xff0c;能够对流域的水循环过程、污染物迁移等过程进行精细模拟和量化分析。SWAT模型目前广泛应用于流域水文过程研究…

【机器学习(九)】分类和回归任务-多层感知机(Multilayer Perceptron,MLP)算法-Sentosa_DSML社区版 (1)111

文章目录 一、算法概念111二、算法原理&#xff08;一&#xff09;感知机&#xff08;二&#xff09;多层感知机1、隐藏层2、激活函数sigma函数tanh函数ReLU函数 3、反向传播算法 三、算法优缺点&#xff08;一&#xff09;优点&#xff08;二&#xff09;缺点 四、MLP分类任务…

tryhackme-Cyber Security 101-Cryptography-Cryptography Basics(加密基础)

目的&#xff1a;了解加密和对称加密的基础知识。 任务1&#xff1a;介绍 你有没有想过如何防止第三方阅读你的消息&#xff1f;您的应用程序 或 Web 浏览器如何与远程服务器建立安全通道&#xff1f;安全是指没有人可以读取或更改交换的数据;此外&#xff0c;我们可以确信我们…

螺杆支撑座在运用中会出现哪些问题?

螺杆支撑座是一种用于支撑滚珠螺杆的零件&#xff0c;通常用于机床、数控机床、自动化生产线等高精度机械设备中。在运用中可能会出现多种问题&#xff0c;这些问题源于多个方面&#xff0c;以下是对可能出现的问题简单了解下&#xff1a; 1、安装不当&#xff1a;安装过程中没…

基于SpringBoot的在线文档管理系统的设计与实现

一、项目背景 随着社会的快速发展&#xff0c;计算机的影响是全面且深入的。员工生活水平的不断提高&#xff0c;日常生活中员工对在线文档方面的要求也在不断提高&#xff0c;在线文档管理受到广大员工的关注&#xff0c;使得在线文档管理系统的开发成为必需而且紧迫的事情。…

UE5 崩溃问题汇总!!!

Using bundled DotNet SDK version: 6.0.302 ERROR: UnrealBuildTool.dll not found in "..\..\Engine\Binaries\DotNET\UnrealBuildTool\UnrealBuildTool.dll" 在你遇到这种极奇崩溃的BUG &#xff0c;难以解决的时候。 尝试了N种方法&#xff0c;都不行的解决方法。…

QML学习(五) 做出第一个简单的应用程序

通过前面四篇对QML已经有了基本的了解&#xff0c;今天先尝试做出第一个单页面的桌面应用程序。 1.首先打开Qt,创建项目&#xff0c;选择“QtQuick Application - Empty” 空工程。 2.设置项目名称和项目代码存储路径 3.这里要注意选择你的编译器类型&#xff0c;以及输出的程…

Dockerfile基础指令

1.FROM 基于基准镜像&#xff08;建议使用官方提供的镜像作为基准镜像&#xff0c;相对安全一些&#xff09; 举例&#xff1a; 制作基准镜像&#xff08;基于centos:lastest&#xff09; FROM cenots 不依赖于任何基准镜像 FROM scratch 依赖于9.0.22版本的tomcat镜像 FROM…