二叉树遍历之morris遍历

Morris算法进行二叉树遍历 二叉树作为计算机中的一个重要数据结构,在很多领域都会涉及到,而提到二叉树,我们首先想到的就是其3种遍历方式--前序、中序和后序,对于这3种遍历方式,我们很容易通过使用递归或者迭代(http://blog.youkuaiyun.com/yangfeisc/article/details/44497429)的方式实现,时间复杂度为O(N)。但是这两种实现方式都需要使用堆栈进行节点信息的存储,即空间复杂度也是O(N 阅读详情

我们在遍历树时使用的 递归遍历 或者 迭代遍历,其实都是用到了堆栈来存储,增加了空间复杂度

有没有办法连这个空间都不要额外分配呢?

考虑一棵树:

1. 如果有 N 个节点,那么就有 2N 个指针(分别指向左子节点,右子节点)

2. 每一个节点,其实只有 1 个指针指向他,根节点没有节点指向,那么用到的指针个数:N-1

3. 结合1,2我们可以得知,还有 2N - (N - 1) = N + 1 个指针是空闲的

    比如节点可能有 0,1,2个子节点,对应空闲指针 2,1,0 个

如果我们能够利用这些空闲指针,就可以降低额外的空间复杂度

以前序遍历来看:1,2,4,5,10,11,3,7

以 节点 4 来讲

前缀节点:2

后缀节点:5

节点4 访问 节点5 需要回溯到其父节点 2;

节点10 访问 节点11 需要回溯到其父节点 5;

节点11 访问 节点3 需要回溯到其父节点 1;

当树的深度够大时,回溯的过程会很长,我们期望利用空闲指针,达成这样的效果

这种方式相当于利用空闲指针建立了一个索引,我们称之为 线索二叉树

前序方式的构建方式,称之为 前序线索二叉树,但是还存有一个问题:

        我们方便的知道后缀节点,但是这样不容易知道 前缀节点是多少

后续方式比较复杂,用的最多的构建方式还是 中序线索二叉树

能够方便的得到前缀及后缀节点,我们来看看怎么做到的

其实中序遍历就是对每个节点的垂直投影

我们构建的线索是这样的:

 

前缀节点:当前节点的左子树最右的那个节点 

        比如 节点 1 的前缀节点,左子树 2 的最右节点 = 11

 后缀节点:线索指针指向节点 或者原本右子树遍历到的最左节点

        比如 节点 4 的后缀节点=2,节点 10 的后缀节点=5,节点 11 的后缀节点 = 1

我们在遍历时,构建的线索二叉树会破坏整个树的形态,所以在构建过程中,还会同时删除构建的线索,这样才能还原本来的样子。

中序线索二叉树 的构建过程:

1. 当前根节点如果为 NULL,直接返回

2.找当前节点的左子树的最右节点        // 节点 1

        2.1 找到当前节点的 左子节点        // 节点 2

        2.2 递归 左子节点的 右子节点,直到为NULL        // 节点 11

        2.3 将找到的该最右的子节点的 right 指向当前节点        // 节点 11->right = 节点 1

3. 继续构建,以 2 为 根节点,得到了 节点 4->right = 2

4. 以 2 为根节点的左子树完成,开始处理其右子树,同理,得到了 节点 10->right = 节点 5

5. 最后 节点 1 的整个左子树构建完成,开始处理其右子树

前序遍历:

void preorderMorris(struct TreeNode* cur)
{
	if(cur == NULL)
		return;
		
	struct TreeNode *mostRight = NULL;
	
	while(cur != NULL)
	{
		mostRight = cur->left;
		
		if(mostRight != NULL)
		{
			while(mostRight->right != NULL && mostRight->right != cur)    // 1
			{
				mostRight = mostRight->right;
			}
			
			if(mostRight->right == NULL)        // 2
			{
				mostRight->right = cur;
				printf("val=%d\n", cur->val);
				cur = cur->left;                // 3
				continue;
			}
			else                                // 4
			{
				mostRight->right = NULL;
			}
		}
		else
		{
			printf("val=%d\n", cur->val);	
		}
		
		cur = cur->right;    // 5
	}
}

        1)  最右节点可能已经有构建,所以只有不为空并且不等于当前的根节点时才继续右子节点遍历

        2)找到最右节点,指向当前根节点

        3)继续根节点的余下左子节点构建过程

        4)已经构建好了,我们需要删除该线索,维持原本树的形态。打印节点过程会在此前完成

        5)  继续处理当前节点的右子树

中序遍历:框架不变,改变打印的地方

