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 ...
随机推荐
- 如何使用 python 爬取酷我在线音乐
前言 写这篇博客的初衷是加深自己对网络请求发送和响应的理解,仅供学习使用,请勿用于非法用途!文明爬虫,从我做起.下面进入正题. 获取歌曲信息列表 在酷我的搜索框中输入关键词 aiko,回车之后可以看到 ...
- rabbitmq简单运用
<?php /** * 生产者 */ $connection = new AMQPConnection([ 'host' => '192.168.23.130', 'port' => ...
- 牛客网 第十八届浙大城市学院程序设计竞赛(同步赛)J--万万没想到 啦啦啦啦啦
我觉得我可以继续wa下去(手动魔鬼笑)-------------------------------------------- 原题链接:https://ac.nowcoder.com/acm/c ...
- c#中判断类是否继承于泛型基类
在c#中,有时候我们会编写类似这样的代码: public class a<T> { //具体类的实现 } public class b : a<string>{} 如果b继承a ...
- Transactional事务,事务嵌套的时候,如果主事务出现问题,子事务执行不需要回滚怎么做?
如果调用的方法在不在同一个service当中,则只需要在子事务当中的方法上方添加注解即可 下方即是:这就话代表:重新开启一个新的事务 @Transactional(propagation = Prop ...
- 2021.12.06 P2508 [HAOI2008]圆上的整点(数论+ π )
2021.12.06 P2508 [HAOI2008]圆上的整点(数论+ \(\pi\) ) https://www.luogu.com.cn/problem/P2508 题意: 求一个给定的圆 \( ...
- 代码管理工具-Git基础介绍及常用技巧
目录 Git起源 基本概念 Branch.HEAD和Commit tree Git分支 git merge 和 git rebase 的区别和抉择 与远程仓库的交互 关于一些实际开发场景的问题和解决方 ...
- XCTF练习题---MISC---stage1
XCTF练习题---MISC---stage1 flag:AlphaLab 解题步骤: 1.观察题目,下载附件 2.打开附件后发现是一张图片,初步判断是图片隐写,上Stegsolve进行转换,得到一张 ...
- ONNXRuntime学习笔记(四)
接上一篇在Python端的onnx模型验证结果,上一篇在Pytorch和onnxruntime-gpu推理库上分别进行效果效率统计分析,结论要比最初设置的50ms高很多,这一篇我将在C++端写个测试代 ...
- Linux 系统安装 AutoFs 挂载服务
关注「开源Linux」,选择"设为星标" 回复「学习」,有我为您特别筛选的学习资料~ 作者: Escape 链接: https://escapelife.github.io/pos ...