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

news2025/1/1 22:52:30

二叉树的前中后序遍历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;        
    }
}

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

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

相关文章

【Sentinel】初识Sentinel

目录 1.1.雪崩问题及解决方案 1.1.1.雪崩问题 1.1.2.超时处理 1.1.3.仓壁模式 1.1.4.断路器 1.1.5.限流 1.1.6.总结 1.2.服务保护技术对比 1.3.Sentinel介绍和安装 1.3.1.初识Sentinel 1.3.2.安装Sentinel 1.4.微服务整合Sentinel 1.1.雪崩问题及解决方案 1.1.1.…

Apriori关联规则算法 HNUST【数据分析技术】(2025)

1.理论知识 Apriori是一种常用的数据关联规则挖掘方法&#xff0c;它可以用来找出数据集中频繁出现的数据集合。该算法第一次实现在大数据集上的可行的关联规则提取&#xff0c;其核心思想是通过连接产生候选项及其支持度&#xff0c;然后通过剪枝生成频繁项集。 Apriori算法的…

如何让Tplink路由器自身的IP网段 与交换机和电脑的IP网段 保持一致?

问题分析&#xff1a; 正常情况下&#xff0c;我的需求是&#xff1a;电脑又能上网&#xff0c;又需要与路由器处于同一局域网下&#xff08;串流Pico4 VR眼镜&#xff09;&#xff0c;所以&#xff0c;我是这么连接 交换机、路由器、电脑 的&#xff1a; 此时&#xff0c;登录…

系统思考—冰山模型

“卓越不是因机遇而生&#xff0c;而是智慧的选择与用心的承诺。”—— 亚里士多德 卓越&#xff0c;从来不是一次性行为&#xff0c;而是一种习惯。正如我们在日常辅导中常提醒自己&#xff1a;行为的背后&#xff0c;隐藏着选择的逻辑&#xff0c;而选择的根源&#xff0c;源…

TP5 动态渲染多个Layui表格并批量打印所有表格

记录&#xff1a; TP5 动态渲染多个Layui表格每个表格设置有2行表头&#xff0c;并且第一行表头在页面完成后动态渲染显示内容每个表格下面显示统计信息可点击字段排序一次打印页面上的所有表格打印页面上多个table时,让每个table单独一页 后端代码示例&#xff1a; /*** Nod…

【笔记】linux虚拟机与windows的文件共享之Samba服务基本配置

做完之后的总结写在最前面便于复习&#xff1a; 虚拟机上要共享的资源通过samba的操作 允许window通过网络去访问其共享资源 防止以后看不懂放在最前面 &#xff08;一&#xff09;虚拟机操作部分 下载 samba smbclient samba-common 在根目录/新建一个samba专用文件夹&…

PyTorch Instance Normalization介绍

Instance Normalization(实例归一化) 是一种标准化技术,与 Batch Normalization 类似,但它对每个样本独立地对每个通道进行归一化,而不依赖于小批量数据的统计信息。这使得它非常适合小批量训练任务以及图像生成任务(如风格迁移)。 Instance Normalization 的原理 对每…

攻防世界web新手第五题supersqli

这是题目&#xff0c;题目看起来像是sql注入的题&#xff0c;先试一下最常规的&#xff0c;输入1&#xff0c;回显正常 输入1‘&#xff0c;显示错误 尝试加上注释符号#或者–或者%23&#xff08;注释掉后面语句&#xff0c;使1后面的单引号与前面的单引号成功匹配就不会报错…

机器视觉中的单线程、多线程与跨线程:原理与应用解析

在机器视觉应用中&#xff0c;程序的运行效率直接影响到系统的实时性和稳定性。随着任务复杂度的提高&#xff0c;单线程处理往往无法满足高性能需求&#xff0c;多线程技术因此被广泛应用。此外&#xff0c;跨线程操作&#xff08;如在多线程中更新界面或共享资源&#xff09;…

JAVA学习笔记第二阶段开始 Day11 五种机制---机制1:泛型机制

JAVA基础进阶版链接 https://pdai.tech/md/java/basic/java-basic-x-generic.html 五种机制 泛型机制 用处&#xff0c;提高类型安全性和代码重用 泛型在编写代码中使用【类型占位符】&#xff0c;而不是具体的类型&#xff0c;泛型是通过“类型擦除”来实现的类型安全性&…

