算法拾遗三十三Morris遍历

news2024/11/25 18:44:56

算法拾遗三十三Morris遍历

      • 常规二叉树遍历
      • Morris遍历
      • Morris遍历判断是否是搜索二叉树
      • 给定一颗二叉树的头节点head,求以head为头的树中,最小深度是多少?

常规二叉树遍历

	public static class Node {
		public int value;
		Node left;
		Node right;

		public Node(int data) {
			this.value = data;
		}
	}

	public static void process(Node root) {
		if (root == null) {
			return;
		}
		// 1 先序遍历
		process(root.left);
		// 2 中序遍历
		process(root.right);
		// 3 后序遍历
	}

时间复杂度为O(N)
空间复杂度为O(M)树的高度
在这里插入图片描述
从一开始会依次调用f(a)->f(b)->f(d),到f(d)后的后续操作为空,返回。递归跑起来是因为有系统在内部进行压栈,看起来在代码层次没有申请空间,但是系统内部生成了这么一个系统栈来让整个递归跑起来。【栈的大小为二叉树的高度】
之前学的所有二叉树的遍历高度这个空间是省不掉的。

Morris遍历

可以实现时间复杂度为O(N),可以优化空间复杂度为O(1),利用了一棵树上大量的指针空闲空间来完成遍历。
遍历细节:
在这里插入图片描述
流程:
在这里插入图片描述
如上图例子
1、当前节点来到头节点的位置
在这里插入图片描述
1)如果cur无左树,cur直接往右移动cur= cur.right。
2)如果cur有左树,则需要找到左树上的最右节点(mostRight),如果mostRight的右指针指向是空的,那么让它指向当前节点,然后cur向左移动。
如果mostRight的右指针是指向当前节点,这个时候让它指回空,然后cur向右移动

如上图例子:
1、首先cur是有左子树的
2、则找到左子树的最右节点e
4、e的右指针指向空的,让e指向cur
在这里插入图片描述
5、cur往左移动来到b
在这里插入图片描述
cur来到b后有左树,左树的最右节点为d,然后让d的右指针指向b
在这里插入图片描述
然后cur来到d位置。
然后d位置左子树为空,则cur向右移动,cur回到b
在这里插入图片描述
然后中了2)条件中的第二种情况【如果mostRight的右指针是指向当前节点,这个时候让它指回空,然后cur向右移动】,cur来到e
在这里插入图片描述

由于e没有左树,所以直接往右移动回到了a
在这里插入图片描述
回到a之后,左树上的最右节点e,e的右指针指向了cur本身。把e指向a改为指向空,然后cur向右移动到c。直到最后移动完【发现每个节点一定都能遍历到】
遍历的顺序为:a,b,d,b,e,a,c,f,c,g
特点,任何一个节点有左树,那么这个节点一定到两次,如果某一个节点没有左树只会到一次

流程实质:
是在用左树上的最右节点的右指针状态来标记我到底是第一次到的这个节点还是第二次。

先序:
Morris加工先序:对于能回到自己两次的节点只打印第一次,对于只到达自己一次的节点只处理一次
先序:a,b,d,e,c,f,g
中序:
对于能回到自己两次的节点,则在第二次打印,对于只能回到自己一次的节点,则第一次打印
中序:d,b,e,a,f,c,g

	public static class Node {
		public int value;
		Node left;
		Node right;

		public Node(int data) {
			this.value = data;
		}
	}
public static void morris(Node head) {
		if (head == null) {
			return;
		}
		//cur指针初始在头节点
		Node cur = head;
		Node mostRight = null;
		while (cur != null) {
			//mostRight先来到左孩子
			mostRight = cur.left;
			if (mostRight != null) {
				//如果左孩子不是空
				//左树上的最右指针指向空和左树上的最右指针指向自己两种情况需停止
				while (mostRight.right != null && mostRight.right != cur) {
					mostRight = mostRight.right;
				}
				//如果mostRight的右指针指向空,让其指向cur,
				// 然后cur向左移动(cur = cur.left)
				if (mostRight.right == null) {
					mostRight.right = cur;
					cur = cur.left;
					continue;
				} else {
					//如果mostRight的右指针指向cur,让其指向null,
					//然后cur向右移动(cur = cur.right)
					mostRight.right = null;
				}
			}
			//如果cur没有左孩子,cur向右移动(cur = cur.right)
			cur = cur.right;
		}
	}