void inorderMorris(struct TreeNode* cur)
{
	if(cur == NULL)
		return;
		
	struct TreeNode *mostRight = NULL;
	
	while(cur != NULL)
	{
		mostRight = cur->left;
		
		if(mostRight != NULL)
		{
			while(mostRight->right != NULL && mostRight->right != cur)
			{
				mostRight = mostRight->right;
			}
			
			if(mostRight->right == NULL)
			{
				mostRight->right = cur;
				
				cur = cur->left;
				continue;
			}
			else
			{
				mostRight->right = NULL;
				printf("val=%d\n", cur->val);
			}
		}
		else
		{
			printf("val=%d\n", cur->val);	
		}
		
		cur = cur->right;
	}
}

后序遍历:框架不变,不同于前序,中序在构建中打印

一如既往的不走寻常路,我们需要观察下遍历顺序 

1. 前缀节点刚好是左子节点时,打印然后删除索引,比如:4,10

2. 但是 11 不符合这个情况,是 1 的前缀节点,但不是其左子节点(2)

    但是 11,5,2 的打印顺序正好是:以当前节点 1的左子节点开始的链 (2,5,11)的反序

3. 处理完左子树后,右子树节点 7 都不符合 1,2 条件,但是也是翻转即可

我们可以合并1,2的打印都作 翻转 (因为 1 个节点的翻转也是自己)

4. 翻转完成,记得再次翻转,否则树的这个链被反向了

struct TreeNode* reverse(struct TreeNode* head)
{
	struct TreeNode *prev = NULL;
	struct TreeNode *cur, *next;
	
	cur = head;
	while(cur != NULL)
	{
			next = cur->right;
			cur->right = prev;
			prev = cur;
			cur = next;
	}
	
	return prev;
}

void printNode(struct TreeNode* head)
{
	struct TreeNode* tail = reverse(head);
	
	while(tail)
	{
		printf("val=%d\n", tail->val);
		tail = tail->right;
	}
	
	reverse(tail);
}

打印的时间就在删除索引的时刻

void postorderMorris(struct TreeNode* cur)
{
	if(cur == NULL)
		return;
		
	struct TreeNode *root = cur; // last chain	
	struct TreeNode *mostRight = NULL;
	
	while(cur != NULL)
	{
		mostRight = cur->left;
		
		if(mostRight != NULL)
		{
			while(mostRight->right != NULL && mostRight->right != cur)
			{
				mostRight = mostRight->right;
			}
			
			if(mostRight->right == NULL)
			{
				mostRight->right = cur;
				
				cur = cur->left;
				continue;
			}
			else
			{
				mostRight->right = NULL;
				printNode(cur->left);
			}
		}
		
		cur = cur->right;
	}
	
	printNode(root);
}

-------preorder Morris------
val=1
val=2
val=4
val=5
val=10
val=11
val=3
val=7

-------inorder Morris------
val=4
val=2
val=10
val=5
val=11
val=1
val=3
val=7

-------postorder Morris------
val=4
val=10
val=11
val=5
val=2
val=7
val=3
val=1

更多数据结构详解

Java二叉树Morris遍历 Morris算法详解,以及Morris先序、中序、后序详解,超清晰原理。 阅读详情

相关推荐

二叉树的莫里斯(Morris)遍历

什么是Morris遍历 其实就是把空间复杂度优化到O(1)的二叉树遍历算法。 对于一般的遍历算法,我们都是利用栈来存储之后需要再次访问的节点。最差情况下,我们需要存储整个二叉树节点。所以空间复杂度为O(n)。而Morris遍历则是将空间复杂度降到了O(1)级别。Morris遍历用到了“线索二叉树”的概念,其实就是利用了叶子节点的左右空指针来存储某种遍历前驱节点或者后继节点。因此没有使用额外的空...

danmo_wuhen的博客 7188

c语言 树遍历算法,Morris 二叉树遍历算法(C语言描述)

对于二叉树的遍历,在数据结构中介绍有三种遍历方式:前序遍历,中序遍历和后序遍历. 这三种遍历方式通常采用递归或者迭代方式实现.递归实现或者迭代实现都是时间复杂度为O(n)的算法,并且在遍历过程中需要用到堆栈保存信息,它们的空间复杂度也都是O(n).下面要介绍的Morris二叉树遍历算法是时间复杂度为O(n),空间复杂度为O(1)的遍历算法,它也有前序遍历,中序遍历和后续遍历三种遍历顺序Morris...

weixin_39649405的博客 278

线索二叉树与Morris遍历

对于一棵普通的二叉树,它的节点结构需要由两个指针域和一个数据域构成。而一棵树中必定存在一些指针域没有被使用到,这就造成了空间的浪费。另一方面,我们经常用到二叉树的前、中、后序遍历,如果想求某种遍历中某个节点的前驱和后继节点,那就需要重新进行遍历。这无疑会造成时间的浪费。所以为什么不把空闲的指针域利用起来,让其指向节点的前驱或后继呢?其实这就是线索二叉树的核心思想。我们以中序遍历为例进行讲解。

