【搞定算法】Morris 遍历二叉树:前序、中序、后序

目  录:

1、Morris 遍历的基本概念

2、Morris 的前序遍历

3、Morris 的中序遍历

4、Morris 的后序遍历


其实之前的文章里已经记录了二叉树的递归/非递归遍历代码实现。但是由于 Morris 遍历可以实现最优的遍历方式,这无疑是在面试时遇到此问题非常加分的回答。

1、Morris 遍历的基本概念

Morris 遍历:时间复杂度 O(N)、额外空间复杂度 O(1),N 为二叉树的节点个数。

说明:和二叉树的遍历有关的最优解都是 Morris 遍历。

 分析:

Morris 遍历:对于有左子树的节点 current,会遍历到两次,否则只会遍历到一次。对于有左子树的节点,它会先让左子树的最右节点 mostRight 指向它,从而达到之后能从底层节点返回上层。

  • 如果 mostRight 的右指针为空,说明是第一次到达 current,然后会让它指向 current;
  • 如果 mostRight 的右指针指向 current ,说明这是第二次到达 current,current 的左子树已经遍历完了,该回到 current,开始遍历其右子树了。

具体步骤:

1、如果 current 无左子树,current 向右移动【遍历其右子树】【无左子树,current 只会经过一次】;

2、如果 current 有左子树,找到 current 左子树上最右的节点 mostRight:

2.1、若 mostRight 的右指针指向 null【说明这是第一次来到 current 】,让 mostRight 的右指针指向 current 【那么之后就可以通过该指针返回 current 了】,current 向左移动【遍历左子树】;

2.2、若 mostRight 的右指针指向的是 current 【说明这是第二次来到 current,current 的左子树已经遍历完了】,让 mostRight 的右指针指向空,current 向右移动。

说明:

传统的二叉树遍历要么是自己实现一个栈要么借助系统栈作为辅助空间,都是为了解决底层节点怎么回到最上层。Morris 遍历利用特殊节点的指针解决了这一个问题,因此比较省空间。

public class Morris {

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

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

    public static void morris(Node root){
        if(root == null){
            return;
        }
        Node current = root;    // 当前节点
        Node mostRight = null;  // 当前节点左子树的最右子节点
        while(current != null){
            mostRight = current.left;
            // current 有左子树时,就要进行Morris遍历
            if(mostRight != null){
                while(mostRight.right != null && mostRight.right != current){
                    // 找到左子树的最右子节点mostRight
                    mostRight = mostRight.right;
                }
                // mosrRight找到了
                if(mostRight.right == null){
                    // 第一次来到current
                    // 遍历完左子树,通过mostRight.right就可以返回到current了
                    mostRight.right = current;
                    // 再次返回到cur以后又会判断mostRight.right 就发现这是第二次到达cur
                    current = current.left;
                    continue;
                }else{
                    // 进到这里说明:mostRight一定指向current,第二次到达当前的current节点
                    mostRight.right = null;
                }
            }
            // 无左子树 或者左子树已经遍历完了就遍历右子树
            current = current.right;
        }
    }
}

2、Morris 的前序遍历

Morris 的前序遍历:不管有无左子树,都是第一次遍历到一个节点时就打印。

public class Morris {

    // Morris先序遍历:有无左子树都是第一次遍历到current时就打印
    public static void morrisPre(Node root){
        if(root == null){
            return;
        }
        Node current = root;
        Node mostRight = null;
        while(current != null){
            mostRight = current.left;
            // current 有左子树时,就要进行Morris遍历
            if(mostRight != null){
                while(mostRight.right != null && mostRight.right != current){
                    // 找到左子树的最右子节点mostRight
                    mostRight = mostRight.right;
                }
                // mosrRight找到了,说明是第一次来到cur
                if(mostRight.right == null){
                    System.out.print(current.value + " ");
                    mostRight.right = current;
                    current = current.left;
                    continue;
                }else{
                    mostRight.right = null;
                }
            }else{
                // 无左子树,第一次来到current
                System.out.print(current.value + " ");
            }
            // 无左子树 或者左子树已经遍历完了就遍历右子树
            current = current.right;
        }
    }
}

3、Morris 的中序遍历

Morris 的中序遍历:对于有左子树能够遍历两次的节点在第二次遍历到的时候打印,无左子树只能遍历一次的节点在第一次遍历到的时候就打印。

public class Morris {

    // Morris中序遍历:没有左子树的节点直接打印,有左子树的节点,第二次遍历到打印
    public static void morrisIn(Node root){
        if(root == null){
            return;
        }
        Node current = root;
        Node mostRight = null;
        while(current != null){
            mostRight = current.left;
            // current 有左子树时,就要进行Morris遍历
            if(mostRight != null){
                while(mostRight.right != null && mostRight.right != current){
                    // 找到左子树的最右子节点mostRight
                    mostRight = mostRight.right;
                }
                // mosrRight找到了,说明是第一次来到cur
                if(mostRight.right == null){
                    mostRight.right = current;
                    current = current.left;
                    continue;
                }else{
                    System.out.print(current.value + " ");
                    mostRight.right = null;
                }
            }else{
                // 无左子树,第一次来到current
                System.out.print(current.value + " ");
            }
            // 无左子树 或者左子树已经遍历完了就遍历右子树
            current = current.right;
        }
    }
}

4、Morris 的后序遍历

后续遍历只关注有左子树能够遍历两次的节点:

1、第二次遍历到的时候,逆序打印当前节点左子树的右边界;

2、遍历全部完成后,最后逆序打印整棵树的右边界。

public class Morris {

    // Morris后续遍历:只关注有左子树的节点,第二次遍历到的时候逆序打印左子树的右边界,最后再单独打印整棵树的右边界
    public static void morrisPost(Node root){
        if(root == null){
            return;
        }
        Node current = root;
        Node mostRight = null;
        while(current != null){
            mostRight = current.left;
            // current 有左子树时,就要进行Morris遍历
            if(mostRight != null){
                while(mostRight.right != null && mostRight.right != current){
                    // 找到左子树的最右子节点mostRight
                    mostRight = mostRight.right;
                }
                // mosrRight找到了,说明是第一次来到cur
                if(mostRight.right == null){
                    mostRight.right = current;
                    current = current.left;
                    continue;
                }else{
                    mostRight.right = null;
                    // 第二次到达该节点,就逆序打印其左子树的右边界
                    printEdge(current.left);
                }
            }
            // 无左子树 或者左子树已经遍历完了就遍历右子树
            current = current.right;
        }
        // 打印整棵树的右边界
        printEdge(root);
    }

    // 逆序打印node节点的右边界,单链表反转的形式实现逆序
    public static void printEdge(Node node){
        Node tail = reverseEdge(node);
        Node current = tail;  // 记录下,用于后面的反转
        while(current != null){
            System.out.print(current.value + " ");
            current = current.right;
        }
        // 打印完成,还需要将树还原,再反转一次
        reverseEdge(tail);
    }

    public static Node reverseEdge(Node node){
        Node pre = null;
        Node next = null;
        while(node != null){
            next = node.right;
            node.right = pre;
            pre = node;
            node = next;
        }
        return pre;
    }
}

 

你可能感兴趣的:(左神算法,手撕代码,数据结构与算法,Morris,前序,中序,后序)