Morris遍历找左树的最右节点的时间复杂度之所以为O(N)是因为在遍历的过程中不会每个节点的左树最右节点不会出现重复
在这里插入图片描述

先序遍历

public static void morrisPre(Node head) {
		if (head == null) {
			return;
		}
		Node cur = head;
		Node 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) {
					//有左树第一次来的时候打印
					System.out.print(cur.value + " ");
					mostRight.right = cur;
					cur = cur.left;
					continue;
				} else {
					mostRight.right = null;
				}
			} else {
				//如果没有左树,直接打印
				System.out.print(cur.value + " ");
			}
			cur = cur.right;
		}
		System.out.println();
	}

中序遍历

public static void morrisIn(Node head) {
		if (head == null) {
			return;
		}
		Node cur = head;
		Node 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了
					//只有第二次来到自己要往右边走的过程才打印
					continue;
				} else {
					mostRight.right = null;
				}
			}
			System.out.print(cur.value + " ");
			cur = cur.right;
		}
		System.out.println();
	}

后续遍历:
还是刚才的二叉树给定morris序列
在这里插入图片描述
我们把处理时机只放在能回到自己两次的节点,然后在第二次回到自己的时候处理,如上图只对a,b,c做处理。
在这里插入图片描述
当来到要处理的节点的时候需要逆序的打印其左树的右边界。
d,e,b,f,g,c

	public static void morrisPos(Node head) {
		if (head == null) {
			return;
		}
		Node cur = head;
		Node 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;
					printEdge(cur.left);
				}
			}
			cur = cur.right;
		}
		printEdge(head);
		System.out.println();
	}

	public static void printEdge(Node head) {
		Node tail = reverseEdge(head);
		Node cur = tail;
		while (cur != null) {
			System.out.print(cur.value + " ");
			cur = cur.right;
		}
		reverseEdge(tail);
	}

	public static Node reverseEdge(Node from) {
		Node pre = null;
		Node next = null;
		while (from != null) {
			next = from.right;
			from.right = pre;
			pre = from;
			from = next;
		}
		return pre;
	}

Morris遍历判断是否是搜索二叉树

public static boolean isBST(Node head) {
		if (head == null) {
			return true;
		}
		Node cur = head;
		Node mostRight = null;
		Integer pre = null;
		boolean ans = true;
		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;
				}
			}
			//上一个节点的值不为空,且上一个节点的值大于当前节点的值,则不是搜索二叉树
			if (pre != null && pre >= cur.value) {
			//不能直接return false,因为此处直接返回morris遍历可能会将整棵树打乱
				ans = false;
			}
			pre = cur.value;
			cur = cur.right;
		}
		return ans;
	}

给定一颗二叉树的头节点head,求以head为头的树中,最小深度是多少?

在这里插入图片描述
1、morris遍历cur来到任何节点的时候,这个level能够正确的更新出来到了第几层。
2、需要正确的发现叶子节点。
如果上面两个问题都解决了,那么问题解决。

如果知道x的层数,y是x的左孩子,那么y一定在x的下一层。
如果知道x的层数,y是x的右孩子,如果y左树上的最右节点指向非x那么y一定在x的下一层。如果是x
那么需要数一下它左树右边界上有几个节点,则用x的层数减去边界上的节点树,这样就解决了第一个问题。
第二个问题:如何发现所有的叶节点,对于能回到自己两次的节点,第二次回到的时候判断其下的是不是叶子节点:如第二次回到b判断d是否是叶子节点,第二次回到a恢复完后判断e是否是叶子节点,最后再判断整棵树的最右节点是否是叶子节点。
在这里插入图片描述

在这里插入图片描述
走一下流程:
在这里插入图片描述
一开始pre = a
level = 0
min = 系统最大

首先a是找a左树的最右节点f,更新level为1,以及指向
在这里插入图片描述
然后cur来到b
在这里插入图片描述
来到k,k左树的最右节点为空,不等于b,k的level是3
在这里插入图片描述
回到b,level = 2,然后更新min = 3
在这里插入图片描述
然后来到c
在这里插入图片描述
在这里插入图片描述
这样依次更新下去

// 本题测试链接 : https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/
public class MinDepth {

	// 不提交这个类
	public static class TreeNode {
		public int val;
		public TreeNode left;
		public TreeNode right;

		public TreeNode(int x) {
			val = x;
		}
	}