夜槿笙歌的博客 1035

二叉树之Morris遍历

二叉树之Morris遍历1. Morris遍历序2.Morris中序遍历3.Morris后序遍历4. Morris遍历判断搜索二叉树5. 回归遍历和Morris遍历的使用时机 若可以修改原始树就可以使用morris遍历,因为morris遍历时利用树叶节点的空指针实现的; 有左树的节点可以到达两次,根据左子树最右节点的指向确定是第几次回到节点, 1. Morris遍历序 #include<iostream> #include<vector> using namespace std;

qhu1600417010的博客 529

Modelsim安装教程以及管理员身份运行patch_dll.bat时找不到文件mgls.dll的解决

Modesim安装教程 Modesim版本有很多,但是安装方式大都相同,我们以modesim.10.5为例,电脑使用的是win10 64位,讲一下安装过程中的步骤以及出现问题的解决方法 一 安装 1.首先解压安装包,在解压的安装包内选择应用程序,右键选择管理员运行。 2.稍等一会,有的电脑会弹出是否允许modesim进行更改,选择 是 即可,然后出现下面的界面,点击下一步。 3.弹出此界面选择安装路径,选择安装路径,我是在D盘新建的文件夹,直接把文件夹中的路径复制在框内。文件夹名称切记不要带中文 4.

weixin_48564372的博客 1万+

微信开放标签wx-open-launch-app样式失效?手把手教你正确设置点击区域(附完整代码)

本文详细解析了微信开放标签`wx-open-launch-app`样式失效和点击无响应的常见问题,提供了完整的解决方案和代码示例。通过分析标签内部实现机制,指导开发者正确设置点击区域和样式,确保跨平台兼容性和用户体验优化。

joy55的博客 320

二叉树神级遍历:Morris遍历

二叉树Morris先序中序后序遍历 空间复杂度O(1) 时间复杂度O(n)

Aslixzhou 299

基于bootstrap+Java的图书管理系统(任务书)

10] 倪甜弟,周晓波,王相喜.基于JSP图书馆自习室预约系统的设计与实现[J].现代计算机, 2024, 30(9):117-120.DOI:10.3969/j.issn.1007-1423.2024.09.023.[2] 陈明雪,赵勇,周小丽,等.基于Node.js和Bootstrap的图书管理系统设计与研究[J].现代信息科技, 2021, 5(24):4.[9] 杨婷婷.基于VOSviewer的图书馆管理系统研究的可视化分析[J].现代信息科技, 2023, 7(3):123-126.

毕业作品网站 647

算法:二叉树的Morris遍历

给定一颗二叉树的头节点 head,完成二叉树的先序、中序和后序遍历。如果二叉树的节点数为 N,要求时间复杂度为 O(N),额外空间复杂度为 O(1)。不管是递归版本,还是非递归版本,实质上它都用到了栈,无法做到额外空间复杂度为O(1)。比如当我们二叉树的高度有100层,那么递归时,系统就会一直压栈,最坏情况下,一直要压入100次遍历的递归函数,因为此处的空间复杂度是跟这颗二叉树的高度相关的。举个例子,如下二叉树:这就是为什么遍历二叉树需要压栈的原因。那么有没有方法,能够不压栈,就能从4节点直接返回到2节点呢

OceanStar的博客 594

调试备忘录-SWD协议解析

目录--点击可快速直达 文章目录写在前面1  SWD协议简介2  SWD物理层协议解析2.1  SWD通信时序分析2.2  SWD 寄存器简介2.2.1  DP寄存器2.2.2  AP寄存器2.3  SWD通信流程2.3.1  SWD复位2.3.2  SWD读IDCODE2.3.3  SWD清除错误标志位,并且使能AP调试2.3.4  SWD读取AP IDR(也就是AP寄存器的ID CODE)2.3.5  SWD读写MCU任意寄存器 写在前面 最近由于公司需要,所以就做了个基于SWD协议的离线烧写器。由于.

洛神殇的博客 1万+

Mac用户必看:Typora最新版永久激活+弹窗屏蔽保姆级教程(附崩溃解决方案)

本文为Mac用户提供了一份详尽的Typora最新版永久激活与弹窗屏蔽教程。通过修改关键JS文件,实现软件激活状态并彻底关闭成功提示弹窗。文章深入剖析了操作原理,并重点提供了解决由此引发的多文档崩溃问题的实用临时方案,同时客观讨论了该方法的局限性与潜在风险。

wine的专栏 2370

二叉树的Morris遍历

