二叉树之Morris遍历

二叉树之Morris遍历

  • 递归理解以及morris遍历
  • morris遍历改成前序遍历
  • morris遍历改成中序遍历
  • morris遍历改成后续遍历
  • 完整测试代码

递归理解以及morris遍历

    static void rec(Node head) { 
        if (head == null) return;
        System.out.print(head.value + " "); //这里打印就是前序遍历
        rec(head.left);
        System.out.print(head.value + " "); //这里打印就是中序遍历
        rec(head.right);
        System.out.print(head.value + " "); //这里打印就是后续遍历
    }

这是我们用递归写的三种遍历方法,如果是下面的例子,上面程序输出如下:
这里写图片描述
程序输出:

1 2 4 8 8 8 4 4 2 5 5 9 9 9 5 2 1 3 6 10 10 10 6 6 3 7 11 11 11 7 7 3 1
  • 可以看到,每一个结点都会被访问三次,也就是说,访问的过程中,每一个结点都会经过三次;
  • 一个结点如果没有parent指针或者我们在非递归遍历中使用栈,是不能回到它的父节点的;
  • morris就是借助叶子结点的空闲指针帮助我们想办法回到父亲结点,省去了递归的空间,或者栈的空间,达到O(1)空间;

每个结点来到三次:
在这里插入图片描述
下面介绍morris遍历规则(现在和前、中、后序无关,就是morris序):

这里写图片描述

流程看下图的举例:
这里写图片描述

这里写图片描述

按照上面的解释可以完全写出下面的morris遍历过程(没有打印):

    static void morris(Node head){
        if(head == null)return;
        Node cur = head;
        Node mostRight = null;//左子树上最右的结点

        while(cur != null){
            mostRight = cur.left;   //cur的第一个左结点
            if(mostRight != null){  //如果左子树不为空

                while(mostRight.right != null && mostRight.right != cur){ //找到最右边的结点 有两种情况
                    mostRight = mostRight.right;
                }

                if(mostRight.right == null){ //第一次来到这个结点  满足第二大条中的第一条
                    mostRight.right = cur;
                    cur = cur.left;
                    continue; //  cur = cur.left 直接结束所有的
                }else { //第二次来到这个结点         满足第二大条中的第二条
                    mostRight.right = null;
                }
            }
            cur = cur.right;  //包括两种情况的 左子树为空和第二次来到这个结点
        }
    }

morris遍历改成前序遍历

需要注意几点:

  • 首先可以肯定是第一次来到这个结点的时候打印,所以是当mostRight.right == null(第一次来的时候,打印当前的cur);
  • 其次,如果一个结点没有左子树,相当于只会来到结点一次,就直接打印一遍就可以了;
  • 其实这样的做法等同于在递归的时候把打印的行为放在第一次来到这个结点的时候;
    //先序遍历
    static void morrisPre(Node head){
        if(head == null)return;
        Node cur = head;
        Node mostRight = null;

        while(cur != null){
            mostRight = cur.left;
            if(mostRight != null){

                while(mostRight.right != null && mostRight.right != cur){
                    mostRight = mostRight.right;
                }

                if(mostRight.right == null){
                    mostRight.right = cur;
                    System.out.print(cur.value + " ");   //先序是第一次来到这个结点的时候打印
                    cur = cur.left;
                    continue;
                }else {
                    mostRight.right = null;
                }
            }else {    //如果一个结点没有左子树  相当于只会来到这个结点一次  直接打印,然后往右走
                System.out.print(cur.value + " ");
            }
            cur = cur.right;
        }
        System.out.println();
    }

morris遍历改成中序遍历