	// 下面的方法是一般解
	public static int minDepth1(TreeNode head) {
		if (head == null) {
			return 0;
		}
		return p(head);
	}

	// 返回x为头的树,最小深度是多少
	public static int p(TreeNode x) {
		//x是叶节点,深度为1
		if (x.left == null && x.right == null) {
			return 1;
		}
		// 左右子树起码有一个不为空
		int leftH = Integer.MAX_VALUE;
		if (x.left != null) {
			leftH = p(x.left);
		}
		int rightH = Integer.MAX_VALUE;
		if (x.right != null) {
			rightH = p(x.right);
		}
		return 1 + Math.min(leftH, rightH);
	}

	// 下面的方法是morris遍历的解
	public static int minDepth2(TreeNode head) {
		if (head == null) {
			return 0;
		}
		TreeNode cur = head;
		TreeNode mostRight = null;
		int curLevel = 0;
		int minHeight = Integer.MAX_VALUE;
		while (cur != null) {
			mostRight = cur.left;
			if (mostRight != null) {
				int rightBoardSize = 1;
				while (mostRight.right != null && mostRight.right != cur) {
					rightBoardSize++;
					mostRight = mostRight.right;
				}
				if (mostRight.right == null) { // 第一次到达
					curLevel++;
					mostRight.right = cur;
					cur = cur.left;
					continue;
				} else { // 第二次到达
					if (mostRight.left == null) {
						minHeight = Math.min(minHeight, curLevel);
					}
					curLevel -= rightBoardSize;
					mostRight.right = null;
				}
			} else { // 只有一次到达
				curLevel++;
			}
			cur = cur.right;
		}
		int finalRight = 1;
		cur = head;
		while (cur.right != null) {
			finalRight++;
			cur = cur.right;
		}
		if (cur.left == null && cur.right == null) {
			minHeight = Math.min(minHeight, finalRight);
		}
		return minHeight;
	}

}

总结:
如果需要信息的强整合,就不能用morris遍历,采用二叉树递归套路,如果不需要手机左右树的全部信息那么可以采用morris遍历

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

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

相关文章

UE4/5样条线学习(二):样条网格体组件的使用

目录 效果展示: 制作: 效果展示: 制作: 前面的步骤和之前的UE4/5样条线学习(一):基础的样条线使用_多方通行8的博客-CSDN博客是一样的。 创建一个actor蓝图,然后一个公告板组件&…

Redis Lua脚本书写

目录 1. 级联缓存值 1.1 级联缓存session及相关信息 lua脚本语句 redis运行示例 2. 级联查询 2.1 级联查询session lua脚本语句 redis运行示例 3. 级联更新 3.1 级联更新accountId对应的用户信息 lua脚本 redis运行示例 4. 级联续期 4.1 刷新session时级联续期 lu…

嵌入式数据库之sqlite3

一、数据库基本概念 数据:能够输入计算机并能被计算机程序识别和处理的信息集合。 数据库:数据库是在数据库管理系统管理和控制之下,存放在存储介质上的数据集合。 二、常用的数据库 1.大型数据库 Oracle公司是最早开发关系数据库的厂商之一…

架构设计之分析系统性能问题

我们在讨论高性能架构之前,需要先聊聊什么叫高性能,以及如何量化地测试系统的性能。在02 讲中,我们讨论了一些和并发相关的指标。事实上,并发数正是系统性能的核心指标之一,因为高并发会引起系统资源短缺,来…

【夜深人静学数据结构与算法 | 第二篇】后缀(逆波兰)表达式

目录 前言: 中缀表达式: 后缀表达式: 中缀表达式转后缀表达式: 后缀表达式计算结果: 总结: 前言: 计算机在计算四则运算的时候,由于括号以及运算优先级的存在,并不…

大数据Doris(四十一):Routine Load严格模式和导入案例

文章目录 Routine Load严格模式和导入案例 一、严格模式 二、严格模式导入Kafka数据到Doris Routine Load严格模式和导入案例

【Thunder送书 | 第三期 】「Python系列丛书」

文章目录 前言《Python高效编程——基于Rust语言》《Python从入门到精通》《Python Web深度学习》《Python分布式机器学习》文末福利 | 赠书活动 前言 Thunder送书第三期开始啦!前面两期都是以【文末送书】的形式开展,本期将赠送Python系列丛书&#xff…

下载安装Visual Studio 2017 Community 来编译NIM_PC_DEMO

