Morris遍历

对于二叉树的遍历,最简单的就是使用递归的方式去实现,容易理解,也容易实现。时间和空间都是O(n)。如果是利用栈去实现,也是同样的复杂度。那么能不能降低空间复杂度呢。当然可以,就是Morris。以下是我自己的理解(建议自己造一棵二叉树,跟着代码跑一遍):


在遍历过程中,我觉得最重要的就是当我们遍历完一个节点的左子树之后,能够回到当前节点,继而继续去遍历右子树。因此我们需要关心的就是如何回到当前节点。

和用栈实现的遍历方式相比, morris需要遍历两遍。
记当前节点为cur,当我们遍历到cur的前驱节点(pre)的时候,也就是意味着我们对cur的左子树遍历完成。为了能在这个时候回到cur节点。当我们第一次处理的cur的时候,就去找到它的前驱节点pre,也就是cur左子树的最右边的一个节点。找到之后,把pre的右节点(初始一定为null)指向cur节点。
最后当我们处理完成之后,还需要把这个pre到cur的连接给切断,所以需要两遍。

  • 先序遍历
    1 判断当前节cur点是否有左节点,没有就访问当前节点的值并进入右节点
    2 如果存在左节点,那么先找到它的前驱节点pre。
    3 如果pre还没有和cur建立连接,那么就建立连接,并且访问cur的值,然后处理cur的左节点
    4 如果pre已经和cur建立连接了。那么说明这是已经处理完成cur的左子树,可以断开连接,并处理cur的右节点了。
//先序遍历
    public List preorderTraversal(TreeNode root) {
        ArrayList<Integer> result = new ArrayList<>();
        TreeNode cur = root;

        while (cur != null) {
            if (cur.left == null) {
                result.add(cur.val);
                cur = cur.right;
            } else {
                /* 查找前驱 */
                TreeNode node = cur.left;
                while (node.right != null && node.right != cur)
                    node = node.right;

                if (node.right == null) { /* 还没建立连接,则建立连接 */
                    result.add(cur.val); 
                    node.right = cur;
                    cur = cur.left;
                } else {    /* 已经建立连接,需要删除 */
                    node.right = null;
                    cur = cur.right;
                }
            }
        }
        return result;
    }
  • 中序遍历
    和先序遍历差不多,都是先出力左子树,右子树,只是访问值的时候不一样

先序遍历是第在建立连接的时候访问节点值。
中序遍历是最后断开连接的时候访问,因为这个时候说明左子树已经遍历完成。

 //中序遍历
    public List inorderTraversal(TreeNode root) {
        ArrayList result = new ArrayList<>();
        TreeNode cur = root;

        while (cur != null) {
            if (cur.left == null) {
                result.add(cur.val);
                prev = cur;
                cur = cur.right;
            } else {
                /* 查找前驱 */
                TreeNode node = cur.left;
                while (node.right != null && node.right != cur)
                    node = node.right;

                if (node.right == null) { 
                    node.right = cur;
                    cur = cur.left;
                } else {    
                    result.add(cur.val);
                    node.right = null;
                    cur = cur.right;
                }
            }
        }
        return result;
    }
  • 后序遍历
    这个相比之下比较麻烦,需要我们记录上次访问过的节点值,
    代码和之前差不多,就是访问值得方式改变了。每次都是最后断开连接的时候才访问值,为了保留cur到最后访问,我们这次只访问left到pre的节点。每次访问的值都是如下形式。为了最后能访问到根节点,所以再新键一个虚拟的根节点左指针指向真实的根节点,
    还是需要自己用数据跟着程序走会更清楚一点。
    Morris遍历_第1张图片
 //后序遍历
    public List postorderTraversal(TreeNode root) {
        ArrayList result = new ArrayList<>();
        TreeNode dummy = new TreeNode(-1);
        dummy.left = root;
        TreeNode cur = dummy;
        TreeNode prev = null;

        while (cur != null) {
            if (cur.left == null) {
                prev = cur; /* 必须要有 */
                cur = cur.right;
            } else {
                TreeNode node = cur.left;
                while (node.right != null && node.right != cur)
                    node = node.right;

                if (node.right == null) { 
                    node.right = cur;
                    prev = cur; /* 必须要有 */
                    cur = cur.left;
                } else {
                    visit_reverse(cur.left, prev, result);
                    prev.right = null;
                    prev = cur; /* 必须要有 */
                    cur = cur.right;
                }
            }
        }
        return result;
    }
    // 逆转路径
    private static void reverse(TreeNode from, TreeNode to) {
        TreeNode x = from;
        TreeNode y = from.right;
        TreeNode z = null;
        if (from == to) return;

        while (x != to) {
            z = y.right;
            y.right = x;
            x = y;
            y = z;
        }
    }

    // 访问逆转后的路径上的所有结点
    private static void visit_reverse(TreeNode from, TreeNode to,
                                      List result) {
        TreeNode p = to;
        reverse(from, to);

        while (true) {
            result.add(p.val);
            if (p == from)
                break;
            p = p.right;
        }

        reverse(to, from);
    }

你可能感兴趣的:(morris遍历,数据结构)