C++Morris遍历

news2024/11/15 13:30:18

一、关于Morris算法

简介

Morris算法是针对二叉树实现的一个遍历算法,它是一种空间复杂度为O(1)的遍历算法
通常情况下使用迭代或递归的方式遍历二叉树的空间开销都是O(N)级别的,较为理想的情况下可以做到O(logn)级别,而Morris算法通过更改节点指针指向的方式做到了它们都做不到的事情,可谓非常厉害。

主要思路

每到达一个节点cur,都查找它是否存在左孩子

  1. 如果没有左孩子,cur向右移动
  2. 如果有左孩子,找到左子树上最右侧的节点mostRight
    • 如果mostRight右指针指向空,将其指向cur,然后cur向左移动
    • 如果mostRight右指针指向cur,将其指向null,然后cur向右移动
  3. cur为空时遍历停止

例:

  • 如果有左孩子时
    在这里插入图片描述

  • 找到mostRight节点,如果是第一次到达改节点,则将其右孩子改为cur
    在这里插入图片描述

  • 最后cur移动到左子节点,进入下一次的循环。
    在这里插入图片描述
    注意mostRight节点只用于判断并修改节点右孩子,真正的当前节点是cur

  • 假设cur来到了上图中的mostRight位置,且mostRight没有左子树(为了方便说明)

  • cur根据指针指向移动到黄色箭头指向的节点,并进入下一次循环

  • 第二次来到该节点时:
    在这里插入图片描述

  • 此时再次找到mostRight节点,且它的右节点与当前节点相等,那么将mostRight节点的右指针制空即可。
    在这里插入图片描述

  • cur向右移动。。。

个人想法:

  • 当你到达一个节点,并将其左子树的最右侧节点链接在当前节点上,那么当你遍历到这个节点位置时,就可以通过这个指向直接回到通常递归遍历时,需要回到的那一层;某种意义上来说,这一个操作跳过了向上查找的过程。

二、主要实现

1、基础版本

	//根节点
	Node* root;
	//标准写法
	void morris()
	{
		if (root == nullptr) return;

		Node* cur = root;
		Node* mostRight = nullptr;
		while (cur)
		{
			mostRight = cur->left;
			//查找是否存在左子树
			if (mostRight)
			{
				//查找最右节点
				while (mostRight->right && mostRight->right != cur)
					mostRight = mostRight->right;

				if (mostRight->right)	// 回到之前的节点
					mostRight->right = nullptr;
				else					//已到当前子树最右节点,向左移动
				{
					mostRight->right = cur;
					cur = cur->left;
					continue;
				}
			}
			cur = cur->right;
		}
	}

2、先序和中序遍历

通过观察整个遍历过程可知,所有有左子节点的节点一定都会到达两次

  • 第一次是从上面遍历下来可以到达一次
  • 第二次是从下面返回上来可以到达一次
  • 且进入cur移动到右节点时,一定无法再次返回右节点的父亲。
    根据以上性质我们可以在合适的位置选择将数据处理代码插入,先序和中序都很好实现
	//先序遍历
	vector<T>* morrisPreorder()
	{
		if (root == nullptr) return nullptr;

		vector<T>* ret = new vector<T>;
		Node* cur = root;
		Node* mostRight = nullptr;
		while (cur)
		{
			mostRight = cur->left;
			if (mostRight)
			{
				//查找最右节点
				while (mostRight->right && mostRight->right != cur)
					mostRight = mostRight->right;

				if (mostRight->right)	// 回到之前的节点
					mostRight->right = nullptr;
				else					//已到当前子树最右节点
				{
					ret->push_back(cur->value);	//第一次处理
					mostRight->right = cur;
					cur = cur->left;
					continue;
				}
			}
			else
			{
				ret->push_back(cur->value);		//第二次处理
			}
			cur = cur->right;
		}
		return ret;
	}

	//中序遍历
	vector<T>* morrisMidorder()
	{
		if (root == nullptr) return nullptr;

		vector<T>* ret = new vector<T>;

		Node* cur = root;
		Node* mostRight = nullptr;
		while (cur)
		{
			mostRight = cur->left;
			if (mostRight)
			{
				//查找最右节点
				while (mostRight->right && mostRight->right != cur)
					mostRight = mostRight->right;

				if (mostRight->right)	// 回到之前的节点
					mostRight->right = nullptr;
				else					//已到当前子树最右节点
				{
					mostRight->right = cur;
					cur = cur->left;
					continue;
				}
			}
			ret->push_back(cur->value);	//数据处理
			cur = cur->right;
		}
		return ret;
	}

3、后序遍历