二叉树的Morris遍历Morris遍历程序示例:Morris遍历实现二叉树先中后序遍历先序遍历中序遍历后序遍历 Morris遍历 Morris遍历就是用来遍历二叉树的一种算法,并且能够满足时间复杂度为O(N),额外空间复杂度为O(1)。算法的大致流程如下:使用一个指针cur,从二叉树的根节点出发遍历二叉树。直至cur指向空。 判断cur是否有左子树: 若没有左子树,直接让cur往右走(cur = cur->right) 若有左子树,则找到左子树的最右结点。对最右结点进行判断 若最右结点的右孩子为

qq_36276431的博客 296

【量化入门】Tushare库获取公募基金的历史净值

在本文中,我们将介绍如何使用Python的Tushare库获取公募基金的历史净值。

木头大左的博客 1144

Morris序遍历二叉树

主要是记录了三种二叉树的遍历,主要是递归、非递归、Morris遍历。分别介绍了三种方法的前序,中序和后序遍历。

qq_53967424的博客 260

微观交通流仿真软件:SUMO (Simulation of Urban MObility)_(1).SUMO入门与基础概念

SUMO(Simulation of Urban MObility)是一个开源的微观交通流仿真软件,用于模拟城市交通系统中的各种动态行为。SUMO不仅可以模拟车辆的行驶,还可以模拟行人、自行车、公交车等交通参与者的运动。通过SUMO,研究人员和工程师可以对交通系统进行详细建模和分析,从而优化交通规划、评估交通政策和设计智能交通系统。车辆路径规划:SUMO可以模拟车辆在路网中的路径选择,包括动态路径选择和静态路径选择。交通信号控制:支持多种交通信号控制策略,包括固定时间控制、自适应控制等。多模式交通:模拟不同

kkchenjj的博客 1182

Whisper 模型在嘈杂环境中的表现:降噪与增强技术

本报告系统分析了OpenAI Whisper模型在嘈杂环境中的表现机制,重点揭示其内在降噪能力的技术边界,并结合语音增强(SE)、时频分析等前沿技术提出优化方案。内容覆盖从基础概念(如噪声类型对ASR的影响)到理论框架(模型架构与数学形式化),再到实际应用(端到端部署策略)的全链路分析,同时通过案例研究与可视化工具(如梅尔频谱对比图)降低复杂概念的认知门槛。核心结论指出:Whisper的多语言预训练虽提升了泛化性,但在低信噪比(SNR<5dB)场景下需结合前端降噪或联合训练技术以突破性能瓶颈。

AI 原生应用开发的博客 489

遍历二叉树的神级方法-Morris遍历【建议收藏】

在前面,我们简单提及过二叉树的遍历方式,有递归和非递归两个版本的遍历。仔细想一想,不管是递归的,还是非递归的遍历,两种版本的遍历都是需要耗费大量的、额外的空间。比如当我们二叉树的高度有100层,那么递归时,系统就会一直压栈,一直要压入100次遍历的递归函数,因为此处的空间复杂度是跟这可二叉树的高度相关的。所以有人就在想,有没有什么方式,能够使这个空间复杂度再压缩一点呢? 前期文章:二叉树的非递归遍历 本期文章源码:GitHub 有,肯定是有的。那就是今天的主题:Morris遍历。这种思想就能使二叉树的遍历

x0919的博客 2509

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

二叉树Morris遍历

Morris中序遍历代码: Morris后序遍历

hezhenwu123的博客 368

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

Morris遍历二叉树

Morris遍历二叉树 Morris遍历可以做到额外空间复杂度O(1),时间复杂度O(n)。 需要两个指针cur和mostringt cur为当前节点 1.若cur没有左孩子 cur=cur->right 2.若cur有左孩子,找到左孩子的最右节点记为mostringt 若mostringt->right= =NULL,mostringt->right=cur , cur=cur...

qq_43477927的博客 194

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

【算法笔记】二叉树的Morris遍历

  Morris遍历: 二叉树之前的遍历方式有空间浪费的问题(递归实现也会占中栈空间)。Morris遍历时间复杂度O(N),额外空间复杂度O(1),通过利用原树中大量空闲指针的方式,达到节省空间的目的根据Morris遍历的过程,写出的代码模板1)如果cur没有左孩子,cur向右移动(cur = cur.right)2)如果cur有左孩子,找到左子树上最右的节点mostRight:2.1)如果mostRight的右指针指向空,让其指向cur,然后cur向左移动(cur = cur.left)2.2)如果mos

u012559967的专栏 1007
上一篇: 二叉树遍历之层序遍历
下一篇: 铺满骨牌的方案
mrbone9
博客等级 码龄18年 1811粉丝 171原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值