1、下载vs2017的引导程序 官方并没有为vs2017提供离线安装包,所以我们选择在线安装。 首先我们下载vs2017的引导程序:Visual Studio 2017安装包 包含如下4个文件: vs_Community.exe: 社区版,免费。但是需要登录微软…

第四节 字符串

文章目录 字符串1.1 字符串介绍1.2 字符串的定义1.3 字符串的输入和输出1.3.1 字符串的索引 1.4 字符串切片1.4.1 切片几种写法 1.5 字符串常用函数1.5.1 find()1.5.2 index()1.5.3 扩展知识: rfind()和rindex()1.5.4 count()1.5.5 replace()1.5.6 split()1.5.7 join() 1.6 字符…

C++常用STL容器--list

C常用STL容器--list list基本概念list构造函数list赋值、交换list大小操作list插入、删除list数据获取list反转、排序 list基本概念 功能: 将数据进行链式存储 链表(list) 是一种物理存储单元上非连续的存储结构,数据元素的逻辑顺序是通过链表中的指针…

高并发架构设计方法

我们知道,“高并发”是现在系统架构设计的核心关键词。一个架构师如果设计、开发的系统不支持高并发,那简直不好意思跟同行讨论。但事实上,在架构设计领域,高并发的历史非常短暂,这一架构特性是随着互联网,…

Linux之配置网络

目录 Linux之配置网络 网络接口 网络类型符号 类型 设备类型或位置选择 类型 网络连接 网络配置 三种方法 方法1 --- 使用nmtui进行网路配置 方法2 --- 使用nmcli设置 方法3 --- 修改配置文件 方法4 --- cockpit配置示意图 使用ip命令配置临时生效的网络连接 测试网…

chatgpt赋能python:Python收集数据的介绍

Python收集数据的介绍 Python是一个多功能的编程语言,其拥有强大的数据收集和分析功能。为了充分利用Python的数据处理和挖掘功能,一些优秀的数据收集工具被开发出来。在本文中,我们将介绍如何使用Python收集数据,并介绍一些常用…

总结900

目标规划: 月目标:6月(线性代数强化9讲,考研核心词过三遍) 周目标:线性代数强化3讲,英语背3篇文章并回诵,检测 每日规划 今日已做 1.读六级阅读 2.完成学习通考试(没做计划) 3.阅…

[编程工具]Unity配表导出工具TableExporter1.1

[ 目录 ] 0. 前言1. 属性拓展优化(1)反射获取转化函数 TryParse(2)反射获取EmptyReplace(3)属性类型(4)属性拓展 2. 模板处理(1)替换内容(2&#…

chatgpt赋能python:Python如何放大界面——实用技巧

Python如何放大界面——实用技巧 在Python中,很多时候我们需要放大界面来更清楚的展示内容。这篇文章将介绍Python放大界面的方法。 放大界面的原理 在Python中,放大界面的原理实际上就是改变窗口的大小。我们可以通过改变窗口的尺寸实现放大效果。 …

LuatOS-Air AT应用指南--RNDIS

简介 RNDIS是指Remote NDIS,基于USB实现RNDIS实际上就是TCP/IP over USB,就是在USB设备上跑TCP/IP,让USB设备看上去像一块网卡。从而使Windows /Linux可以通过 USB 设备连接网络。 Window系统 window系统支持RNDIS直接用usb连接就可以使用&a…

2023/6/14总结

JS的学习: JavaScript是一种运行在客户端(浏览器)的编程语言,实现人机交互的效果 主要作用: 网页特效表单验证数据交互 JS的组成 ECMAScript 规定了js基础的语法核心知识 Web APIs DOM:操作文档,对页面…

简单的TCP网络程序·线程池(后端服务器)

目录 版本四:线程池 注意事项 文件:Task.hpp -- 任务单独为一个文件 组件:日志修改 新函数:vprintf() 可变参数的提取逻辑 vfprintf()的工作原理 初始化一个va_list 日志准备 获取时间小知识 日志初版 日志启动测试 …

DAY23:二叉树(十三)二叉树的最近公共祖先+二叉搜索树的最近公共祖先

文章目录 236.二叉树的最近公共祖先思路完整版后序遍历的进一步理解为什么左为空右不为空的时候return right这个逻辑是否包含p/q本身就是公共祖先的情况 235.二叉搜索树的最近公共祖先思路关于遍历顺序 递归法最开始的写法debug测试修改版 迭代法最开始的写法为什么最开始这种…