面试中很值得聊的二叉树遍历方法——Morris遍历
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遍历的更多相关文章
- 为什么要重写hashcode和equals方法?初级程序员在面试中很少能说清楚。
我在面试 Java初级开发的时候,经常会问:你有没有重写过hashcode方法?不少候选人直接说没写过.我就想,或许真的没写过,于是就再通过一个问题确认:你在用HashMap的时候,键(Key)部分, ...
- 剑指offer-第六章面试中的各项能力(二叉树的深度)
题目:1:输入一个二叉树,求二叉树的深度.从根节点开始最长的路径. 思路:我们可以考虑用递归,求最长的路径实际上就是求根节点的左右子树中较长的一个然后再加上1. 题目2:输入一颗二叉树的根节点,判断该 ...
- 将Eclipse项目导入到Android studio 中 很多点9图出现问题解决方法
在build.gradle里添加以下两句: aaptOptions.cruncherEnabled = false aaptOptions.useNewCruncher = false
- 【数据结构与算法】二叉树的 Morris 遍历(前序、中序、后序)
前置说明 不了解二叉树非递归遍历的可以看我之前的文章[数据结构与算法]二叉树模板及例题 Morris 遍历 概述 Morris 遍历是一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1 ...
- 算法进阶面试题03——构造数组的MaxTree、最大子矩阵的大小、2017京东环形烽火台问题、介绍Morris遍历并实现前序/中序/后序
接着第二课的内容和带点第三课的内容. (回顾)准备一个栈,从大到小排列,具体参考上一课.... 构造数组的MaxTree [题目] 定义二叉树如下: public class Node{ public ...
- Morris 遍历实现二叉树的遍历
Morris 遍历实现二叉树的遍历 作者:Grey 原文地址: 博客园:Morris 遍历实现二叉树的遍历 CSDN:Morris 遍历实现二叉树的遍历 说明 Morris 遍历可以实现二叉树的先,中 ...
- 经典算法 Morris遍历
内容: 1.什么是morris遍历 2.morris遍历规则与过程 3.先序及中序 4.后序 5.morris遍历时间复杂度分析 1.什么是morris遍历 关于二叉树先序.中序.后序遍历的递归和非递 ...
- 基于bs4库的HTML标签遍历方法
基于bs4库的HTML标签遍历方法 import requests r=requests.get('http://python123.io/ws/demo.html') demo=r.text HTM ...
- 面试大总结之二:Java搞定面试中的二叉树题目
package BinaryTreeSummary; import java.util.ArrayList; import java.util.Iterator; import java.util.L ...
随机推荐
- VS Code 全部快捷键一览表(巨TM全)
常用 General 按 Press 功能 Function Ctrl + Shift + P,F1 显示命令面板 Show Command Palette Ctrl + P 快速打开 Quick O ...
- LeetCode 45. 跳跃游戏 II | Python
45. 跳跃游戏 II 题目来源:https://leetcode-cn.com/problems/jump-game-ii 题目 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素 ...
- vue-cli3使用全局scss
在开发项目的时候,经常会出现多个元素样式相同,比如颜色相同.这里就需要我们设置公共样式,方便后期调试 一配置方法 1.在src/assets/styles目录下创建文件variable.scss // ...
- Coursera课程笔记----计算导论与C语言基础----Week 12
期末编程测试(Week 12) Quiz1 判断闰年 #include <iostream> using namespace std; int main() { int year; cin ...
- 第十章:Python高级编程-多线程、多进程和线程池编程
第十章:Python高级编程-多线程.多进程和线程池编程 Python3高级核心技术97讲 笔记 目录 第十章:Python高级编程-多线程.多进程和线程池编程 10.1 Python中的GIL 10 ...
- Scrapy模块使用出错,出现builtins.ImportError: DLL load failed: 找不到指定的程序
问题描述:初次学习scrapy,使用scrapy官方文档创建爬虫项目出错, 出现builtins.ImportError: DLL load failed: 找不到指定的程序, ImportError ...
- QTreeWidget更新后保存节点的展开状态
class Xx : public QWidget { Q_OBJECT struct ItemState{ ItemState(); int _id; bool _isExpend; }; publ ...
- 单线程和多线程执行对比—Python多线程编程
单线程和多线程执行对比 本章使用递归求斐波那契.阶乘与累加函数的执行来对比单线程与多线程: 斐波那契.阶乘与累加(mtfacfib.py): import threading from time ...
- NOI Online #2 赛后题解
color 题意 \(\;\) 给定\(p_1,p_2\),要求\(p_1\)的倍数格子填红色,\(p_2\)的倍数格子填蓝色,既是\(p_1\)又是\(p_2\)倍数的格子颜色任选.求是否存在一种填 ...
- 网页爬虫--python3.6+selenium+BeautifulSoup实现动态网页的数据抓取,适用于对抓取频率不高的情况
说在前面: 本文主要介绍如何抓取 页面加载后需要通过JS加载的数据和图片 本文是通过python中的selenium(pyhton包) + chrome(谷歌浏览器) + chromedrive(谷歌 ...