Morris 遍历实现二叉树的遍历

作者:Grey

原文地址:

博客园:Morris 遍历实现二叉树的遍历

CSDN:Morris 遍历实现二叉树的遍历

说明

Morris 遍历可以实现二叉树的先,中,后序遍历,且时间复杂度O(N), 空间复杂度可以做到O(1)

Morris 遍历流程

假设有一棵如下的二叉树

Morris遍历的流程主要分如下几个步骤:

第一步,从头节点开始遍历。

第二步,假设当前遍历的节点是cur

第三步,如果cur无左树, cur来到其右树上,即:cur = cur.right

第四步,如果cur有左树,找到cur左树最右节点,假设叫mostRight,则有如下两种小情况:

情况1,如果mostRight的右指针指向空, 则将mostRight的右指针指向cur,即:mostRight.right = cur, 然后将cur向左移动,即:cur = cur.left

情况2,如果mostRight的右指针指向当前节点cur,则将mostRight的右指针指向空,即:mostRight.right = null,然后将cur向右移动,即:cur = cur.right

第五步:当cur = null,遍历结束。

根据如上流程,示例二叉树的Morris遍历序列为:

1-->2-->4-->7-->11-->7-->4-->8-->12-->8-->1-->3-->5-->3-->6-->9-->13-->6-->10

Morris遍历可以实现在O(N)时间复杂度内,用O(1)的空间复杂度实现对树的遍历,而且,只要某个节点有右树,则这个节点一定会被遍历两次,我们可以通过Morris遍历来实现二叉树的先,中,后序遍历,做到时间复杂度O(N),空间复杂度O(1)

代码实现如下:

public class Code_Morris {

    //当前是cur
//1. cur无左树,cur = cur.right
//2. cur有左树,找到左树最右节点mostRight
// a. mostRight的右指针指向null, mostRight.right = cur, cur = cur.right
// b. mostRight的右指针指向当前节点cur,mostRight.right = null, cur = cur.right
//3. cur = null 停
public static void morrisPrint(TreeNode head) {
if (head == null) {
return;
}
System.out.println("....morris order....");
TreeNode cur = head;
System.out.print(cur.val + "-->");
TreeNode mostRight;
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;
System.out.print(cur.val + "-->");
continue;
} else {
mostRight.right = null;
}
}
cur = cur.right;
if (cur != null) {
System.out.print(cur.val + "-->");
}
}
}
}

Morris遍历实现先序遍历

根据Morris的遍历结果,没有右树的点只会遍历一次,有右树的点会遍历两次,针对遍历一次的点,遍历到就收集,针对遍历两次的点,第一次遍历到就收集,第二次遍历到不收集,整个流程跑完,则得到了先序遍历的结果。

代码如下:

    public static List<Integer> preorderTraversal(TreeNode root) {
if (null == root) {
return new ArrayList<>();
}
List<Integer> ans = new ArrayList<>();
TreeNode mostRight;
TreeNode cur = root;
while (cur != null) {
mostRight = cur.left;
if (mostRight != null) {
while (mostRight.right != null && mostRight.right != cur) {
mostRight = mostRight.right;
}
if (mostRight.right == null) {
// 有右树,第一次来到自己就收集
ans.add(cur.val);
mostRight.right = cur;
cur = cur.left;
continue;
} else {
// mostRight.right = cur;
mostRight.right = null;
}
} else {
// 没有右树的,来到就收集
ans.add(cur.val);
}
cur = cur.right;
}
return ans;
}

测评链接:LeetCode 144. Binary Tree Preorder Traversal

Morris遍历实现中序遍历

针对遍历一次的点,遍历到就收集,针对遍历两次的点,第一次遍历到不收集,第二次遍历才收集,整个流程跑完,则得到了中序遍历的结果。

代码如下:

class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<Integer> ans = new ArrayList<>();
TreeNode mostRight;
TreeNode cur = root;
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 {
// 来到自己两次的点,第二次来到才收集
ans.add(cur.val);
mostRight.right = null;
}
} else {
// 只来到自己一次的点,来到就收集
ans.add(cur.val);
}
cur = cur.right;
}
return ans;
}
}

测评链接:LeetCode 94. Binary Tree Inorder Traversal

Morris遍历实现后序遍历

Morris遍历实现后序遍历相对比较麻烦,处理时机只放在能回到自己两次的点,能回到自己两次的点在第二次回到自己的时刻,不打印它自己,而是逆序打印他左树的右边界, 整个遍历结束后,单独逆序打印整棵树的右边界,即得到了后序遍历的结果。

代码如下:

    public List<Integer> postorderTraversal(TreeNode root) {
if (root == null) {
return new ArrayList<>();
}
List<Integer> ans = new ArrayList<>();
TreeNode cur = root;
TreeNode mostRight;
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;
// 第二次来到自己的时候,收集自己的左树的右边界
collect(cur.left, ans);
}
}
cur = cur.right;
}
collect(root, ans);
return ans;
} private void collect(TreeNode root, List<Integer> ans) {
TreeNode node = reverse(root);
TreeNode c = node;
while (c != null) {
ans.add(c.val);
c = c.right;
}
reverse(node);
} private TreeNode reverse(TreeNode node) {
TreeNode pre = null;
TreeNode cur = node;
while (cur != null) {
TreeNode t = cur.right;
cur.right = pre;
pre = cur;
cur = t;
}
return pre;
}

需要注意两点:

第一点,collect方法即逆序收集左树的有边界,由于每个节点没有指向父的指针,所以,要实现逆序,需要针对右边界采用反转链表的方式。即reverse函数的逻辑。

第二点,在collect方法调用完反转链表操作后,还要还原整个右边界。否则整棵树的指针就指乱了。

测评链接:LeetCode 145. Binary Tree Postorder Traversal

