• 面试中很值得聊的二叉树遍历方法——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

     1     public static void morris(TreeNode head){
     2         if(head == null) return;
     3         TreeNode cur = head;
     4         TreeNode mostRight = null;
     5         while(cur != null){//cur为空遍历停止【4】
     6             mostRight = cur.left;//是cur左子树上最右的节点
     7             if(mostRight != null){//有左子树【3】
     8                 while(mostRight.right != null && mostRight != cur){//mostRight!=cur不加就会绕环循环
     9                     mostRight = mostRight.right;//找最右节点【3】
    10                 }
    11                 if(mostRight.right == null){//第一次来到cur【3.1】
    12                     mostRight.right = cur;
    13                     cur = cur.left;
    14                     continue;//执行循环
    15                 }else {//mostRight.right = cur第二次来到cur【3.2】
    16                     mostRight.right = null;
    17                 }
    18             }
    19             cur = cur.right;//没有左子树【2】
    20 
    21         }
    22     }

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

    基于Morris的先中后序遍历

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

    先序遍历

     Morris:1242513637

     Morris先序:1245367

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

     1     public static void morrisPre(TreeNode head){
     2         if(head == null) return;
     3         TreeNode cur = head;
     4         TreeNode mostRight = null;
     5         while(cur != null){//有左子树
     6             mostRight = cur.left;
     7             if(mostRight != null){
     8                 while(mostRight.right != null && mostRight != cur){
     9                     mostRight = mostRight.right;
    10                 }
    11                 if(mostRight.right == null){//第一次来到左子树
    12                     System.out.println(cur.val);//打印
    13                     mostRight.right = cur;
    14                     cur = cur.left;
    15                     continue;
    16                 }else {
    17                     mostRight.right = null;
    18                 }
    19             }else{
    20                 System.out.println(cur.val);//没有左子树 只会遍历一次
    21             }
    22             cur = cur.right;
    23         }
    24     }

    中序遍历

     Morris:1242513637

     Morris中序:4251637

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

     1     public static void morrisIn(TreeNode head) {
     2         if(head == null) return;
     3         TreeNode cur = head;
     4         TreeNode mostRight = null;
     5         while(cur != null){
     6             mostRight = cur.left;
     7             if(mostRight != null){
     8                 while(mostRight.right != null && mostRight != cur){
     9                     mostRight = mostRight.right;
    10                 }
    11                 if(mostRight.right == null){
    12                     mostRight.right = cur;
    13                     cur = cur.left;
    14                     continue;
    15                 }else {
    16                     mostRight.right = null;
    17                 }
    18             }
    19             //没有左树跳过if直接打印,有左树第二次来到cur退出循环打印
    20             System.out.println(cur.val);
    21             cur = cur.right;
    22         }
    23     }

    后序遍历

     Morris:1242513637

     Morris先序:4526731

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

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

     1       public static TreeNode reverseEdge(TreeNode from) {
     2         TreeNode pre = null;
     3         TreeNode next = null;
     4         while(from != null){
     5             next = from.right;
     6             from.right = pre;
     7             pre = from;
     8             from = next;
     9         }
    10         return pre;
    11     }

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

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

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

     1     public static void morrisPost(TreeNode head){
     2         if(head == null) return;
     3         TreeNode cur = head;
     4         TreeNode mostRight = null;
     5         while(cur != null){
     6             mostRight = cur.left;
     7             if(mostRight != null){
     8                 while(mostRight.right != null && mostRight != cur){
     9                     mostRight = mostRight.right;
    10                 }
    11                 if(mostRight.right == null){
    12                     mostRight.right = cur;
    13                     cur = cur.left;
    14                     continue;
    15                 }else {//第二次达到 逆序打印左子树的右边界
    16                     mostRight.right = null;
    17                     printRightEdge(cur.left);
    18                 }
    19             }
    20             cur = cur.right;
    21         }
    22         //最后退出循环之后,单独打印整棵树的右边界
    23         printRightEdge(head);
    24     }

    Morris遍历的应用

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

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

     1     public static boolean isBST(TreeNode head){
     2         if(head == null) return true;
     3         TreeNode cur = head;
     4         TreeNode mostRight = null;
     5         int preValue = Integer.MIN_VALUE;//上一次得到的值
     6         while(cur != null){
     7             mostRight = cur.left;
     8             if(mostRight != null){
     9                 while(mostRight.right != null && mostRight != cur){
    10                     mostRight = mostRight.right;
    11                 }
    12                 if(mostRight.right == null){
    13                     mostRight.right = cur;
    14                     cur = cur.left;
    15                     continue;
    16                 }else {
    17                     mostRight.right = null;
    18                 }
    19             }
    20             //中序遍历的操作时机在这里 所以在这里进行判断
    21             if(cur.val <= preValue){//没有递增
    22                 return false;
    23             }
    24             preValue = cur.val;
    25             cur = cur.right;
    26         }
    27         return true;
    28     }

    总结

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

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

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

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

  • 相关阅读:
    ios中的XMPP简介
    iOS项目开发中的目录结构
    ios中怎么样点击背景退出键盘
    ios中怎么处理键盘挡住输入框
    ios中怎么样调节占位文字与字体大小在同一高度
    ios中怎么样设置drawRect方法中绘图的位置
    ios中用drawRect方法绘图的时候设置颜色
    字符串常见操作
    字典、列表、元组
    字符串查看及应用
  • 原文地址:https://www.cnblogs.com/xdcat/p/12971855.html
Copyright © 2020-2023  润新知