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 ...
随机推荐
- Warmup小记
什么是warmup 热身,在刚刚开始训练时以很小的学习率进行训练,使得网络熟悉数据,随着训练的进行学习率慢慢变大,到了一定程度,以设置的初始学习率进行训练,接着过了一些inter后,学习率再慢慢变小: ...
- python基础练习题(题目 递归求等差数列)
day20 --------------------------------------------------------------- 实例028:递归求等差数列 题目 有5个人坐在一起,问第5个 ...
- python学习-Day6
目录 学习内容详述 上期 '猜年龄'练习详解: while循环补充 while + else 死循环与while的嵌套 while嵌套 for循环 range关键字 range用法 什么是爬虫??? ...
- create-react-app的TS支持以及css模块化
开始: 利用官方脚手架,搭建react工程.参考:https://react.docschina.org/docs/create-a-new-react-app.html. 过程: 1.暴露webpa ...
- 浅尝Spring注解开发_自定义注册组件、属性赋值、自动装配
Spring注解开发 浅尝Spring注解开发,基于Spring 4.3.12 包含自定义扫描组件.自定义导入组件.手动注册组件.自动注入方法和参数.使用Spring容器底层组件等 配置 @Confi ...
- Git (常用命令)
某程序猿退休后决定练习书法,于是花重金买下文房四宝.某日,饭后突生雅兴,一番磨墨拟纸 并点上上好檀香.定神片刻,泼墨挥毫,郑重地写下一行:Hello World 斯~ 有被冷到吗哈哈哈 Git常用命令 ...
- 建设Kubernetes生产环境的16条建议
点击上方"开源Linux",选择"设为星标" 回复"学习"获取独家整理的学习资料! Kubernetes是用于构建高度可扩展系统的强大工具. ...
- 实战|Linux大文件切割
一个执着于技术的公众号 日常工作中需要对日志文件进行分析,当日志文件过大时,Linux中使用vim.cat.grep.awk等这些工具对大文件日志进行分析将会成为梦魇,具体表现在: 执行速度缓慢,文件 ...
- 442. Find All Duplicates in an Array - LeetCode
Question 442. Find All Duplicates in an Array Solution 题目大意:在数据中找重复两次的数 思路:数组排序,前一个与后一个相同的即为要找的数 Jav ...
- 637. Average of Levels in Binary Tree - LeetCode
Question 637. Average of Levels in Binary Tree Solution 思路:定义一个map,层数作为key,value保存每层的元素个数和所有元素的和,遍历这 ...