课程中提到的方式是将整个左子树以右侧指针逆序,然后输出;我觉得没必要在内存上卡那么死,直接用个栈存储一下就好了

思路

  • 在每个第二次回到的节点位置,逆序打印左子树的整条右边:
    在这里插入图片描述
    上图中,cur从头节点的遍历和输出顺序为:

7 -> 3 -> 1 -> 3(输出1) -> 2 -> 7(输出2, 3) -> 6 -> 4 -> 6(输出4) -> 5 ->
nullptr
最后再逆序输出根节点的整条右边(5, 6, 7)

	//后续遍历
	vector<T>* morrisBackorder()
	{
		if (root == nullptr) return nullptr;

		vector<T>* ret = new vector<T>;

		Node* cur = root;
		Node* mostRight = nullptr;
		while (cur)
		{
			mostRight = cur->left;
			if (mostRight)
			{
				//查找最右节点
				while (mostRight->right && mostRight->right != cur)
					mostRight = mostRight->right;

				if (mostRight->right)	// 回到之前的节点
				{
					mostRight->right = nullptr;
					func(ret, cur->left);	//插入数据
				}
				else					//已到当前子树最右节点
				{
					mostRight->right = cur;
					cur = cur->left;
					continue;
				}
			}
			cur = cur->right;
		}
		func(ret, root);
		return ret;
	}

	void func(vector<T>* vec, Node* cur)
	{
		Node* c = cur;
		stack<T> stk;
		while (c)
		{
			stk.push(c->value);
			c = c->right;
		}
		while (!stk.empty())
		{
			vec->push_back(stk.top());
			stk.pop();
		}
	}

三、其他

时间复杂度

整颗树上的每个有左孩子的节点向下做两次查找;
换一种方式想,就是每一个左子树的右侧边都进行2次搜索,
再加上本身遍历开销一次,那么总遍历次数为三次;
所以整体时间复杂度为O(N)级别


感觉我一个搞游戏前端的,学这个确实用处不大哈哈

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

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

相关文章

Windows Kerberos客户端配置并访问CDH

安装 Kerberos 客户端 配置 hosts 1、配置集群 hosts 到 Windows&#xff08;C:\Windows\System32\drivers\etc\hosts&#xff09;&#xff1b; 2、调整windows环境变量&#xff0c;将系统环境变量 PATH 中的 C:\Program Files\MIT\Kerberos\bin 放置在最前边&#xff0c;建…

目标跟踪心得篇五:MOT数据集标注、DarkLabel不能自动跟踪解决方案

跟踪方向的标注成本非常很大的 ,那么我们如何尽可能一次性弄好呢? 所选标注工具:DarkLabel DarkLabel是一个轻量的视频标注软件,尤其做MOT任务非常友好,其标注可以通过脚本转化为标准的目标检测数据集格式、ReID数据集格式和MOT数据集格式。 使用之前: darklabel.yml:保…

Python国际化学习教程

很幸运python提供了中文等其他语言的教程&#xff01; 这里以13.11.1为例 Python 是一门易于学习、功能强大的编程语言。它提供了高效的高级数据结构&#xff0c;还能简单有效地面向对象编程。Python 优雅的语法和动态类型以及解释型语言的本质&#xff0c;使它成为多数平台上写…

nacos的本地配置与启动步骤及NoDataSourceset问题解决

文章前提是本地机器已经安装好了mysql&#xff0c;配置好了mysql与Java环境变量。 首先在网络上找到一个nacos-server包。我本想上传自己的包&#xff0c;但是总是提示资源已经存在&#xff0c;那么可以自行搜索下。 解压开后是个.gz的文件&#xff0c;可以用windows自带的Win…

Golang Profiling - pprof 使用

Golang Profiling - pprof 使用 在编写大型应用程序&#xff0c;处理复杂业务与逻辑时&#xff0c;开发者经常面临系统内存泄漏问题。查找代码是否有效运行的一种有效方法是检查内存堆、CPU、磁盘的使用情况。 要在运行时检查Go应用程序的CPU和内存使用情况以及其他配置文件&…

LeetCode | 704. 二分查找

题&#xff1a;力扣 704. 二分查找 给定一个 n 个元素有序的&#xff08;升序&#xff09;整型数组 nums 和一个目标值 target &#xff0c; 写一个函数搜索 nums 中的 target&#xff0c;如果目标值存在返回下标&#xff0c;否则返回 -1。示例 1:输入: nums [-1,0,3,5,9,1…

Lua 调试(Debug)