更多

算法和数据结构笔记

参考资料

算法和数据结构体系班-左程云

Morris 遍历实现二叉树的遍历的更多相关文章

  1. 二叉树的遍历--C#程序举例二叉树的遍历

    二叉树的遍历--C#程序举例二叉树的遍历 关于二叉树的介绍笨男孩前面写过一篇博客 二叉树的简单介绍以及二叉树的存储结构 遍历方案 二叉树的遍历分为以下三种: 先序遍历:遍历顺序规则为[根左右] 中序遍 ...

  2. 二叉树的遍历(递归,迭代,Morris遍历)

    二叉树的三种遍历方法: 先序,中序,后序,这三种遍历方式每一个都可以用递归,迭代,Morris三种形式实现,其中Morris效率最高,空间复杂度为O(1). 主要参考博客: 二叉树的遍历(递归,迭代, ...

  3. 二叉树的遍历(递归,迭代,Morris遍历)

    二叉树的遍历: 先序,中序,后序: 二叉树的遍历有三种常见的方法, 最简单的实现就是递归调用, 另外就是飞递归的迭代调用, 最后还有O(1)空间的morris遍历: 二叉树的结构定义: struct ...

  4. 二叉树的遍历——Morris

    在之前的博客中,博主讨论过二叉树的经典遍历算法,包括递归和常规非递归算法,其时间复杂度和空间复杂度均为O(n).Morris算法巧妙地利用了二叉树的线索化思路,将二叉树的遍历算法的空间复杂度降低为O( ...

  5. 二叉树中序遍历,先序遍历,后序遍历(递归栈,非递归栈,Morris Traversal)

    例题 中序遍历94. Binary Tree Inorder Traversal 先序遍历144. Binary Tree Preorder Traversal 后序遍历145. Binary Tre ...

  6. [LeetCode] Construct Binary Tree from Preorder and Inorder Traversal 由先序和中序遍历建立二叉树

    Given preorder and inorder traversal of a tree, construct the binary tree. Note:You may assume that ...

  7. 剑指Offer 通过中序和先序遍历重建二叉树

    题目描述 输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树.假设输入的前序遍历和中序遍历的结果中都不含重复的数字.例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7, ...

  8. C++ 二叉树深度优先遍历和广度优先遍历

    二叉树的创建代码==>C++ 创建和遍历二叉树 深度优先遍历:是沿着树的深度遍历树的节点,尽可能深的搜索树的分支. //深度优先遍历二叉树void depthFirstSearch(Tree r ...

  9. 【二叉树遍历模版】前序遍历&&中序遍历&&后序遍历&&层次遍历&&Root->Right->Left遍历

    [二叉树遍历模版]前序遍历     1.递归实现 test.cpp: 12345678910111213141516171819202122232425262728293031323334353637 ...

随机推荐

  1. js实现全屏弹框

    <!DOCTYPE html> <html> <head lang="en"> <meta charset="UTF-8&quo ...

  2. SpringBoot定时任务 - 集成quartz实现定时任务(单实例和分布式两种方式)

    最为常用定时任务框架是Quartz,并且Spring也集成了Quartz的框架,Quartz不仅支持单实例方式还支持分布式方式.本文主要介绍Quartz,基础的Quartz的集成案例本,以及实现基于数 ...

  3. lamp平台构建

    目录 lamp平台构建 安装httpd 安装mysql 安装php 配置apache 启用代理模块 配置虚拟主机 启用代理模块 验证 lamp平台构建 环境说明: 系统平台 IP 需要安装的服务 ce ...

  4. Python 懂车帝全车系销量排行榜

    本文所有教程及源码.软件仅为技术研究.不涉及计算机信息系统功能的删除.修改.增加.干扰,更不会影响计算机信息系统的正常运行.不得将代码用于非法用途,如侵立删! Python 懂车帝全车系销量排行榜 需 ...

  5. GDB技巧:使用终端界面模式

    欢迎来到 GreatSQL社区分享的MySQL技术文章,如有疑问或想学习的内容,可以在下方评论区留言,看到后会进行解答 GreatSQL社区原创内容未经授权不得随意使用,转载请联系小编并注明来源. 简 ...

  6. [CSharpTips]判断两条线段是否相交

    判断两条线段是否相交 主要用到了通过向量积的正负判断两个向量位置关系 向量a×向量b(×为向量叉乘),若结果小于0,表示向量b在向量a的顺时针方向:若结果大于0,表示向量b在向量a的逆时针方向:若等于 ...

  7. 一、JDK和JRE

    JDK和JRE JDK=JRE+开发工具包: JRE=JVM+核心类库 如果只是运行Java程序,安装JRE即可:开发Java程序并运行则需要安装JDK.目前最稳定版本是JDK8.0,并且马上部分企业 ...

  8. C#基础_VS常用快捷键

    Shift+End,Shift+Home,跳转到当前代码的行尾和行首. 1.窗口快捷键 记忆诀窍: 凡跟窗口挂上钩的快捷键必有一个W(Windows): Ctrl+W,W:  浏览器窗口 (浏览橱窗用 ...

  9. Linux驱动开发十六.input系统——3.系统自带的input驱动

    前面两章我们通过input子系统构建了一个按键类型的输入设备的驱动,其实Linux的内核还提供了一套基于GPIO的按键驱动程序,和LED设备一样,我们只需要在编译内核的过程中进行配置然后在设备树中定义 ...

  10. 04_Linux基础-.&..-cat-tac-重定向-EOF-Shell-more-ps-less-head-tail-sed-grep-which-whereis-PATH-bash-usr-locate-find

    04_Linux基础-.&..-cat-tac->&>>-EOF-Shell-more-ps-less-head-tail-sed-grep-which-wherei ...