Morris遍历

通过利用空闲指针的方式,来节省空间。时间复杂度O(N),额外空间复杂度O(1)。普通的非递归和递归方法的额外空间和树的高度有关,递归的过程涉及到系统压栈,非递归需要自己申请栈空间,都具有O(N)的额外空间复杂度。

Morris遍历的原则:

1. 假设当前节点为cur,

2. 如果cur没有左孩子,cur向右移动,cur = cur.right

3. 如果cur有左孩子,找到左子树上最右的节点mostRight

  3.1 如果mostRight.right == null,令mostRight.right = cur,cur向左移动,cur = cur.left

  3.2 如果mostRight.right == cur,令mostRight.right = null,cur向右移动,cur = cur.right

4. 如果cur == null 停止遍历

Morris:1242513637

     public static void morris(TreeNode head){
if(head == null) return;
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){//cur为空遍历停止【4】
mostRight = cur.left;//是cur左子树上最右的节点
if(mostRight != null){//有左子树【3】
while(mostRight.right != null && mostRight != cur){//mostRight!=cur不加就会绕环循环
mostRight = mostRight.right;//找最右节点【3】
}
if(mostRight.right == null){//第一次来到cur【3.1】
mostRight.right = cur;
cur = cur.left;
continue;//执行循环
}else {//mostRight.right = cur第二次来到cur【3.2】
mostRight.right = null;
}
}
cur = cur.right;//没有左子树【2】 }
}

所有节点遍历左子树右边界的时间总代价O(N)

基于Morris的先中后序遍历

如果cur有左子树一定能遍历2次,没有左子树只能遍历一次。

先序遍历

Morris:1242513637

Morris先序:1245367

基于Morris的先序遍历,如果一个节点可以到达两次则打印第一次,如果只能到达一次直接打印。

     public static void morrisPre(TreeNode head){
if(head == null) return;
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){//有左子树
mostRight = cur.left;
if(mostRight != null){
while(mostRight.right != null && mostRight != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){//第一次来到左子树
System.out.println(cur.val);//打印
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
}
}else{
System.out.println(cur.val);//没有左子树 只会遍历一次
}
cur = cur.right;
}
}

中序遍历

Morris:1242513637

Morris中序:4251637

基于Morris的中序遍历,如果一个节点可以到达两次则打印第二次,如果只能到达一次直接打印。

     public static void morrisIn(TreeNode head) {
if(head == null) return;
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){
mostRight = cur.left;
if(mostRight != null){
while(mostRight.right != null && mostRight != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
}
}
//没有左树跳过if直接打印,有左树第二次来到cur退出循环打印
System.out.println(cur.val);
cur = cur.right;
}
}

后序遍历

Morris:1242513637

Morris先序:4526731

基于Morris的后序遍历,如果一个节点可以到达两次则第二次到达时逆序打印左树的右边界,单独逆序打印整棵树的右边界。

(1)逆序右边界(等同于单链表的逆序)

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

(2)逆序打印以head为头节点的右边界。

     public static void printRightEdge(TreeNode head) {
TreeNode tail = reverseEdge(head);//逆转右边界
TreeNode cur = tail;
while(cur != null){
System.out.println(cur.val + " ");
cur = cur.right;
}
reverseEdge(tail);//逆转回去 恢复原树
}