Lua 调试(Debug) 参考至菜鸟教程。 Lua 提供了 debug 库用于提供创建我们自定义调试器的功能。Lua 本身并未有内置的调试器&#xff0c;但很多开发者共享了他们的 Lua 调试器代码。 Lua 中 debug 库包含以下函数&#xff1a; 序号方法 & 用途1.debug():进入一个用户交互模…

2022年前端React杂记

以下记录的是&#xff0c;我在学习中的一些学习笔记&#xff0c;这篇笔记是自己学习的学习大杂烩&#xff0c;主要用于记录&#xff0c;方便查找1、学习概述React 是当下最火的前端三大框架之一。之前一直没有时间来学习&#xff0c;国庆得空来快速消掉这一块的盲点。学习react…

python 字典 小白笔记

字典反映的是一种映射关系。1.定义用花括号括起来&#xff0c;每个元素包括键值对&#xff0c;键必须是可以用哈希值计算的对象&#xff0c;通常是数字或者字符串。值可以是任何类型的对象。键和值之间用“&#xff1a;”分割。zidian{jian:zhi,jian1:zhi,}2.查找字典可以用键获…

python采集某所有数据,从此不用money

前言 大家早好、午好、晚好吖 ❤ ~ 基本思路流程: <通用的> 一. 数据来源分析: 明确需求: 明确采集的网站是什么? 明确采集的数据是什么? 通过开发者工具<浏览器自带的工具(谷歌浏览器)>, 进行抓包分析 先分析一章内容, 然后再分析如何采集多章内容 打开开发…

ubuntu docker 安装rocketmq记录

安装链接参考该博客 上面的是非ubuntu安装的docker&#xff0c;下面记录ubuntu安装docker遇到的问题及解决 1 创建挂载目录 ─── rocketmq├── conf│ └── broker.conf└── data├── broker│ ├── logs│ └── store└── namesrv├── logs└── st…

ch1_3计算机硬件的技术指标

机器字长&#xff1a; CPU一次能够处理 数据的位数&#xff1b; 与CPU中的寄存器位数有关&#xff1b; 1. 运算速度 不同的指令&#xff0c;执行的频率不同&#xff1b; 部分指令&#xff0c; 执行起来很慢&#xff0c; 但是很少执行&#xff0c;出现的次数低&#xff0c;对…

gf-v1项目结构及目录说明

文章目录1. gf版本2. 项目结构3. 目录说明1. gf版本 2. 项目结构 / ├── app │ ├── common │ │ ├── adapter │ │ ├── api │ │ ├── dao │ │ ├── service │ │ ├── model │ │ ├── … │ ├── system │ │ ├── api │ │ ├── dao │…

MySQL的14个小技巧

我最近几年用MYSQL数据库挺多的&#xff0c;发现了一些非常有用的小玩意&#xff0c;今天拿出来分享到大家&#xff0c;希望对你会有所帮助。 1.group_concat 在我们平常的工作中&#xff0c;使用group by进行分组的场景&#xff0c;是非常多的。 比如想统计出用户表中&…

C++ 设计模式 外观模式 The Facade Pattern

C 设计模式 外观模式 The Facade Pattern 介绍 Facade Pattern 为一组复杂的子系统提供了一个统一的简单接口&#xff0c;它是一种结构型设计模式。 它隐藏了子系统的复杂性&#xff0c;并向客户端提供了一个简单的接口来访问子系统。通过使用 Facade 模式&#xff0c;客户端…

Tag和Untag相关知识科普

欢迎来到东用知识小课堂&#xff01;端口的出和入是针对交换机而言的&#xff0c;即数据帧进入交换机即为进入某个端口。接下来我们就以PEC系列工业级交换机为例&#xff0c;来给大家详细讲解一下1.Access&#xff1a;接入链路&#xff1a;1&#xff09;.入方向&#xff1a;收到…

c++11 标准模板(STL)(std::forward_list)(十三)

定义于头文件 <forward_list> template< class T, class Allocator std::allocator<T> > class forward_list;(1)(C11 起)namespace pmr { template <class T> using forward_list std::forward_list<T, std::pmr::polymorphic_…

湖仓一体电商项目(一):项目背景和架构介绍

文章目录 项目背景和架构介绍 一、项目背景介绍

vue iframe展示pdf请求接口

<iframe:src"pdfUrl"style"border: none; width: 100%; height: calc(100% - 10px)"frameborder"0">iframe是一个非常好用的标签&#xff0c;用于文件的展示src地址可以一个访问后端的一个地址&#xff08;https://mp.csdn.net/mp_blog/cr…

day25|51.N皇后、37.解数独

51.N皇后 按照国际象棋的规则&#xff0c;皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案…