中序更加简单:

  • 如果一个结点有左子树,那么我打印的时机是我遍历完左子树之后的打印,也就是第二次来到这个结点时候打印,也就是mostRight.right = cur的时候打印;
  • 如果一个结点没有左子树,那么本来也要打印一下,打印完就往右边窜了;
  • 本质就是不论怎么样要处理玩左子树才打印根结点;
    //中序遍历
    static void morrisIn(Node head){
        if(head == null)return;
        Node cur = head;
        Node mostRight = null;

        while(cur != null){
            mostRight = cur.left;
            if(mostRight != null){

                while(mostRight.right != null && mostRight.right != cur){
                    mostRight = mostRight.right;
                }

                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();
    }

morris遍历改成后续遍历

后续遍历比较麻烦: 因为morris只会来到一个结点两次,但是递归会来到一个结点三次:

主要看以下几点:

  • 我们只关心那些有左子树,也就是会到一个结点两次的结点;
  • 逆序打印有左子树结点的左子树的右边界;
  • 最后打印整棵树的右边界的逆序;

这里写图片描述

    //morris后续
    static void morrisPos(Node head){
        if(head == null)return;
        Node cur = head;
        Node mostRight = null;

        while(cur != null){
            mostRight = cur.left;
            if(mostRight != null){

                while(mostRight.right != null && mostRight.right != cur){
                    mostRight = mostRight.right;
                }

                if(mostRight.right == null){
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                }else { //第二次来的时候
                    mostRight.right = null;
                    printEdge(cur.left); //打印左子树的右边界
                }
            }
            cur = cur.right;
        }
        printEdge(head); //最后打印整棵树的右边界
        System.out.println();
    }

    //打印边界
    static void printEdge(Node head) {
        //先逆序边界
        Node tail = reverseEdge(head);
        //打印
        Node cur = tail;
        while(cur != null){
            System.out.print(cur.value + " ");
            cur = cur.right;
        }
        //再逆序回来
        reverseEdge(tail);
    }

    //有点类似链表的逆序
    static Node reverseEdge(Node cur) {
        Node pre = null;
        Node next = null;
        while(cur != null){
            next = cur.right;//先保存下一个
            cur.right = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }

完整测试代码(例子就是上面的例子):

public class Morris {

    static class Node {
        public int value;
        public Node left;
        public Node right;

        public Node(int value) {
            this.value = value;
        }
    }


    static Node createTree(int[] arr, int i) {
        if (i >= arr.length || arr[i] == -1) return null;
        Node head = new Node(arr[i]);
        head.left = createTree(arr, 2 * i + 1);
        head.right = createTree(arr, 2 * i + 2);
        return head;
    }


    static void process(Node head) {
        if (head == null) return;
        System.out.print(head.value + " ");
        process(head.left);
        System.out.print(head.value + " ");
        process(head.right);
        System.out.print(head.value + " ");
    }


    static void pre(Node head) {
        if (head == null) return;
        System.out.print(head.value + " ");
        pre(head.left);
        pre(head.right);
    }

    static void in(Node head) {
        if (head == null) return;
        in(head.left);
        System.out.print(head.value + " ");
        in(head.right);
    }

    static void pos(Node head) {
        if (head == null) return;
        pos(head.left);
        pos(head.right);
        System.out.print(head.value + " ");
    }


    static void morris(Node head) {
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;//左子树上最右的结点

        while (cur != null) {
            mostRight = cur.left;   //cur的第一个左结点
            if (mostRight != null) {  //如果左子树不为空

                while (mostRight.right != null && mostRight.right != cur) { //找到最右边的结点 有两种情况
                    mostRight = mostRight.right;
                }

                if (mostRight.right == null) { //第一次来到这个结点  满足第二大条中的第一条
                    mostRight.right = cur;
                    cur = cur.left;
                    continue; //  cur = cur.left 直接结束所有的
                } else { //第二次来到这个结点         满足第二大条中的第二条
                    mostRight.right = null;
                }
            }
            cur = cur.right;  //包括两种情况的 左子树为空和第二次来到这个结点
        }
    }


    //先序遍历
    static void morrisPre(Node head) {
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;

        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {

                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }

                if (mostRight.right == null) {
                    mostRight.right = cur;
                    System.out.print(cur.value + " ");   //先序是第一次来到这个结点的时候打印
                    cur = cur.left;
                    continue;
                } else {
                    mostRight.right = null;
                }
            } else {    //如果一个结点没有左子树  相当于只会来到这个结点一次  直接打印,然后往右走
                System.out.print(cur.value + " ");
            }
            cur = cur.right;
        }
        System.out.println();
    }

    //中序遍历
    static void morrisIn(Node head) {
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;

        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {

                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }

                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();
    }

    //morris后续
    static void morrisPos(Node head) {
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;

        while (cur != null) {
            mostRight = cur.left;
            if (mostRight != null) {

                while (mostRight.right != null && mostRight.right != cur) {
                    mostRight = mostRight.right;
                }

                if (mostRight.right == null) {
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else { //第二次来的时候
                    mostRight.right = null;
                    printEdge(cur.left); //打印左子树的右边界
                }
            }
            cur = cur.right;
        }
        printEdge(head); //最后打印整棵树的右边界
        System.out.println();
    }

    //打印边界
    static void printEdge(Node head) {
        //先逆序边界
        Node tail = reverseEdge(head);
        //打印
        Node cur = tail;
        while (cur != null) {
            System.out.print(cur.value + " ");
            cur = cur.right;
        }
        //再逆序回来
        reverseEdge(tail);
    }

    //有点类似链表的逆序
    static Node reverseEdge(Node cur) {
        Node pre = null;
        Node next = null;
        while (cur != null) {
            next = cur.right;//先保存下一个
            cur.right = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }


    public static void main(String[] args) {
        int[] arr = {1, 2, 3, 4, 5, 6, 7, 8, -1, -1, 9, 10, -1, 11, -1};
        Node head = createTree(arr, 0);
        System.out.println("------------递归树每个结点回到三次-----------");
        process(head);
        System.out.println();
        System.out.println();

        System.out.println("---------------------前序----------------");
        pre(head);
        System.out.println();
        morrisPre(head);
        System.out.println();


        System.out.println("---------------------中序----------------");
        in(head);
        System.out.println();
        morrisIn(head);
        System.out.println();


        System.out.println("---------------------后序----------------");
        pos(head);
        System.out.println();
        morrisPos(head);
        System.out.println();

    }
}

测试结果:
这里写图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值