树的前中后序的Morris遍历
目录
一.Morris遍历
1.什么是Morris遍历
2.基本思想
3.Morris遍历的优点和缺点
4.知识回顾----二叉树的线索化
二.中序Morris遍历
1.中序Morris遍历的分析
2.中序Morris遍历的思路
3.具体的代码实现
三.前序Morris遍历
1.前序Morris遍历的思路
2.具体的代码实现
四.后序Morris遍历
1.后序Morris遍历的思路
2.具体的代码实现
一.Morris遍历
1.什么是Morris遍历
Morris遍历是一种用于二叉树遍历的算法,它可以在不使用栈或队列的情况下实现中序遍历。该算法的时间复杂度为O(n),空间复杂度为O(1)。
2.基本思想
Morris遍历的基本思想是,利用叶子节点的空指针来存储临时信息,以达到节省空间的目的。具体来说,对于当前遍历到的节点,如果它有左子节点,就找到左子树中最右边的节点,将其右子节点指向当前节点,然后将当前节点更新为其左子节点。如果它没有左子节点,就输出当前节点的值,并将当前节点更新为其右子节点。重复以上步骤,直到遍历完整棵树。
3.Morris遍历的优点和缺点
Morris遍历的优点是空间复杂度低O(1),但它的缺点是会改变原来的二叉树结构,因此需要在遍历完后还原二叉树。此外,该算法可能会比递归或使用栈的算法稍微慢一些。
4.知识回顾----二叉树的线索化
二叉树的线索化,主要是利用了叶子结点中的空指针域,存放了在某种遍历顺序下的前驱或者后续结点,从而达到了线索二叉树的目的
例如,下图中序遍历结果展示如下,根据中序遍历对空指针域进行线索化
线索化的二叉树为下, 画在左边表示left结点指向,画在右边表示right指向,例如7的前驱结点为5,那么7的left指向5,7的后继节点为1,那么7的right指向1
由此,我们可能总结出这样的结论:中序遍历二叉树的指向当前结点的结点为当前结点的左子树的最右端结点,例如指向1的结点为1的左节点2的最右端结点为7,指向2结点的为2的左节点4的最右端结点4.这一点在之后的morris遍历中很重要
具体的代码实现如下:
public class InorderThreadedBinaryTree {private ThreadTreeNode pre = null;public void threadedNodes(ThreadTreeNode node) {//如果node==null,不能线索化if (node == null) {return;}//1、先线索化左子树threadedNodes(node.left);//2、线索化当前结点//处理当前结点的前驱结点//以8为例来理解//8结点的.left = null,8结点的.leftType = 1if (node.left == null) {//让当前结点的左指针指向前驱结点node.left = pre;//修改当前结点的左指针的类型,指向前驱结点node.leftType = 1;}//处理后继结点if (pre != null && pre.right == null) {//让当前结点的右指针指向当前结点pre.right = node;//修改当前结点的右指针的类型=pre.rightType = 1;}//每处理一个结点后,让当前结点是下一个结点的前驱结点pre = node;//3、线索化右子树threadedNodes(node.right);}}class ThreadTreeNode {int val;ThreadTreeNode left;//0为非线索化,1为线索化int leftType;ThreadTreeNode right;//0为非线索化,1为线索化int rightType;public ThreadTreeNode(int val) {this.val = val;}
}
但是在实现Morris遍历的时候,并不需要把结点的左节点线索化,只需要把结点的右节点进行线索化即可,具体的原因在下面进行分析.
二.中序Morris遍历
1.中序Morris遍历的分析
上面我们说了Morris遍历的时候只需要线索化右节点,这里给大家进行解释.当我们在中序遍历一棵树的时候,还比如是这样一棵树,我们一步步的node.left来到了6这个结点,这个结点的left为空,所以我们打印6这个结点的值,此时我们需要返回上一个结点,如果我们是要中序递归进行遍历的话,需要返回上一个栈,而我们Morris遍历的时候无法进行递归的返回,所以这个时候我们只需要把6的right结点进行线索化,这个时候6的right指向4,我们就可以返回到4,把4这个结点进行打印,4也线索化返回了2,把2进行打印,然后进行2的right结点5,5的left结点为空,因此打印5,之后进入到5的right结点7,打印7,7的right结点也进行了线索化,进入7的right结点为1,然后打印1,进入3结点并且打印
因为最好不要改变树的结构,所以我们在打印的时候,将线索化的结点的right结点置为空.
2.中序Morris遍历的思路
Morris遍历是利用了线索二叉树的思想,在遍历的过程中不适用栈,从而达到了空间复杂度为O(1)
具体的实现如下:
1.初始化当前的结点为根结点
2.若当前的结点的左节点为空,则输出当前结点,然后遍历当前结点的右子树,即'curr=curr.right'
3.若当前结点的左节点不为空,则找到当前结点的前驱节点,即当前结点左节点的最右侧结点,记为'prev'
- 如果'prev.right''为空,则将pre.right指向curr结点,然后遍历当前结点的左子树,即'curr=curr.left'
- 如果'prev.right''不为空,说明已经遍历完了当前节点的左子树,断开 `prev.right` 的连接,即'prev.left=null',输出当前节点,然后遍历当前节点的右子树,即 `curr=curr.right`.
3.具体的代码实现
public class Morris {/*** 将当前根结点中序遍历的结果存储到list集合中* @param root 根结点* @return 中序遍历的结合*/public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();TreeNode curr = root;while (curr != null) {if (curr.left == null) { // 左子树为空,则输出当前节点,然后遍历右子树res.add(curr.val); //如果要求直接打印,直接输出System.out.println(curr.val);curr = curr.right;} else {// 找到当前节点的前驱节点TreeNode prev = curr.left;while (prev.right != null && prev.right != curr) {prev = prev.right;}if (prev.right == null) {// 将前驱节点的右子树连接到当前节点prev.right = curr;curr = curr.left;} else {// 前驱节点的右子树已经连接到当前节点,断开连接,输出当前节点,然后遍历右子树prev.right = null;res.add(curr.val);//如果要求直接打印,直接输出System.out.println(curr.val);curr = curr.right;}}}return res;}
}class TreeNode {int val;TreeNode left;TreeNode right;TreeNode(int x) {val = x;}
}
测试:
还是这样一颗二叉树,输出如下:
[6, 4, 2, 5, 7, 1, 3]
三.前序Morris遍历
1.前序Morris遍历的思路
前序和中序的遍历很想,只不过在打印(收集结点信息的时候不同),中序遍历是在当前结点的左节点为空(curr.left==null),或者当前结点已经被线索化(prev.right==curr)的时候进行打印,仔细观察前序遍历的过程,我们通过修改打印的顺序即可.前序遍历是在当前结点的左节点为空(curr.left==null),或者当前结点没有被线索化(prev.right==null)的时候进行打印
具体的思路如下:
1.初始化当前的结点为根结点
2.若当前的结点的左节点为空,则输出当前结点,然后遍历当前结点的右子树,即'curr=curr.right'
3.若当前结点的左节点不为空,则找到当前结点的前驱节点,即当前结点左节点的最右侧结点,记为'prev'
- 如果'prev.right''为空,输出当前节点,然后将pre.right指向curr结点,然后遍历当前结点的左子树,即'curr=curr.left'
- 如果'prev.right''不为空,说明已经遍历完了当前节点的左子树,断开 `prev.right` 的连接,即'prev.left=null',然后遍历当前节点的右子树,即 `curr=curr.right`.
2.具体的代码实现
public List<Integer> preorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();TreeNode curr = root;while (curr != null) {if (curr.left == null) { // 左子树为空,则输出当前节点,然后遍历右子树res.add(curr.val);//如果要求直接打印,直接输出System.out.println(curr.val);curr = curr.right;} else {// 找到当前节点的前驱节点TreeNode prev = curr.left;while (prev.right != null && prev.right != curr) {prev = prev.right;}if (prev.right == null) {res.add(curr.val);//如果要求直接打印,直接输出System.out.println(curr.val);// 将前驱节点的右子树连接到当前节点prev.right = curr;curr = curr.left;} else {// 前驱节点的右子树已经连接到当前节点,断开连接,输出当前节点,然后遍历右子树prev.right = null;curr = curr.right;}}}return res;}
测试:
public static void main(String[] args) {TreeNode root = new TreeNode(1);root.left = new TreeNode(2);root.left.right = new TreeNode(5);root.left.right.right = new TreeNode(7);root.right = new TreeNode(3);root.left.left = new TreeNode(4);root.left.left.left = new TreeNode(6);System.out.println(preorderTraversal(root));}
还是这样一颗二叉树,输出如下:
[1, 2, 4, 6, 5, 7, 3]
四.后序Morris遍历
1.后序Morris遍历的思路
后序Morris遍历实现起来有一定的难度,但是基本代码还是不变,只是在打印的地方有略微的区别,
具体的思路如下:
1.初始化当前的结点为根结点
2.若当前的结点的左节点为空,则输出当前结点,然后遍历当前结点的右子树,即'curr=curr.right'
3.若当前结点的左节点不为空,则找到当前结点的前驱节点,即当前结点左节点的最右侧结点,记为'prev'
- 如果'prev.right''为空,然后将pre.right指向curr结点,然后遍历当前结点的左子树,即'curr=curr.left'
- 如果'prev.right''不为空,此时进行逆序存储,说明已经遍历完了当前节点的左子树,断开 `prev.right` 的连接,即'prev.left=null',然后遍历当前节点的右子树,即 `curr=curr.right`.
2.具体的代码实现
public List<Integer> postorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();TreeNode dump = new TreeNode(0);//建立一个临时结点dump.left = root; //设置dump的左节点为rootTreeNode curr = dump; //当前节点为dumpwhile (curr != null) {if (curr.left == null) { // 左子树为空,则输出当前节点,然后遍历右子树curr = curr.right;} else {// 找到当前节点的前驱节点TreeNode prev = curr.left;while (prev.right != null && prev.right != curr) {prev = prev.right;}if (prev.right == null) {// 将前驱节点的右子树连接到当前节点prev.right = curr;curr = curr.left;} else {reverseAddNodes(curr.left, prev, res);// 前驱节点的右子树已经连接到当前节点,断开连接,输出当前节点,然后遍历右子树prev.right = null;curr = curr.right;}}}return res;}private void reverseAddNodes(TreeNode begin, TreeNode end, List<Integer> res) {reverseNodes(begin, end); //将begin到end的进行逆序连接TreeNode curr = end;while (true) {//将逆序连接后端begin到end添加res.add(curr.val);if (curr == begin)break;curr = curr.right;}reverseNodes(end, begin);//恢复之前的连接状态}/*** 将begin到end的进行逆序连接** @param begin* @param end*/private void reverseNodes(TreeNode begin, TreeNode end) {TreeNode prev = begin;TreeNode curr = prev.right;TreeNode post;while (prev != end) {post = curr.right;curr.right = prev;prev = curr;curr = post;}}
测试:
public static void main(String[] args) {TreeNode root = new TreeNode(1);root.left = new TreeNode(2);root.left.right = new TreeNode(5);root.left.right.right = new TreeNode(7);root.right = new TreeNode(3);root.left.left = new TreeNode(4);root.left.left.left = new TreeNode(6);System.out.println(postorderTraversal(root));}
还是这样一颗二叉树,输出如下:
[6, 4, 7, 5, 2, 3, 1]
相关文章:
树的前中后序的Morris遍历
目录 一.Morris遍历 1.什么是Morris遍历 2.基本思想 3.Morris遍历的优点和缺点 4.知识回顾----二叉树的线索化 二.中序Morris遍历 1.中序Morris遍历的分析 2.中序Morris遍历的思路 3.具体的代码实现 三.前序Morris遍历 1.前序Morris遍历的思路 2.具体的代码实现 四…...
到底什么是线程?线程与进程有哪些区别?
上一篇文章我们讲述了什么是进程,进程的基本调度 http://t.csdn.cn/ybiwThttp://t.csdn.cn/ybiwT 那么本篇文章我们将了解一下什么是线程?线程与进程有哪些区别?线程应该怎么去编程? 目录 http://t.csdn.cn/ybiwThttp://t.csdn…...
你真的知道如何系统高效地学习数据结构与算法吗?
文章目录前言:什么是数据结构?什么是算法?学习这个算法需要什么基础?学习的重点在什么地方?一些可以让你事半功倍的学习技巧1.边学边练,适度刷题2.多问、多思考、多互动3.打怪升级学习法4.知识需要沉淀&…...
Linux操作系统基础的常用命令
1,Linux简介Linux是一种自由和开放源码的操作系统,存在着许多不同的Linux版本,但它们都使用了Linux内核。Linux可安装在各种计算机硬件设备中,比如手机、平板电脑、路由器、台式计算机。1.1Linux介绍Linux出现于1991年,…...
Jasypt加密库基本使用方法
目录 1 Jasypt简介... 2 基础知识回顾... 3 Jasypt基本加密器... 4 JasyptPBE加密器... 5 Jasypt池化加密器... 6 Jasypt客户端工具... 7 JasyptSpringboot基本用法... 8 JasyptSpringboot自定义加密器... 9 JasyptSprin…...
C++并发编程之五 高级线程管理
文章目录5.1.1 线程池5.1.1 线程池 在前面我们引入了线程的通信和同步手段,那么为什么还要引入线程池呢? 线程池是一种管理多个线程的技术,它可以减少线程的创建和销毁的开销,提高并发性能。线程池中有一定数量的空闲线程&#x…...
单片机——IIC协议与24C02
1、基础知识 1.1、IIC串行总线的组成及工作原理 I2C总线只有两根双向信号线。一根是数据线SDA,另一根是时钟线SCL。 1.2、I2C总线的数据传输 I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟…...
案例05-将不必要的逻辑放到前端(发送调查问卷)
目录一:背景介绍背景二:思路&方案重大问题:解决办法优点:三:总结一:背景介绍 本篇博客书写的意义是警示大家不必把不必要的逻辑放到前端。 明确前后端分离的意义。 背景 下面的主要逻辑是࿱…...
【每日一题】——矩阵相等判定
🌏博客主页:PH_modest的博客主页 🚩当前专栏:每日一题 💌其他专栏: 🔴 每日反刍 🟢 读书笔记 🟡 C语言跬步积累 🌈座右铭:广积粮,缓称…...
Linux防火墙的关闭
查看防火墙的状态打开终端输入如下命令systemctl status firewalld如图所示:running表示防火墙目前处于打开状态输入命令进行关闭防火墙:systemctl stop firewalld如图所示正常的用户是没有权限的,需要输入管理员的密码才能够进行关闭防火墙。…...
Request和Response的概述
⭐作者介绍:大二本科网络工程专业在读,持续学习Java,输出优质文章⭐作者主页:︶ㄣ释然⭐如果觉得文章写的不错,欢迎点个关注😉有写的不好的地方也欢迎指正,一同进步😁Request和Respo…...
常见的Web安全漏洞:SYN攻击/CSRF/XSS
一、SYN攻击(属于DOS攻击) 什么情况下被动方出现SYN_RCVD状态?(flood攻击服务) 客户伪造 ip 端口, 向服务端发送SYN请求。完成2次握手,第三次服务端 等待客户端ACK确认,但由于客户不存在服务端一直未收到确认&#…...
【STC15单片机】 超声波模块的使用
目录 1 基于STC15F2K60S2的超声波测距代码 1.1 基本注意事项 1.1.1 跳线帽接法 1.1.2 晶振设置 1.2 板载超声波工作原理 1.2.1 原理总结 1.2.2 超声波代码思路 1.3 STC15单片机代码部分 1.3.1 定时器0&定时器1初始化 1.3.2 超声波ultrasonic.c ultrasonic.h文件配…...
SpringBoot 动态操作定时任务(启动、停止、修改执行周期)增强版
前段时间编写了一篇博客SpringBoot 动态操作定时任务(启动、停止、修改执行周期,该篇博客还是帮助了很多同学。 但是该篇博客中的方法有些不足的地方: 只能通过前端控制器controller手动注册任务。【具体的应该是我们提前配置好我们的任务&am…...
快排函数 -- qsort函数(Quick Sort)
文章目录🔎1.qsort函数简介💡1.1.函数原型💡1.2.参数含义🔎2.比较函数介绍🔎3.比较函数使用案例💡3.1.整型数组💡3.2.浮点型数组💡3.3.结构体类型 - 字符串🔎4.利用冒泡排…...
条形码和二维码
前言:需要的包的相关文档 1. Barcode:https://pypi.org/project/python-barcode/0.8.1/ 2. Qrcode:https://pypi.org/project/qrcode/ 3. Zbar: https://pypi.org/project/pyzbar/ 4. Opencv: https://docs.opencv.org/3.4.11/ 5. OpenC…...
大数据-学习实践-5企业级解决方案
大数据-学习实践-5企业级解决方案 (大数据系列) 文章目录大数据-学习实践-5企业级解决方案1知识点2具体内容2.1小文件问题2.1.1 SequenceFile2.1.2 MapFile2.1.3 小文件存储计算2.2数据倾斜2.3 YARN2.3.1 YARN架构2.3.2 YARN调度器2.3.2 YARN多资源队列配置和使用2.4Hadoop官方…...
破解吲哚花菁素IR-808 N3,IR-808 azide,IR-808叠氮,酯溶性染料修饰叠氮基团,相关知识
基础产品数据(Basic Product Data):CAS号:N/A中文名:IR-808叠氮英文名:IR-808 N3,IR-808 azideIR-808结构式(Structural):详细产品数据(Detailed …...
面试官:MQ的好处到底有哪些?
💗推荐阅读文章💗 🌸JavaSE系列🌸👉1️⃣《JavaSE系列教程》🌺MySQL系列🌺👉2️⃣《MySQL系列教程》🍀JavaWeb系列🍀👉3️⃣《JavaWeb系列教程》…...
事务机制:Redis能实现ACID属性吗?
ACID特性无需多言。我们知道关系数据库比如mysql可以实现事务的ACID特性,begin,commit,回滚实现。 那么redis可以实现ACID吗,结论是不能完全保证。 首先要知道redis通过MULTI关键字开启事务,中间一系列操作,加到操作队列中并不执…...
如何在 Apinto 实现 HTTP 与 gRPC 的协议转换(上)
什么是 gRPC 像 gRPC 是由 google 开发的一个高性能、通用的开源 RPC 框架,主要面向移动应用开发且基于 HTTP/2 协议标准而设计,同时支持大多数流行的编程语言。 gRPC 基于 HTTP/2 协议传输,而 HTTP/2 相比 HTTP1.x ,有以下优势:…...
3分钟看完-丄-Python自动化测试【项目实战解析】经验分享
目录:导读 引言 自动化测试 背景 测试团队 测试体系发展 测试平台 自动化测试现状 现状一: 现状二: 现状三: 现状四: 现状五: 现状六: 失败的背景 失败的经历 失败总结 引言 内…...
Web漏洞-命令执行和代码执行漏洞
命令执行原理就是指用户通过浏览器或其他辅助程序提交执行命令,由于服务器端没有针对执行函数做过滤,导致在没有指定绝对路径的情况下就执行命令。漏洞成因它所执行的命令会继承WebServer的权限,也就是说可以任意读取、修改、执行Web目录下的…...
Towards Unsupervised Text Classification Leveraging Experts and Word Embeddings
Towards Unsupervised Text Classification Leveraging Experts and Word Embeddings Abstract 该论文提出了一种无监督的方法,使用每个文档中相关单词之间的文本相似度以及每个类别的关键字字典将文档分为几类。所提出的方法通过人类专业知识和语言模型丰富了类别…...
linux进程管理
进程管理 进程是启动的可执行程序的一个指令 1、进程简介 (1)进程的组成部分 已分配内存的地址空间安全属性,包括所有权凭据和特权程序代码的一个或多个执行线程进程状态 (2)程序和进程的区别 程序是一个静态的二进制…...
【深度强化学习】(6) PPO 模型解析,附Pytorch完整代码
大家好,今天和各位分享一下深度强化学习中的近端策略优化算法(proximal policy optimization,PPO),并借助 OpenAI 的 gym 环境完成一个小案例,完整代码可以从我的 GitHub 中获得: https://gith…...
【数据结构】第二站:顺序表
目录 一、线性表 二、顺序表 1.顺序表的概念以及结构 2.顺序表的接口实现 3.顺序表完整代码 三、顺序表的经典题目 1.移除元素 2.删除有序数组中的重复项 3.合并两个有序数组 一、线性表 在了解顺序表前,我们得先了解线性表的概念 线性表(linear…...
嵌入式安防监控项目——实现真实数据的上传
目录 一、相关驱动开发 二、A9主框架 三、脚本及数据上传实验 https://www.yuque.com/uh1h8r/dqrma0/tx0fq08mw1ar1sor?singleDoc# 《常见问题》 上个笔记的相关问题 一、相关驱动开发 /* mpu6050六轴传感器 */ i2c138B0000 { /* #address-cells <1>…...
SAP 生成UUID
UUID含义是通用唯一识别码 (Universally Unique Identifier),这 是一个软件建构的标准,也是被开源软件基金会 (Open Software Foundation, OSF) 的组织应用在分布式计算环境 (Distributed Computing Environment, DCE) 领域的一部分。 UUID-Universally…...
DevOPs介绍,这一篇就足够了
一、什么是DevOps? DevOps是一种将软件开发和IT运维进行整合的文化和运动。它的目标是通过加强软件开发、测试和运维之间的协作和沟通,使整个软件开发和交付过程更加高效、快速、安全和可靠。DevOps涵盖了从计划和设计到开发、测试、交付和部署的全生命…...
soho在哪里做网站/产品网络营销策划
JavaScript,DOM操作表格 学习要点: 1.操作表格 DOM在操作生成HTML上,还是比较简明的。不过,由于浏览器总是存在兼容和陷阱,导致最终的操作就不是那么简单方便了。本章主要了解一下DOM操作表格和样式的一些知识。 一&am…...
自助建站系统源码下载/开源crm系统
联想thinkpad e580笔记本网卡驱动是款不错的lenovo系列的驱动程序,如果您的联想笔记本出现无法识别网卡驱动或网络卡顿等情况,可以来本站下载此e580网卡驱动帮助您有效的解决相关问题等,欢迎有需要的用户前来下载!联想e580网卡驱动…...
最专业的手机网站制作/舆情网站直接打开怎么弄
我在熊猫数据框中有一组计算的OHLCVA每日证券数据,如下所示:>>> type(data_dy)>>> data_dyOpen High Low Close Volume Adj CloseDate2012-12-28 140.64 141.42 139.87 140.03 148806700 134.632012-12-31 13…...
政府网站建设 政府采购/公司网站怎么申请怎么注册
AppleFramework在数据访问控制层采用了Spring Data作为这一层的解决方案,下面就对Spring Data相关知识作一个较为详细的描述。 1.Spring Data所解决的问题 Spring Data :提供了一整套数据访问层(DAO)的解决方案,致力于减少数据访问层(DAO)的开发量。它使…...
wordpress微信分享缩微图/seo网站排名厂商定制
1.前言: 在对物联网网关进行压测的时候,发现在腾讯云部署网关程序,设备接入数量只能达到4000多个长连接,之后就再也无法接入终端了。 之前在阿里云部署的时候明明可以到达2万左右,而且腾讯云的这个服务器比阿里云的硬…...
手机网站建设软件有哪些/cps广告联盟
ps:个人学习笔记,视频链接https://www.bilibili.com/video/BV1Y7411d7Ys 参考链接https://blog.csdn.net/bit452/category_10569531.html 文章目录相关知识点线性模型梯度下降1.梯度下降2.随机梯度下降反向传播1.两层神经网络示例2.反向传播计算损失函数对权重偏导P…...