<JavaDS> 二叉树遍历各种遍历方式的代码实现 -- 前序、中序、后序、层序遍历

目录

有以下二叉树:

一、递归

1.1 前序遍历-递归

1.2 中序遍历-递归

1.3 后序遍历-递归

二、递归--使用链表

2.1 前序遍历-递归-返回链表

2.2 中序遍历-递归-返回链表

2.3 后序遍历-递归-返回链表

三、迭代--使用栈

3.1 前序遍历-迭代-使用栈

3.2 中序遍历-迭代-使用栈

3.3 后序遍历-迭代-使用栈

四、层序遍历

4.1 层序遍历-迭代-使用队列

4.2 层序遍历-迭代-返回二维链表


有以下二叉树:


一、递归

逻辑/思路:
递归思想是将大问题分解为解法相似的小问题
已知根节点有左右子树,根节点的子节点又各有自己的左右子树,不断地对每棵子树进行左右分解,这就是从大问题到小问题。
递归有两大关键条件:递推条件和回归条件,前者作用于递,后者作用于归。
递推条件在方法中,不断将左右子节点分别作为参数,重复调用本方法,每轮调用方法都在访问更深地子节点,达成了递归中的推进条件;
回归条件当访问到的节点为null时(如上图),就不能继续往下访问了,所以节点为null时,就return,这是回归条件;

二叉树的前中后序递归遍历,思路基本相同,区别只在于调用方法和打印的顺序不同。
以下分别是二叉树的前序、中序、后序递归遍历的代码:

