17_morris

About 4 minstudyalgorithm

一、介绍

一种遍历二叉树的方式,并且时间复杂度O(N),额外空间复杂度O(1) 通过利用原树中大量空闲指针的方式,达到节省空间的目的

二、遍历细节

假设来到当前节点cur,开始时cur来到头节点位置

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

2)如果cur有左孩子,找到左子树上最右的节点mostRight:

  • 如果mostRight的右指针指向空,让其指向cur, 然后cur向左移动(cur = cur.left)

  • 如果mostRight的右指针指向cur,让其指向null, 然后cur向右移动(cur = cur.right)

3)cur为空时遍历停止

三、代码

    // 省空间的二叉树遍历时间负责度O(n),空间复杂度O(1)
    public static void morris(Node node) {
        if(node == null) {
            return;
        }

        Node cur = node, mostRight = null;
        // 遍历头节点,如果头节点为空则结束遍历
        while (cur != null) {
            mostRight = cur.left;
            if(mostRight != null) {
                // 右节点不为空并且右节点没有指向当前节点时(没有改过指针)则最右指针往右移动
                while (mostRight.right !=null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                // 如果右边等于空,则最右指向cur,cur往左移动
                if(mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }
                // 否则最右指向空
                else {
                    mostRight.right = null;
                }
            }
            // 没有左节点则往右移动
            cur = cur.right;
        }
    }	

四、先、中、后序遍历

1、先序

当前节点处理2次的节点,第一次处理时输出。 当前节点处理1次节点时直接输出就是先序遍历

        // morris先序遍历
    public static void morrisPre(Node node) {
        if(node == null) {
            return;
        }

        Node cur = node, mostRight = null;
        // 遍历头节点,如果头节点为空则结束遍历
        while (cur != null) {
            mostRight = cur.left;
            if(mostRight != null) {
                // 右节点不为空并且右节点没有指向当前节点时(没有改过指针)则最右指针往右移动
                while (mostRight.right !=null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                // 如果右边等于空,则最右指向cur,cur往左移动
                if(mostRight.right == null) {
                    // 有两次处理,第一次输出就是先序遍历
                    System.out.print(cur.value + " ");
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }
                // 否则最右指向空
                else {
                    mostRight.right = null;
                }
            } else {
                // 只有一次此处输出就是先序遍历
                System.out.print(cur.value + " ");
            }
            // 没有左节点则往右移动
            cur = cur.right;
        }
        System.out.println();
    }

2、中序

当前节点处理2次的节点,第二次处理时输出。 当前节点处理1次节点时直接输出就是先序遍历

// morris中序遍历
    public static void morrisIn(Node node) {
        if(node == null) {
            return;
        }

        Node cur = node, mostRight = null;
        // 遍历头节点,如果头节点为空则结束遍历
        while (cur != null) {
            mostRight = cur.left;
            if(mostRight != null) {
                // 右节点不为空并且右节点没有指向当前节点时(没有改过指针)则最右指针往右移动
                while (mostRight.right !=null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                // 如果右边等于空,则最右指向cur,cur往左移动
                if(mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }
                // 否则最右指向空
                else {
                    mostRight.right = null;
                }
            }
            // 两次和一次的情况都在此处输出就是中序遍历
            System.out.print(cur.value + " ");

            // 没有左节点则往右移动
            cur = cur.right;
        }
        System.out.println();
    }

3、后序

递归是在第3次处理时打印,但是morris只处理了两次。

则只处理两次的时机的节点a才进行后序输出,输出时逆序输出左树的右边界。跑完后单独打印整个树的右边界

	    // morris后序遍历
    public static void morrisPos(Node node) {
        if(node == null) {
            return;
        }

        Node cur = node, mostRight = null;
        // 遍历头节点,如果头节点为空则结束遍历
        while (cur != null) {
            mostRight = cur.left;
            if(mostRight != null) {
                // 右节点不为空并且右节点没有指向当前节点时(没有改过指针)则最右指针往右移动
                while (mostRight.right !=null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }
                // 如果右边等于空,则最右指向cur,cur往左移动
                if(mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }
                // 否则最右指向空
                else {
                    mostRight.right = null;
                    // 只有第二次输出是才打印左树的右边界
                    printEdge(cur.left);
                }
            }
            // 没有左节点则往右移动
            cur = cur.right;
        }
        // 最后打印整棵树的右边界
        printEdge(node);
    }

    // 打印右边界
    private static void printEdge(Node node) {
        Node tail = reverseEdge(node);
        Node cur = tail;
        while (cur != null) {
            System.out.print(cur.value + " ");
            cur = cur.right;
        }
        reverseEdge(tail);
    }

    // 逆序树
    public static Node reverseEdge(Node from) {
        Node pre = null;
        Node next = null;
        while (from != null) {
            next = from.right;
            from.right = pre;
            pre = from;
            from = next;
        }
        return pre;
    }

五、获取数最小深度

本题测试链接 : https://leetcode-cn.com/problems/minimum-depth-of-binary-tree/

    public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        public TreeNode(int x) {
            val = x;
        }
    }

    // 获取最小深度
    public static int minDepth(TreeNode head) {
        if(head == null) {
            return 0;
        }
        TreeNode cur = head;
        TreeNode mostRight = null;
        int curLevel = 0;
        int minHeight = Integer.MAX_VALUE;
        // 遍历头节点,如果头节点为空则结束遍历
        while (cur != null) {
            mostRight = cur.left;
            if(mostRight != null) {
                int rightBoardSize = 1;
                // 右节点不为空并且右节点没有指向当前节点时(没有改过指针)则最右指针往右移动
                while (mostRight.right != null && mostRight.right != cur) {
                    rightBoardSize++;
                    mostRight = mostRight.right;
                }
                // 第一次到达。 如果右边等于空,则最右指向cur,cur往左移动
                if(mostRight.right == null) {
                    curLevel++;
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }

                // 第二次到达
                else {
                    if(mostRight.left == null) {
                        minHeight = Math.min(minHeight, curLevel);
                    }
                    // 第二次到达时减去之前的右边界层数
                    curLevel -= rightBoardSize;
                    mostRight.right = null;
                }
            } else {
                // 只有一次达到
                curLevel++;
            }
            cur = cur.right;
        }

        // 单独遍历左树的最右节点,计算右深度
        int finalRight = 1;
        cur = head;
        while (cur.right != null) {
            finalRight++;
            cur = cur.right;
        }

        // 获取最小的高度
        if(cur.left == null && cur.right == null) {
            minHeight = Math.min(minHeight, finalRight);
        }
        return minHeight;
    }
Last update:
Contributors: gaoqisen