(3)在Morris遍历中按时机打印。

     public static void morrisPost(TreeNode head){
if(head == null) return;
TreeNode cur = head;
TreeNode mostRight = null;
while(cur != null){
mostRight = cur.left;
if(mostRight != null){
while(mostRight.right != null && mostRight != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {//第二次达到 逆序打印左子树的右边界
mostRight.right = null;
printRightEdge(cur.left);
}
}
cur = cur.right;
}
//最后退出循环之后,单独打印整棵树的右边界
printRightEdge(head);
}

Morris遍历的应用

如何判断一棵树是否是搜索二叉树?

中序遍历升序就是搜索二叉树。

     public static boolean isBST(TreeNode head){
if(head == null) return true;
TreeNode cur = head;
TreeNode mostRight = null;
int preValue = Integer.MIN_VALUE;//上一次得到的值
while(cur != null){
mostRight = cur.left;
if(mostRight != null){
while(mostRight.right != null && mostRight != cur){
mostRight = mostRight.right;
}
if(mostRight.right == null){
mostRight.right = cur;
cur = cur.left;
continue;
}else {
mostRight.right = null;
}
}
//中序遍历的操作时机在这里 所以在这里进行判断
if(cur.val <= preValue){//没有递增
return false;
}
preValue = cur.val;
cur = cur.right;
}
return true;
}

总结

树型DP问题的套路:定义一个类收集树的信息,定义一个递归函数,递归地收集左子树的信息和右子树的信息,再整合得到以当前节点为根的树的信息。

什么时候用树型DP什么时候用Morris遍历?

当必须得到左树的信息和右树的信息后,再在当前节点整合二者信息后做出判断则用树型DP是最优解。

当不需要整合左树和右树信息的时候,可以用树型DP,但是Morris是最优解。

面试中很值得聊的二叉树遍历方法——Morris遍历的更多相关文章

  1. 为什么要重写hashcode和equals方法?初级程序员在面试中很少能说清楚。

    我在面试 Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分, ...

  2. 剑指offer-第六章面试中的各项能力(二叉树的深度)

    题目:1:输入一个二叉树,求二叉树的深度.从根节点开始最长的路径. 思路:我们可以考虑用递归,求最长的路径实际上就是求根节点的左右子树中较长的一个然后再加上1. 题目2:输入一颗二叉树的根节点,判断该 ...

  3. 将Eclipse项目导入到Android studio 中 很多点9图出现问题解决方法

    在build.gradle里添加以下两句: aaptOptions.cruncherEnabled = false aaptOptions.useNewCruncher = false

  4. 【数据结构与算法】二叉树的 Morris 遍历(前序、中序、后序)

    前置说明 不了解二叉树非递归遍历的可以看我之前的文章[数据结构与算法]二叉树模板及例题 Morris 遍历 概述 Morris 遍历是一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1 ...

  5. 算法进阶面试题03——构造数组的MaxTree、最大子矩阵的大小、2017京东环形烽火台问题、介绍Morris遍历并实现前序/中序/后序

    接着第二课的内容和带点第三课的内容. (回顾)准备一个栈,从大到小排列,具体参考上一课.... 构造数组的MaxTree [题目] 定义二叉树如下: public class Node{ public ...

  6. Morris 遍历实现二叉树的遍历

    Morris 遍历实现二叉树的遍历 作者:Grey 原文地址: 博客园:Morris 遍历实现二叉树的遍历 CSDN:Morris 遍历实现二叉树的遍历 说明 Morris 遍历可以实现二叉树的先,中 ...

  7. 经典算法 Morris遍历

    内容: 1.什么是morris遍历 2.morris遍历规则与过程 3.先序及中序 4.后序 5.morris遍历时间复杂度分析 1.什么是morris遍历 关于二叉树先序.中序.后序遍历的递归和非递 ...

  8. 基于bs4库的HTML标签遍历方法

    基于bs4库的HTML标签遍历方法 import requests r=requests.get('http://python123.io/ws/demo.html') demo=r.text HTM ...

  9. 面试大总结之二:Java搞定面试中的二叉树题目

    package BinaryTreeSummary; import java.util.ArrayList; import java.util.Iterator; import java.util.L ...

随机推荐

  1. MacBook Pro装Win7后喇叭没有声音

    将MacBook的系统由XP改为Win7 64位后,发现喇叭没有声音了,装了bootcamp并升级到3.2版本都无济于事,google了下,发现还是驱动的问题,Win7下在设备管理器中看到声卡为Hig ...

  2. csp-j2019游记

    我一pj蒟蒻这点水平还来写游记? 算了,毕竟是第一次,记录一下吧 noip->csp 话说我跟竞赛是不是天生八字不合啊...... 小学的时候学小奥,等我开始报名比赛,当时似乎所有竞赛都被叫停了 ...

  3. prufer编码学习笔记

    prufer 编码 对于一个无根树,他的 prufer 编码是这样确定的: 每次找到编号最小的一个叶子节点,也就是度数为\(1\)的节点,把和它相连的点,加入 prufer 编码序列的末尾,然后把这个 ...

  4. 慎用ToLower和ToUpper,小心把你的系统给拖垮了

    不知道何时开始,很多程序员喜欢用ToLower,ToUpper去实现忽略大小写模式的字符串相等性比较,有可能这个习惯是从别的语言引进的,大胆猜测下是JS,为了不引起争论,我指的JS是技师的意思~ 一: ...

  5. 量子纠错码——Stabilizer codes

    对于错误,一般有两种: random: 错误以一定的概率发生在每个比特上(对这种问题的研究一般是信息论中,信道熵一类的问题) worst case: 错误发生在某个比特上,这也是纠错码襄阳解决的问题 ...

  6. G - Pictures with Kittens (easy version) dp

    https://codeforces.com/problemset/problem/1077/F1 这个其实是一个比较简单的dp了 题目大意: 给你n个数,让你从n个数里选出x个数,并且每隔k个至少选 ...

  7. 服务器3C直连网络好呢还是3C精品网络更好呢?

    3C直连网络:通过用自有AS号与中国电信CTcc,中国联通CUcc,中国移动CMcc企业网进行直接接驳,提供对大陆方向有更高要求的网络接入服务. 简称:国内3c直连. 3C精品专线网:在3C直连基础上 ...

  8. 在Maven项目中添加代码目录下的配置文件

    问题 Maven 是约定大于配置的一种工具, 通常约定在 src/main/resources 目录下放配置文件, 当我们想要在 src/main/java 代码目录下放置配置文件用来测试, Mave ...

  9. 字节码编程,Javassist篇二《定义属性以及创建方法时多种入参和出参类型的使用》

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 在上一篇 Helloworld 中,我们初步尝试使用了 Javassist字节编程的 ...

  10. python语法学习第一天--变量、运算符、数据类型

    变量:计算机中的一块内存,给变量赋值意味着将值存入内存中 python中变量不用类型声明(根据赋的值决定类型),但使用时(创建时)必须赋值(=赋值). 多个变量的赋值: ①a=b=c=1; ②a,b, ...