ZLG嵌入式笔记 | 电源设计避坑(上)

产品上量后&#xff0c;通常都会有降成需求。多年来&#xff0c;接触过不少产品降成案例&#xff0c;在电源上下刀过猛&#xff0c;引发了产品偶发性问题&#xff0c;带来了很不好的负面影响。本文将对这些案例进行总结&#xff0c;提供电源设计参考&#xff0c;确保产品降成不…

全面了解 SQL Server:功能、优势与最佳实践

SQL Server 是微软公司推出的一款关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;广泛应用于企业级数据存储、数据分析、应用开发等领域。作为全球最受欢迎的数据库管理系统之一&#xff0c;SQL Server 提供了强大的功能和工具&#xff0c;支持从小型应用到大型…

WPF TextBox 输入限制 详解

总目录 前言 通常对于WPF输入框 输入的控制无非以下方式 1 直接禁止输入(包括粘贴) 不符合要求的字符 如只可输入数字的输入框&#xff0c;当你输入字母的时候是无法输入的 2 输入后&#xff0c;校验内容是否符合要求&#xff0c;然后提示错误&#xff0c;禁止提交信息 如只可…

从0入门自主空中机器人-4-【PX4与Gazebo入门】

前言: 从上一篇的文章 从0入门自主空中机器人-3-【环境与常用软件安装】 | MGodmonkeyの世界 中我们的机载电脑已经安装了系统和常用的软件&#xff0c;这一篇文章中我们入门一下无人机常用的开源飞控PX4&#xff0c;以及ROS中无人机的仿真 1. PX4的安装 1.1 PX4固件代码的下载…

Android笔记(四十一):TabLayout内的tab不滚动问题

背景 假设二级页面是上面图片的布局&#xff0c;当进来时TabLayout和ViewPager2绑定完就马上调setCustomItem&#xff0c;跳转到最后一个tab页面时&#xff0c;会发现tab不滚动&#xff0c;手动滑一下ViewPager2时才会滚动tab到正确的位置 原因分析 调用TabLayoutMediator.at…

Pandas04

Pandas01 Pandas02 Pandas03 文章目录 内容回顾1 数据的合并和变形1.1 df.append (了解)1.2 pd.concat1.3 merge 连接 类似于SQL的join1.4 join (了解) 2 变形2.1 转置2.2 透视表 3 MatPlotLib数据可视化3.1 MatPlotLib API 套路 &为什么要可视化3.2 单变量可视化3.3 双变量…

idea 禁用/关闭 sonarlint 后台自动分析(默认开启可能会引起idea卡顿)

idea 的 SonarLint 插件安装后&#xff0c;idea的使用经常出现卡顿&#xff0c;并且运行内存使用非常高&#xff0c;出现的原因之一就可能是 SonarLint 正在进行自动扫描&#xff0c;所以一般情况我们可以选择关闭 SonarLint 自动扫描功能&#xff0c;在需要对代码进行规范检查…

“鼎和财险一体化数据安全管控实践”入选信通院金融领域优秀案例

近日&#xff0c;由中国信通院举办的深度观察报告会系列论坛在京召开。在数字生态治理分论坛上&#xff0c;2024年度首期“磐安”优秀案例——六大行业应用优秀案例遴选结果发布&#xff0c;由北京原点数安科技有限公司与鼎和财产保险股份有限公司联合申报的“鼎和财险一体化数…

音视频入门基础:MPEG2-TS专题(24)——FFmpeg源码中,显示TS流每个packet的pts、dts的实现

音视频入门基础&#xff1a;MPEG2-TS专题系列文章&#xff1a; 音视频入门基础&#xff1a;MPEG2-TS专题&#xff08;1&#xff09;——MPEG2-TS官方文档下载 音视频入门基础&#xff1a;MPEG2-TS专题&#xff08;2&#xff09;——使用FFmpeg命令生成ts文件 音视频入门基础…

小白考研历程:跌跌撞撞,起起伏伏,五个月备战历程!!!

说真的&#xff0c;7月前我都没有想过我自己要考研&#xff0c;属于前期都是在大学中准备比赛&#xff0c;证书&#xff0c;直到参加蓝桥杯获得国赛三等奖&#xff0c;我问自己&#xff0c;再继续参加比赛吗&#xff1f;已经没有并肩同行的同学&#xff08;他们都准备考公考研啦…