Moriis神级遍历!
Moriis 遍历
Morris 遍历是二叉树遍历的一种方式,传统的递归和非递归遍历的时间复杂的都是O(N),空间复杂度都是O(h)(h为树的高度),而 Morris 遍历可以做到时间复杂的依然为 O(N) 的情况下,空间复杂度降到 O(1)。
Morris 遍历的实现原理
定义一个 cur 节点,指向 root 节点:
当 cur.left == null 时,cur 向右移动 cur = cur.right
当 cur.left != null 时,找到 cur 左子树的最右的节点 mostRight
- 当 mostRight 右指针为空时,修改 mostRight 右指针指向 cur,cur 向左移动,cur = cur.left
- 当 mostRight 右指针指向 cur 时,修改 mostRight 右指针指向 null,cur 向右移动
cur == null 停止。
cur 依次来到节点的顺序我们称之为 Moriis 序。我们来看下面的图片:











从 Morris 序中我们可以发现:
任何节点如果其有左孩子一定会在 Moriis 序中出现两次,为什么改动左子树最右节点的右指针的走向,为了确认是第一次来到当前节点还是第二次来到当前节点,左子树最右节点右指针指向空表示第一次来到当前节点,指向自己则说明第二次来。
Morris 遍历代码实现
我们观察上面示例的 Morris 序:1,2,4,2,5,1,3,6,3,7 可以发现:
拥有左子树的节点1,2,3都出现了两次,
所以我们看只打印第一次出现的节点,次序为1,2,4,5,3,6,7是不是发现这就是先序遍历了呢。
我们在尝试让其打印出现两次的节点的第二次,只出现一次的直接打印,顺序为4,2,5,1,6,3,7是不是发现这就是中序遍历了呢。
要实现后序遍历有一点繁琐,打印时机就在能回到自己两次且第二次回到自己的时候,但并非打印他自己 ,而是逆序打印它的左树右边界(二叉树的边界:左边界的定义是从根到最左侧结点的路径。 右边界的定义是从根到最右侧结点的路径。 若根没有左子树或右子树,则根自身就是左边界或右边界。),然后再单独逆序打印整棵树的右边界,来用上面的示例模拟一下过程:
- 当第二次回到2时,此时 cur 的左树右边界为4,逆序打印4
- 当第二次回到1时,此时 cur 的左树右边界为2 --> 5,逆序打印后4,5,2
- 当第二次回到3时,此时 cur 的左树右边界为6,逆序打印后4,5,2,6
- 最后单独逆序打印整棵树的右边界,打印后4,5,2,6,7,3,1为后序遍历的结果。
代码实现:
中序遍历:
public static void morrisIn(TreeNode root) {
if(root == null) {
return;
}
TreeNode cur = root;
TreeNode mostRight = null;
while(cur != null) {
mostRight = cur.left; //cur的左子树
if(mostRight != null) { //左子树不为空
//mostRight.right==null --> 找到左子树最右节点(第一次)
//mostRight.right != cur --> 找到左子树最右节点(第二次)
while(mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}//从while中出来就表示找到左子树最右节点为mostRight
if(mostRight.right == null) { //第一次到达
mostRight.right = cur; //修改mostRight右指针指向当前节点
cur = cur.left; //当前节点cur左移
continue;
} else { //mostRight.right == cur
mostRight.right = null; //当前节点第二次被访问,修改mostRight右指针指向null
}
}
System.out.println(cur.val + " "); //中序遍历
cur = cur.right; //cur.left == null(左子树为空) || mostRight.right != cur 此时都需要右移
}
}
先序遍历
public static void morrisPre(TreeNode root) {
if(root == null) {
return;
}
TreeNode cur = root;
TreeNode mostRight = null;
while(cur != null) {
mostRight = cur.left; //cur的左子树
if(mostRight != null) { //左子树不为空
//mostRight.right==null --> 找到左子树最右节点(第一次)
//mostRight.right != cur --> 找到左子树最右节点(第二次)
while(mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}//从while中出来就表示找到左子树最右节点为mostRight
if(mostRight.right == null) { //第一次到达
mostRight.right = cur; //修改mostRight右指针指向当前节点
System.out.println(cur.val); //先序遍历
cur = cur.left; //当前节点cur左移
continue;
} else { //mostRight.right != cur
mostRight.right = null; //当前节点第二次被访问,修改mostRight右指针指向null
}
}
else {
System.out.println(cur.val); //先序遍历
}
cur = cur.right; //cur.left == null(左子树为空) || mostRight.right != cur 此时都需要右移
}
}
后续遍历:
public static void morrisPos(TreeNode root) {
if(root == null){
return;
}
TreeNode cur = root;
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;
printEdge(cur.left); //第二次出现,打印左树右边界
}
}
cur = cur.right;
}
printEdge(root); //打印整棵树的左树右边界
System.out.println();
}
public static void printEdge(TreeNode node){
TreeNode tail =reverseEdge(node);
TreeNode cur = tail;
while (cur != null ){
System.out.print(cur.value+" ");
cur =cur.right;
}
reverseEdge(tail);
}
public static TreeNode reverseEdge(TreeNode node){
Node pre = null;
Node next = null;
while (node != null){
next = node.right;
node.right = pre;
pre = node;
node = next;
}
return pre;
}
Moriis神级遍历!的更多相关文章
- 《程序员代码面试指南》第三章 二叉树问题 遍历二叉树的神级方法 morris
题目 遍历二叉树的神级方法 morris java代码 package com.lizhouwei.chapter3; /** * @Description:遍历二叉树的神级方法 morris * @ ...
- VIM自动补全插件 - YouCompleteMe--"大神级vim补全插件"
VIM自动补全插件 - YouCompleteMe 序言 vim 之所以被称为编辑器之神多半归功于其丰富的可DIY的灵活插件功能,( 例如vim下的这款神级般的代码补全插件YouCompleteMe) ...
- Github欢乐多 PHP神级代码引发吐槽热
前日,github的PHP板块惊现一段能够提升70%运行效率的代码,引发了全世界众多网友的吐槽和调侃,“awesome!”.“well done!”.“PHP是世界第一语言!”平时不苟言笑,埋头苦干的 ...
- 用了TextMate才知道什么叫神级Editor
用了TextMate才知道什么叫神级Editor 一直用Eclipse作为开发Ruby和Java项目的IDE,但是太耗内存,再开个Firefox和虚拟机就可以直接将MBP弄残了..看到大家都对Mac下 ...
- 20171201 - macOS High Sierra 神级 bug
昨日亲测有效,macOS High Sierra 神级 bug,系统管理员 root 密码为空,输入就可以登录,具备最高权限. 让人不禁想象 Apple Software 怎么了,人才都流失了吗?
- 大神级回答exists与in的区别
google搜了一下,很多帖子,而且出发点不同,各有各的道理,但是有一个帖子讲的特别好: http://zhidao.baidu.com/question/134174568.html 忍不住在百度上 ...
- IntelliJ IDEA 15款 神级超级牛逼插件推荐(超赞,谁用谁知道)
满满的都是干货 所有插件都是在 ctrl+alt+s 里的plugins 里进行搜索安装 1.CodeGlance 代码迷你缩放图插件 2. Codota 代码提示工具,扫描你的代码后,根据你的敲击 ...
- 左神算法书籍《程序员代码面试指南》——3_05Morris遍历二叉树的神级方法【★★★★★】
[问题]介绍一种时间复杂度O(N),额外空间复杂度O(1)的二叉树的遍历方式,N为二叉树的节点个数无论是递归还是非递归,避免不了额外空间为O(h),h 为二叉树的高度使用morris遍历,即利用空节点 ...
- linux内核神级list
源码: #ifndef _LINUX_LIST_H #define _LINUX_LIST_H /* * Simple doubly linked list implementation. * * S ...
随机推荐
- 通过nfs将centos目录挂载到windows 系统的磁盘上
环境:centos8,windows7 1.在centos上安装nfs服务 yum -y install nft-utils 2.启动nfs服务 systemctl start nfs-server ...
- iOS全埋点解决方案-UITableView和UICollectionView点击事件
前言 在 $AppClick 事件采集中,还有两个比较特殊的控件: UITableView •UICollectionView 这两个控件的点击事件,一般指的是点击 UITableViewCell 和 ...
- Java进阶 JVM 内存与垃圾回收篇(一)
JVM 1. 引言 1.1 什么是JVM? 定义 Java Vritual Machine - java 程序的运行环境(Java二进制字节码的运行环境) 好处 一次编译 ,到处运行 自动内存管理,垃 ...
- petite-vue源码剖析-沙箱模型
在解析v-if和v-for等指令时我们会看到通过evaluate执行指令值中的JavaScript表达式,而且能够读取当前作用域上的属性.而evaluate的实现如下: const evalCache ...
- golang bufio解析
golang bufio 当进行频繁地对少量数据读写时会占用IO,造成性能问题.golang的bufio库使用缓存来一次性进行大块数据的读写,以此降低IO系统调用,提升性能. 在Transport中可 ...
- 【Electron】在 WSL2 中 打包 electron Linux 版本
[Electron]在 WSL2 中 打包 electron Linux 版本. 安装 WSL 我使用的是 Ubuntu 20.04.4 LTS 的版本. 安装 WSL 文档地址:https://do ...
- Error:java: Can‘t generate mapping method with primitive return type.报错
原因:Spring项目中使用了JPA以及Mybatis–mapper文件注解引错包导致编译错误 解决: 错误:import org.mapstruct.Mapper;正确路径:import org.a ...
- 并发编程系列之Lock锁可重入性与公平性
一.相似之处:Lock锁 vs Synchronized 代码块 Lock锁是一种类似于synchronized 同步代码块的线程同步机制.从Java 5开始java.util.concurrent. ...
- 一起看 I/O | Flutter 3 正式发布!
作者 / Tim Sneath, Google Flutter 和 Dart 产品组产品经理 Flutter 3 实现了 Flutter 以移动端为中心扩展到多平台的产品规划,并在今年 I/O 大会的 ...
- 由C# dynamic是否装箱引发的思考
前言 前几天在技术群里看到有同学在讨论关于dynamic是否会存在装箱拆箱的问题,我当时第一想法是"会".至于为啥会有很多人有这种疑问,主要是因为觉得dynamic可能是因为有点特 ...