二叉树——Morris遍历:同等时间复杂度下将空间复杂度将收敛到O(1)

morris遍历,笔试的时候就不要用了,以试题最快accept为准,因此绝大多数用在面试中

  1. 比如现在来了一道 给你一个二叉树的头节点,判断这棵树是不是搜索二叉树

    1. 一万个人都会说递归方法怎么做或者非递归方法怎么做,虽然递归的方法和Morris的时间复杂度都是O(N),但是不同的是空间复杂度morris会收敛到O(1)

    2. 假如你在面试官前将morris讲清楚,中标几率不就大幅提升了不是,手动狗头!

    3. 来个插曲:KMP中的morris和二叉树的这个morris是同一个人,算是我的偶像吧哈哈哈。

  2. 要掌握morris,必须要直到morris的规则(重中之重:也是改前中后序的基础

    1. 如果没有左树,当前cur节点直接跳到当前节点的右树

    2. 如果当前有左子树,找到当前左子树的最右节点

      1. 如果最右节点的右子树为null,将最右节点的右子树指向当前节点,当前节点跳到当前节点的左子节点

      2. 如果当前节点的左子树的最右节点 = 当前节点,将当前节点的左子树的最右节点的右节点变为null。然后当前节点 = 当前节点的右节点。

      3. 得出结论:

        1.  

  3.  根据morris规则以及上面的二叉树图得出morris序
    1.         1,2,4,2,5,1,3,6,3,7
  4. 所谓的前中后序遍历也就是打印的时机不一样
    1. 前序
      1. 前序的打印时机为cur第一次来到当前节点的时候:
        1. 如何判断是否是第一次还是第二次来到当前节点极为关键:
          1. 根据上述规则得知,第二次来到当前节点,当前节点的左子节点的最右节点的右节点 = 当前节点
    2. 中序
      1. 中序的打印时机为两个
        1. 当前节点没有左子节点:打印
        2. 第二次来到当前节点:打印
          1. 第一次和第二次怎么判断请看前序的方式
    3. 后序
      1. 后序相对来说还是略显麻烦哈哈哈,没关系,淦
        1. 根据第二次来到当前节点的左子树的右节点会将当前树进行划分,如下图:最后发现剩下当前树的右边界
        2. 得出打印机制:
          1. 第二次来到当前节点:逆序打印当前节点左子树的右边界
            1. 关于如何逆序值得深思,不能开辟额外空间,必须保持空间复杂度为big o  1
            2. 逆序打印这块用的是反转链表:我所用的知识点为快慢指针,打印完毕后再将链表反转,保证树的结构不会被破坏
  5. 看代码
  6. package LiKou.Morris;
    
    class TreeNode {
        int val;
        TreeNode left;
        TreeNode right;
    
        TreeNode(int x) {
            val = x;
        }
    }
    
    public class MyMorrisTest {
        public static void main(String[] args) {
            TreeNode root = new TreeNode(1);
            root.left = new TreeNode(2);
            root.left.left = new TreeNode(4);
            root.left.right = new TreeNode(5);
    
            root.right = new TreeNode(3);
            root.right.left = new TreeNode(6);
            root.right.right = new TreeNode(7);
    
            //调用morris方法开始遍历
            //morris(root);
            //morrisMid(root);
            //morrisPost(root);
            System.out.println(isBST(root));//中序遍历的各个值呈递增状态。
        }
    
        /**
         * morris的大致流程
         * @param root
         */
        private static void morris(TreeNode root) {
            if (root == null) { //base case
                return;
            }
            //当前节点
            TreeNode cur = root;
            //最右节点
            TreeNode mostRight = null;
    
    
            while (cur != null) { //当前节点为null终止循环
                if (cur.left != null) {//如果有左儿子,找到左儿子的最右儿子
                    mostRight = cur.left;
                    while (mostRight.right != null && mostRight.right != cur) { //第二条规则的第二点
                        mostRight = mostRight.right; //一直往右,直到找到最右节点
                    }
    
                    if (mostRight.right == null) {//知道mostRight.right ==null; 根据第二个规则的第二点 mostRight.right = 当前节点
                        mostRight.right = cur;
                        cur = cur.left;
                        continue;
                    } else if (mostRight.right == cur) {//mostRight.right == cur ,直接转为cur的右子节点
                        mostRight.right = null;
                        cur = cur.right;
                        continue;
                    }
                } else {
                    //没有左儿子,就往右儿子上跑
                    cur = cur.right;
                }
            }
        }
    
    
        /**
         * morris进行前序打印
         * @param root
         */
        private static void morrisPre(TreeNode root) {
            if (root == null) { //base case
                return;
            }
            //当前节点
            TreeNode cur = root;
            //最右节点
            TreeNode mostRight = null;
    
    
            System.out.println(cur.val);
            while (cur != null) { //当前节点为null终止循环
                if (cur.left != null) {//如果有左儿子,找到左儿子的最右儿子
                    mostRight = cur.left;
                    while (mostRight.right != null && mostRight.right != cur) { //第二条规则的第二点
                        mostRight = mostRight.right; //一直往右,直到找到最右节点
                    }
    
                    if (mostRight.right == null) {//知道mostRight.right ==null; 根据第二个规则的第二点 mostRight.right = 当前节点
                        mostRight.right = cur;
                        cur = cur.left;
                        System.out.println(cur.val);
                        continue;
                    } else if (mostRight.right == cur) {//mostRight.right == cur ,直接转为cur的右子节点
                        mostRight.right = null;
                        cur = cur.right;
                        System.out.println(cur.val);
                        continue;
                    }
                } else {
                    //没有左儿子,就往右儿子上跑
                    cur = cur.right;
                }
            }
        }
    
        /**
         * 中序打印
         * @param root
         */
        private static void morrisMid(TreeNode root) {
            if (root == null) { //base case
                return;
            }
            //当前节点
            TreeNode cur = root;
            //最右节点
            TreeNode mostRight = null;
    
            while (cur != null) { //当前节点为null终止循环
                if (cur.left != null) {//如果有左儿子,找到左儿子的最右儿子
                    mostRight = cur.left;
                    while (mostRight.right != null && mostRight.right != cur) { //第二条规则的第二点
                        mostRight = mostRight.right; //一直往右,直到找到最右节点
                    }
    
                    if (mostRight.right == null) {//知道mostRight.right ==null; 根据第二个规则的第二点 mostRight.right = 当前节点
                        mostRight.right = cur;
                        cur = cur.left;
                        continue;
                    } else if (mostRight.right == cur) {//mostRight.right == cur ,直接转为cur的右子节点
    
                        System.out.println(cur.val);
                        mostRight.right = null;
    
                        cur = cur.right;
                        continue;
                    }
                } else {
                    //没有左儿子,就往右儿子上跑
                    System.out.println(cur.val);
    
                    cur = cur.right;
                }
            }
        }
    
        /**
         * 后序打印
         * @param root
         */
        private static void morrisPost(TreeNode root) {
            if (root == null) { //base case
                return;
            }
            //当前节点
            TreeNode cur = root;
            //最右节点
            TreeNode mostRight = null;
    
            while (cur != null) { //当前节点为null终止循环
                if (cur.left != null) {//如果有左儿子,找到左儿子的最右儿子
                    mostRight = cur.left;
                    while (mostRight.right != null && mostRight.right != cur) { //第二条规则的第二点
                        mostRight = mostRight.right; //一直往右,直到找到最右节点
                    }
    
                    if (mostRight.right == null) {//知道mostRight.right ==null; 根据第二个规则的第二点 mostRight.right = 当前节点
                        mostRight.right = cur;
                        cur = cur.left;
                        continue;
                    } else if (mostRight.right == cur) {//mostRight.right == cur ,直接转为cur的右子节点
                        //第二次来到有左树的节点,这时候打印当前节点左树的右边界
                        mostRight.right = null;
                        printlnLeftMostRightEdge(cur.left);
                        cur = cur.right;
                        continue;
                    }
                } else {
                    //没有左儿子,就往右儿子上跑
                    cur = cur.right;
                }
            }
    
            //最后打印当前树的右边界
            printlnLeftMostRightEdge(root);
        }
    
        /**
         * 功能:  打印当前节点的右边界,逆序后再逆序
         *
         * @param cur
         */
        private static void printlnLeftMostRightEdge(TreeNode cur) {
            //说明是叶子节点
            if (cur.right == null) {
                System.out.println(cur.val);
                return;
            }
    
            //不止叶子节点一个,先反转右边界节点之间的关系(反转链表),最后将其之间的关系再调过来
            TreeNode newCur = coverse(cur);
    
            //打印
            TreeNode p = newCur;
            while (p != null) {
                System.out.println(p.val);
                p = p.right;
            }
    
            //再反转关系
            coverse(newCur);
        }
    
        /**
         * 逆序方法
         * @param cur
         * @return
         */
        private static TreeNode coverse(TreeNode cur) {
            TreeNode temp = cur.right;
            cur.right = null;
            while (temp != null) {
                TreeNode right = temp.right;
                temp.right = cur;
                cur = temp;
                temp = right;
            }
            return cur;
        }
    
    
        /**
         * 用morris判断是否为BST
         * @param root
         * @return
         */
        private static boolean isBST(TreeNode root) {
            if (root == null) { //base case
                return true;
            }
            //当前节点
            TreeNode cur = root;
            //最右节点
            TreeNode mostRight = null;
    
            int curValue = Integer.MIN_VALUE;
    
            while (cur != null) { //当前节点为null终止循环
                if (cur.left != null) {//如果有左儿子,找到左儿子的最右儿子
                    mostRight = cur.left;
                    while (mostRight.right != null && mostRight.right != cur) { //第二条规则的第二点
                        mostRight = mostRight.right; //一直往右,直到找到最右节点
                    }
    
                    if (mostRight.right == null) {//知道mostRight.right ==null; 根据第二个规则的第二点 mostRight.right = 当前节点
                        mostRight.right = cur;
                        cur = cur.left;
                        continue;
                    } else if (mostRight.right == cur) {//mostRight.right == cur ,直接转为cur的右子节点
    
                        //需要判断是否没有初始化
                        if (cur.val > curValue) {
                            curValue = cur.val;
                        } else {
                            return false;
                        }
    
                        mostRight.right = null;
    
                        cur = cur.right;
                        continue;
                    }
                } else {
                    if (cur.val > curValue) {
                        curValue = cur.val;
                    } else {
                        return false;
                    }
                    cur = cur.right;
                }
            }
            return true;
        }
    
    
    }
    

     

    还是老话:不懂的兄弟DEBUG一下就差不多就明白了,你知道的越多,你不知道的就越多,准备回家啦,下期见!

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值