Morries法遍历二叉树

参考 树(一)——Morris 二叉树神级遍历

为什么可以不采用辅助结构??

首先思考一下,为什么我们要用栈或者队列来辅助我们遍历?
因为二叉树的结构是,一个父节点可以轻松的找到子节点,但是子节点无法直接找到父节点。我们需要利用栈或者队列保存访问的记录,以便于我们可以回溯到父节点。
Morris 遍历采用多个指针,使得通过复杂度不高的操作可以找到父节点。

Morris 遍历

Morris 遍历是一种节省空间复杂度的方法。将叶子节点上的空指针利用起来,指向父节点,当再次遍历到这个节点的时候再修改回来,这样最后二叉树的结构也没有发生改变。

遍历规则

cur:当前遍历的指针
rightMost:cur节点的左子树的最右节点

  1. 如果cur左子树为空:cur=cur.right;

  2. 如果cur左子树不为空: 找到rightMost

    ------1)如果rightMost.right=null, 那么令rightMost=cur, cur=cur.left;
    ------2)否则,不为空则说明rightMost.right曾经被修改过,我们这是第二次来到这个点,修改rightMost.right=null,cur=cur.right;

解释一下上面的步骤:

  1. 首先整体向左走,如果没有左子树向右走。
  2. 如果左子树不为空,那么就找到左子树上最右的节点。这个节点的右子树一定是空的。把这个空指针指向当前节点,即保存了一个指向父节点的指针。此时,左子树还有值没有访问,cur向左走。
  3. 3中的结果是rightMost遍历到这个节点的,cur指针也会遍历到这个节点,这就是第二次访问到了,那么此时该节点作为rightMost时,是被修改过的,所以我们要重新修改其为null。
  4. 当出现3的情况时,说明该节点的左子树全部访问完毕,所以此时cur向右走。
  5. cur是真正有效的移动指针,它会走过所有的节点,rightMost是为了修改指针而存在的。
Morris遍历伪代码
    //morries遍历
    public static void morries(Node root){
        if(root==null){
            return;
        }
        Node cur = root;
        Node rightMost;
        // cur指针来遍历二叉树
        while (cur!=null){
            //左子树不为空
            if(cur.left!=null){
                // 向左走,目的是遍历完左子树
                rightMost = cur.left;
                // 找到rightMost
                while (rightMost.right!=null&&rightMost.right!=cur){
                    rightMost=rightMost.right;
                }
                // 没有被修改过,说明这个左子树没有遍历完,continue会使得最下面的cur向右移动执行不到,因为我们不希望想右走,左边子树还没有遍历完呢,继续向左走
                //第一次访问
                if(rightMost.right==null){
                    System.out.print(cur.data+" ");
                    rightMost.right=cur;
                    cur=cur.left;
                    continue;
                }else {
                    //修改过了,这是第二次到了该节点,我们需要将节点的指针修改回来
                    //第二次访问
                    System.out.print(cur.data+" ");
                    rightMost.right=null;
                }
            }else{
                //左子树为空
                System.out.print(cur.data+" ");
            }
            //左子树为空会向右走,
            // 如果左子树全部访问过了,也会向右走
            cur = cur.right;
        }
    }
基于Morris前序遍历

因为所有节点只有俩种情况:访问一次和访问两次
访问一次的节点即没有左子树的节点,那么遇到就打印。
访问两次的节点:有左子树,遇到第一次就打印,第二次不打印

    //morries 前序遍历
    //因为所有节点只有俩种情况:访问一次和访问两次
    //只访问一次的,即没有左子树的节点,直接打印
    //访问2次的,第一次访问直接打印,第二次访问不打印
    public static void morriesPreOrder(Node root){
        if(root==null){
            return;
        }
        Node cur = root;
        Node rightMost;
        while (cur!=null){
            //左子树不为空
            if(cur.left!=null){
                rightMost = cur.left;
                 找到cur的左子树的最右节点,因为我们要通过这个节点返回回来。
                while (rightMost.right!=null&&rightMost.right!=cur){
                    rightMost=rightMost.right;
                }
                //第一次访问
                if(rightMost.right==null){
                    System.out.print(cur.data+" ");
                    rightMost.right=cur;
                    cur=cur.left;
                    continue;
                }else {
                    //第二次访问
                    //System.out.print(cur.data+" ");
                    rightMost.right=null;
                }

            }else{
                //左子树为空
                System.out.print(cur.data+" ");
            }
            cur = cur.right;
        }
    }
基于Morris中序遍历

访问一次的节点即没有左子树的节点,那么遇到就打印。
访问两次的节点:有左子树,遇到第一次不打印,第二次打印

    //morries 中序遍历
    //只访问一次的,直接打印
    //访问2次的,第一次访问不打印,第二次访问直接打印
    public static void morriesInOrder(Node root){
        if(root==null){
            return;
        }
        Node cur = root;
        Node rightMost;
        while (cur!=null){
            //左子树不为空
            if(cur.left!=null){
                rightMost = cur.left;
                while (rightMost.right!=null&&rightMost.right!=cur){
                    rightMost=rightMost.right;
                }
                //第一次访问
                if(rightMost.right==null){
                    //System.out.print(cur.data+" ");
                    rightMost.right=cur;
                    cur=cur.left;
                    continue;
                }else {
                    //第二次访问
                    System.out.print(cur.data+" ");
                    rightMost.right=null;
                }

            }else{
                //左子树为空
                System.out.print(cur.data+" ");
            }
            cur = cur.right;
        }
    }
基于Morris后序遍历

后序相对比较复杂。
整体思路还是基于Morris遍历。
但是:对于被访问一次的节点,不做任何处理。
访问两次的节点,第二次访问时,逆序打印其左子树右边界。

    //morries 后序遍历
    //只访问一次的,不打印
    //访问2次的,第一次访问不打印,第二次访问调用函数  逆序打印其左子树右边界。
    public static void morriesPostOrder(Node root){
        if(root==null){
            return;
        }
        Node cur = root;
        Node rightMost;
        while (cur!=null){
            //左子树不为空
            if(cur.left!=null){
                rightMost = cur.left;
                while (rightMost.right!=null&&rightMost.right!=cur){
                    rightMost=rightMost.right;
                }
                //第一次访问
                if(rightMost.right==null){
                    //System.out.print(cur.data+" ");
                    rightMost.right=cur;
                    cur=cur.left;
                    continue;
                }else {
                    //第二次访问
                    //System.out.print(cur.data+" ");
                    rightMost.right=null;
                    function(cur.left);
                }

            }else{
                //左子树为空
                //System.out.print(cur.data+" ");
            }
            cur = cur.right;
        }
        function(root);
    }

    public static void function(Node head) {
        Node tail = traverse(head);
        Node cur = tail;
        while (cur != null) {
            System.out.print(cur.data +" ");
            cur = cur.right;
        }
        head = traverse(tail);
    }


    //反转链表
    public static Node traverse(Node head) {
        Node cur = head;
        Node pre = null;
        Node next;
        while (cur != null) {
            next = cur.right;
            cur.right = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值