美文网首页二叉树
二叉树的Morris遍历

二叉树的Morris遍历

作者: 铜炉 | 来源:发表于2021-01-06 21:07 被阅读0次

    前言

    前面已经进行过二叉树的递归遍历和迭代遍历,在前两种遍历中,二叉树遍历的空间复杂度都是O(n),这是因为不管是递归遍历还是迭代遍历,都用了栈空间来帮我们维护调用关系,方便我们在走到端点的时候可以回退。

    那么,换一个思路想想,我们可不可以不用栈,而是通过记录下节点要回退的位置,帮助我们直接退回到目标节点呢?这个回退的指针我们又该怎么维护呢?可以发现,二叉树的叶子节点的左右指针都为空,而我们需要回退的位置也是在叶子节点触发,那么我们考虑可以将叶子节点的指针指向目标节点,这样也就不需要额外的栈空间了。

    而事实上,Morris遍历之所以能将二叉树的空间复杂度降为O(1),也是这么做的。

    Morris遍历的实现原则

    1、如果cur无左孩子,cur向右移动(cur=cur.right)
    2、 如果cur有左孩子,找到cur左子树上最右的节点,记为mostright
    1)如果mostright的right指针指向空,让其指向cur,cur向左移动(cur=cur.left)
    2)如果mostright的right指针指向cur,让其指向空,cur向右移动(cur=cur.right)

    以上就是Morris遍历的规则,其中mostright的节点,即为节点在中序遍历中的前序节点。
    Morris遍历的本质就是会遍历两次节点,一个快指针,一个慢指针,慢指针到达一个节点的时候,快指针就走到当前节点的左子树的最右节点,将该节点的右指针指向慢指针的位置,然后慢指针再移动到下一步。如此往复,等回退的时候,就可以通过之前便利过程中建立的指针关系迅速回到目标节点位置。

    Morris前序遍历
    接下来,先看一下Morris前序遍历的实现。

    public List<Integer> preorderTraversal(TreeNode root) {
            List<Integer> res = new ArrayList<Integer>();
            if (root == null) {
                return res;
            }
            // 慢指针
            TreeNode cur = root;
            // 快指针,寻找cur的中序遍历的前序节点
            TreeNode mostright = null;
            
            while (cur != null) {
                // 快指针移动到左子树
                mostright = cur.left;
                
                if (mostright != null) {
                    // 左子树不为空,快指针移动到左子树的最右节点,即cur的中序遍历的前序节点
                    // 此处注意不能走过头,如果最右节点已经标记过,就停在最右节点处
                    while (mostright.right != null && mostright.right != cur) {
                        mostright = mostright.right;
                    }
                    
                    if (mostright.right == null) {
                        // 如果是最右节点还没有和当前节点建立关系,就建立关系,
                        // 因为是前序遍历,cur指针在移动之前都要记录
                        res.add(cur.val);
                        mostright.right = cur;
                        cur = cur.left;
                        continue;
                    } else {
                        // 逻辑到这里,说明本次遍历到的最右节点已经和当前节点建立了关系,本次是第二次遍历到cur所在的节点,所以把关系清除.
                        mostright.right = null;
                    }
                } else {
                    // 此时的mostright在左节点,如果左节点为空,cur不用向左走了,直接记录下来
                    res.add(cur.val);
                }
                // 到这里两种情况,左子树为空,且当前节点已经记录,往右走
                // cur指针到达了某个节点的左子树的最右节点,回到那个节点
                cur = cur.right;
            }
            return res;
    
        }
    

    Morris中序遍历

    在前序遍历的基础上,中序遍历需要改动一下记录cur节点的触发时机

    public List<Integer> inorderTraversal(TreeNode root) {
                List<Integer> res = new ArrayList<Integer>();
                if (root == null) {
                    return res;
                }
                // 慢指针
                TreeNode cur = root;
                // 快指针,寻找cur的中序遍历的前序节点
                TreeNode mostright = null;
    
                while (cur != null) {
                    // 快指针移动到左子树
                    mostright = cur.left;
    
                    if (mostright != null) {
                        // 左子树不为空,快指针移动到左子树的最右节点,即cur的中序遍历的前序节点
                        // 此处注意不能走过头,如果最右节点已经标记过,就停在最右节点处
                        while (mostright.right != null && mostright.right != cur) {
                            mostright = mostright.right;
                        }
    
                        if (mostright.right == null) {
                            // 如果是最右节点还没有和当前节点建立关系,就建立关系,
                            mostright.right = cur;
                            cur = cur.left;
                            continue;
                        } else {
                            // 中序遍历,左子树遍历完成,记录当前节点
                            res.add(cur.val);
                            // 逻辑到这里,说明本次遍历到的最右节点已经和当前节点建立了关系,本次是第二次遍历到cur所在的节点,左子树已经遍历完成,所以把关系清除.
                            mostright.right = null;
                        }
                    } else {
                        // 此时的mostright在左节点,如果左节点为空,cur不用向左走了,直接记录下来
                        res.add(cur.val);
                    }
                    // 到这里两种情况,左子树为空,且当前节点已经记录,往右走
                    // cur指针到达了某个节点的左子树的最右节点,回到那个节点
                    cur = cur.right;
                }
                return res;
        }
    

    相关文章

      网友评论

        本文标题:二叉树的Morris遍历

        本文链接:https://www.haomeiwen.com/subject/fouooktx.html