1.1 前序遍历-递归

    public static void PreorderTraversal1(TreeNode root) { //当前节点为null,则return;if(root == null){return;}//打印当前节点;System.out.print(root.val+" ");//找左子节点;PreorderTraversal1(root.left);//找右子节点;PreorderTraversal1(root.right);}//运行结果:
1 2 3 4 5 6 7 

1.2 中序遍历-递归

    public static void InorderTraversal1(TreeNode root) {//当前节点为null,则return;if(root == null){return;}//找左子节点;InorderTraversal1(root.left);//打印当前节点;System.out.print(root.val+" ");//找右子节点;InorderTraversal1(root.right);}//运行结果:
3 2 4 1 5 7 6 

1.3 后序遍历-递归

    public static void PostorderTraversal1(TreeNode root) {//当前节点为null,则return;if(root == null){return;}//找左子节点;PostorderTraversal1(root.left);//找右子节点;PostorderTraversal1(root.right);//打印当前节点;System.out.print(root.val+" ");}//运行结果:
3 4 2 7 6 5 1 

二、递归--使用链表

逻辑/思路:
        同样是使用递归的逻辑思想,只是由于使用了链表的数据结构,所以在递归的过程中需要将元素加入到链表中。

2.1 前序遍历-递归-返回链表

    public static List<Integer> preorderTraversal2(TreeNode root){//新建链表;List<Integer> list = new ArrayList<>();//当前节点为null,则return;if(root == null){return list;}//add当前节点;list.add(root.val);//找当前节点的左子节点,并存储在新链表中;List<Integer> listLeft = preorderTraversal2(root.left);//将代表左子树的新链表中的元素全部添加到list中;list.addAll(listLeft);//找当前节点的右子节点,并存储在新链表中;List<Integer> listRight = preorderTraversal2(root.right);//将代表右子树的新链表中的元素全部添加到list中;list.addAll(listRight);//返回代表这个子树的list;return list;}//运行结果:
1 2 3 4 5 6 7 

2.2 中序遍历-递归-返回链表

    public static List<Integer> InorderTraversal2(TreeNode root){//新建链表;List<Integer> list = new ArrayList<>();//当前节点为null,则return;if(root == null){return list;}//找当前节点的左子节点,并存储在新链表中;List<Integer> listLeft = InorderTraversal2(root.left);//将代表左子树的新链表中的元素全部添加到list中;list.addAll(listLeft);//add当前节点;list.add(root.val);//找当前节点的右子节点,并存储在新链表中;List<Integer> listRight = InorderTraversal2(root.right);//将代表右子树的新链表中的元素全部添加到list中;list.addAll(listRight);//返回代表这个子树的list;return list;}//运行结果:
3 2 4 1 5 7 6 

2.3 后序遍历-递归-返回链表

    public static List<Integer> PostorderTraversal2(TreeNode root){//新建链表;List<Integer> list = new ArrayList<>();//当前节点为null,则return;if(root == null){return list;}//找当前节点的左子节点,并存储在新链表中;List<Integer> listLeft = PostorderTraversal2(root.left);//将代表左子树的新链表中的元素全部添加到list中;list.addAll(listLeft);//找当前节点的右子节点,并存储在新链表中;List<Integer> listRight = PostorderTraversal2(root.right);//将代表右子树的新链表中的元素全部添加到list中;list.addAll(listRight);//add当前节点;list.add(root.val);//返回代表这个子树的list;return list;}//运行结果:
3 4 2 7 6 5 1 

三、迭代--使用栈

逻辑/思路:

        迭代是使用栈来帮助遍历二叉树。这种遍历方式利用了栈“后进先出”的特点,来达到对二叉树中的父节点进行回溯的目的。

        也就是说,当遍历到一个节点即将该节点压栈,当完成对左子树的访问之后,利用弹出并记录栈顶元素的方式,得到左子树的父节点,并通过这个父节点访问右子树。

        因为压栈的第一个元素必然为根节点,因此,当栈为空时,必然全部节点都遍历完成了。

3.1 前序遍历-迭代-使用栈

    public static void PreorderTraversal3(TreeNode root){//如果root为null,return;if(root == null){return;}//新建一个栈;Stack<TreeNode> stack = new Stack<>();//将根节点压栈;stack.push(root);//如果栈不为空;while (!stack.isEmpty()){//弹出栈顶元素,并记录为cur;TreeNode cur = stack.pop();//因为是前序遍历,打印当前节点,再进行后续操作;System.out.print(cur.val+" ");//如果cur的右子节点不为空,则将其压栈;if(cur.right != null){stack.push(cur.right);}//如果cur的左子节点不为空,则将其压栈;if(cur.left != null){stack.push(cur.left);}}}//运行结果:
1 2 3 4 5 6 7 
为什么压栈先压右子节点,再压左子节点?
        因为要按前序遍历打印,而栈是后进先出,所以后压左子节点,等下先弹出的也是左子节点,先弹出先打印。

3.2 中序遍历-迭代-使用栈

    public static void InorderTraversal3(TreeNode root){//如果root为null,return;if(root == null){return;}//新建一个栈;Stack<TreeNode> stack = new Stack<>();//用一个临时“指针”记录root(因为要移动指针,不然等下根节点跑哪去都不知道了)TreeNode cur = root;//如果cur不为空或者栈不为空;while (cur != null || !stack.isEmpty()){//如果节点不为空,则将节点压栈,并让指针不断向左子节点移动,直到节点为空;//当循环停下时,此时栈顶元素必然是树中最左边且未被遍历过的节点;while (cur != null){stack.push(cur);cur = cur.left;}//弹出栈顶元素,并记录为pre;TreeNode pre = stack.pop();//因为是中序遍历,打印当前节点,再进行后续操作;System.out.print(pre.val+" ");//如果pre的右子节点不为空,则将指针cur移动到右子节点上;if(pre.right != null){cur = pre.right;}}}//运行结果:
3 2 4 1 5 7 6 
为什么进入循环的判断条件是cur != null || !stack.isEmpty()?

        cur不为空的判断条件是为了让一开始栈中还没有元素时,能够顺利进入循环。

        栈不为空代表还有元素没有遍历。

3.3 后序遍历-迭代-使用栈

    public static void PostorderTraversal3(TreeNode root){//如果root为null,return;if(root == null){return;}//新建一个栈;Stack<TreeNode> stack = new Stack<>();//用一个临时“指针”记录root(因为要移动指针,不然等下根节点跑哪去都不知道了)TreeNode cur = root;//将根节点压栈;stack.push(root);//如果栈不为空;while (!stack.isEmpty()) {//查看并记录栈顶元素这个节点;TreeNode peek = stack.peek();//根据以下条件,进行后续操作;if (peek.left != null && peek.left != cur && peek.right != cur) {stack.push(peek.left);} else if (peek.right != null && peek.right != cur) {stack.push(peek.right);} else {System.out.print(stack.pop().val + " ");cur = peek;}}}//运行结果:
3 4 2 7 6 5 1 
上述代码中的 if...else if..else 为什么这样设置条件?

if (peek.left != null && peek.left != cur && peek.right != cur) { stack.push(peek.left); }

判断peek有没有左子节点,且peek的左右子节点有没有被处理过;

如果左右子节点都没有被处理过,那么将peek的左子节点压栈;
else if (peek.right != null && peek.right != cur) { stack.push(peek.right); }

再判断peek有没有右子节点,且peek的右子节点有没有被处理过;

在这里不能对左子节点判断是否操作过,因为是先遍历的左子节点,如果存在左子节点必然是操作过的。所以如果加入左子节点的判断,则必然进不了这个else if;

如果右子节点没有被处理过,那么将peek的右子节点压栈;
通过前两个条件可以看出,只要有左子节点,必然先处理左子节点,没有左子节点或者左子节点被处理完了,才开始处理右子节点;处理方法如下:
else {
                System.out.print(stack.pop().val + " ");
                cur = peek;
            }
直到所有左右子节点处理完毕,最后一个弹出栈并被处理的,必然是一开始压栈的根节点root。

四、层序遍历

逻辑/思路:

        层序遍历与前序、中序、后序遍历都不同,层序遍历使用的是队列的数据结构进行遍历。

        核心思想是利用队列“先进先出”和“队头出,队尾入”的特点,分层遍历二叉树。

        如果需要返回一个二维链表,则是将二叉树每层的节点按顺序添加到各个链表中,每个链表代表一层,最终链表将作为元素,被添加到二维链表中;

4.1 层序遍历-迭代-使用队列

    public static void SequenceTraversal1(TreeNode root){//如果root为null,return;if(root == null){return;}//创建队列,root入队列;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);//如果队列中还有元素;while (queue.size() != 0){//队首出队列并记录cur;TreeNode cur = queue.poll();//打印cur的值;System.out.print(cur.val + " ");//如果cur有左子节点,将左子节点入队列;if(cur.left != null){queue.offer(cur.left);}//如果cur有右子节点,将右子节点入队列;if(cur.right != null){queue.offer(cur.right);}}}//运行结果:
1 2 5 3 4 6 7 

4.2 层序遍历-迭代-返回二维链表

    public static List<List<Integer>> SequenceTraversal2(TreeNode root){//创建二维链表diList;List<List<Integer>> diList = new LinkedList<>();//如果root为空则return;if(root == null){return diList;}//创建队列,root入队列;Queue<TreeNode> queue = new LinkedList<>();queue.offer(root);//如果队列中还有元素;while(!queue.isEmpty()){//计算队列中的元素数量;int size = queue.size();//创建链表,用于存储每层的节点;List<Integer> list = new LinkedList<>();//根据上面的size确定需要循环多少次,即处理多少个节点;while (size>0){//队首出队列;TreeNode cur = queue.poll();//出队列一个size就--;size--;//把出队列的元素添加到list中;list.add(cur.val);//如果cur有左子节点,将左子节点入队列;if(cur.left != null){queue.offer(cur.left);}//如果cur有右子节点,将右子节点入队列;if(cur.right != null){queue.offer(cur.right);}}//把链表list添加到diList中;diList.add(list);}//返回diList;return diList;}//运行结果:
1 2 5 3 4 6 7 

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

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

相关文章

服务器bash进程占用cpu过多疑似中挖矿病毒记录

发现过程 因为我有使用conky的习惯&#xff0c;也就是在桌面上会显示cpu和内存的占用情况&#xff0c;由于服务器不止我一个人使用&#xff0c;最近发现好几次我同学的账户下的bash进程占用特别多&#xff0c;问了他之后&#xff0c;他也说他几次都是没有使用过bash相关服务&a…

【LeetCode】每日一题 2023_11_28 设计前中后队列(数组/链表/双端队列)

文章目录 刷题前唠嗑题目&#xff1a;设计前中后队列题目描述代码与解题思路偷看大佬题解 结语 刷题前唠嗑 LeetCode&#xff1f;启动&#xff01;&#xff01;&#xff01; 这道题的难度&#xff0c;才是我想象中的中等题的难度好吧&#xff0c;昨天那玩意对我来说还是太难了…

9.Spring 整合 Redis

引入依赖&#xff1a;spring-boot-starter-data-redis配置 Redis&#xff1a;配置数据库参数、编写配置类&#xff0c;构造 RedisTemplate访问 Redis&#xff1a; redisTemplate.opsForValue() redisTemplate.opsForHash() redisTemplate.opsForList() redisTemplate.opsForSe…

Vue3-toRaw 和 markRaw 函数

Vue3-toRaw 和 markRaw 函数 toRaw(转换为原始)&#xff1a;将响应式对象转换为普通对象&#xff0c;只适用于 reactive 生成的响应式对象。markRaw(标记为原始)&#xff1a;标记某个对象&#xff0c;让这个对象永远都不具备响应式。一些集成的第三方库&#xff0c;会有大量的…

UE4 UE5 使用SVN控制

关键概念&#xff1a;虚幻引擎中使用SVN&#xff0c;帮助团队成员共享资源。 1. UE4/UE5项目文件 如果不需要编译的中间缓存&#xff0c;则删除&#xff1a; DerivedDataCache、Intermediate、Saved 三个文件夹 2.更新、上传

C++单调向量(栈):好子数组的最大分数

作者推荐 利用广度优先或模拟解决米诺骨牌 题目 给你一个整数数组 nums &#xff08;下标从 0 开始&#xff09;和一个整数 k 。 一个子数组 (i, j) 的 分数 定义为 min(nums[i], nums[i1], …, nums[j]) * (j - i 1) 。一个 好 子数组的两个端点下标需要满足 i < k <…

【LangChain实战】LangChain快速入门

1、什么是大语言模型 大语言模型是一种人工智能模型&#xff0c;通常使用深度学习技术&#xff0c;比如神经网络&#xff0c;来理解和生成人类语言。这些模型的“大”在于它们的参数数量非常多&#xff0c;可以达到数十亿甚至更多&#xff0c;这使得它们能够理解和生成高度复杂…

【Vue3】

组合式API setup选项 执行时机&#xff1a;比beforeCreate早 不能使用this 数据和函数需要return才能应用 标准写法 <script>export default {setup() {const message "vue32"const logMessage () > {console.log(message)}return {message,logMess…

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号

livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号livox 介绍更换环境更换livox激光雷达型号 livox 半固体激光雷达 gazebo 仿真 | 更换环境与雷达型号 livox 介绍 览沃科技有限公司&#xff08;Livox&#xff…

【动态规划】求最长递增子序列问题

目录 问题描述递推关系建立递推关系的思路约束条件:以 s [ k ] s[k] s[k] 结尾约束条件:以 s [ k ] s[k] s[k] 开头约束条件:增加子问题参数&#xff08;前缀&#xff09;约束条件:增加子问题参数&#xff08;后缀&#xff09;约束条件:LIS长度为k且末尾元素最小 运行实例 问…

将图像的rgb数据转成DICOM医学图像格式

dcmtk官方文档&#xff1a;https://support.dcmtk.org/docs/ dcmtk最新源码下载&#xff1a;https://www.dcmtk.org/en/dcmtk/dcmtk-software-development/ dcmtk旧版本源码下载&#xff1a;https://dicom.offis.de/download/dcmtk/ 用DCMTK库实现将图像转成dcm格式 dcmtk库的…

C++二分查找、离线算法:最近的房间

作者推荐 利用广度优先或模拟解决米诺骨牌 本文涉及的基础知识点 二分查找算法合集 题目 一个酒店里有 n 个房间&#xff0c;这些房间用二维整数数组 rooms 表示&#xff0c;其中 rooms[i] [roomIdi, sizei] 表示有一个房间号为 roomIdi 的房间且它的面积为 sizei 。每一…

mitmproxy安装以及模拟接口数据返回

使用pycharm直接安装&#xff0c;pip install mitmproxy 安装成功后直接使用命令mitmdump --version查看版本 然后自己本地下载https://mitmproxy.org/downloads/#10.1.1/ 之后一步步安装即可 安装成功后这里会出现一个.mitmproxy文件 双击这个文件进入开始安装证书 我…

【闲读 1】量子论引出对认知的思考

文章目录 一、物理学的两朵乌云故事量子力学的世界 二、 波粒二象性三、量子不确定性四、感知尺度 混沌学院课程《【物理学思维】第四节 量子论》&#xff0c;观后感。 一、物理学的两朵乌云故事 19世纪末&#xff0c;著名的物理学家开尔文爵士&#xff08;温度单位命民&…

vue2通过权限控制tab标签显示和隐藏

vue2通过权限控制tab标签显示和隐藏 1、前言2、v-if实现自定义控制 1、前言 在开发过程中&#xff0c;我们可能会遇到这样一个场景&#xff1a;根据不同权限对tab栏内容进行控制&#xff0c;这时候用自定义指令v-permission就达不到我们想要的效果&#xff0c;其是将当前节点的…

图书管理系统源码,图书管理系统开发,图书借阅系统源码整体功能演示

用户登录 基础资料 操作员管理 超期罚金设置 读者分类 读者管理 图书分类 图书管理 图书借还管理 图书借取 图书还去 图书借还查询 读者借书排行 用户登录 运行View目录下Login文件夹下的Index.csthml出现登录界面&#xff0c;输入用户名密码分别是admin密码admin12…

IDEA删除的文件怎么找回更新

一、 查找本地历史记录IDEA在进行代码版本管理时&#xff0c;会自动创建本地历史记录&#xff0c;如果我们误删了文件&#xff0c;可以通过查找本地历史记录来找回文件。 1.在项目中&#xff0c;选中被删文件的父级目录&#xff0c;“File”->“Local History”->“Show…

智能优化算法应用:基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于头脑风暴算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.头脑风暴算法4.实验参数设定5.算法结果6.参考…

Linux:文件系统初步理解

文章目录 文件的初步理解C语言中对文件的接口系统调用的接口位图的理解open调用接口 文件和进程的关系进程和文件的低耦合 如何理解一切皆文件&#xff1f; 本篇总结的是关于Linux中文件的各种知识 文件的初步理解 在前面的文章中有两个观点&#xff0c;1. 文件 内容 属性&…

算法通关村-----数据流的中位数

数据流的中位数 问题描述 中位数是有序整数列表中的中间值。如果列表的大小是偶数&#xff0c;则没有中间值&#xff0c;中位数是两个中间值的平均值。 例如 arr [2,3,4] 的中位数是 3 。 例如 arr [2,3] 的中位数是 (2 3) / 2 2.5 。 实现 MedianFinder 类: MedianFin…