【左神算法笔记】二叉树的Morris遍历

Morris遍历是一种遍历二叉树的方式,时间复杂度O(N),空间复杂度O(1)

之前的遍历方法,不管是递归(系统帮我们压栈,空间消耗是树的高度)或自己创建栈,都需要额外的空间

思想:利用树中空闲指针,来节省空间

面试里可以聊聊,笔试就别写了,还是有点复杂的

遍历细节:

来到了cur节点,初始位置是根节点

1、cur无左孩子,cur=cur.right

2、cur有左孩子,找到左子树的最右节点mostRight

1)若mostRight右指针为null,则mostRight.right=cur,然后cur=cur.left

注:这一步实际上记录了回溯到子树根节点的路径

2)若mostRight右指针为cur,则mostRight.right=null,然后cur=cur.right

注:这一步实际上删除了前面记录的路径,且这棵子树已经遍历完成了

3、cur为null时,遍历结束

规律:一个节点有左树,则它一定会被遍历到2次,没有就只会遍历1次,可以根据这个节点左子树的最右节点指向谁,看是第几次遍历到它

在最简单递归遍历二叉树中,每个节点会被遍历三次:

public static void process(Node node) {
    
    
	if (node == null) {
    
    
		return;
	}
	// 1
	process(node.left);
	// 2
	process(node.right);
	// 3
}

但在morris遍历中,只有2次或1次

代码:

    public static void morris(Node head) {
    
    
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
    
    
            mostRight = cur.left; // mostRight是cur的左孩子
            if (mostRight != null) {
    
    
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
    
    
                    mostRight = mostRight.right;
                } // 找到了左子树的mostRight
                if (mostRight.right == null) {
    
     // 第一次来到cur
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
    
     // mostRight.right==cur
                    // 这是第二次来到cur
                    mostRight.right = null;
                }
            }
            cur = cur.right;
        }
    }

时间复杂度分析:

发现对于每个结点,需要遍历它左子树的右边界,且所有节点这样的遍历都是不重复的,所以总时间代价是O(N)

改造

在这里插入图片描述

morris遍历序为:1242513637

改造为先序遍历:
1、只遍历一次的节点,这一次的时候输出(或别的操作)
2、遍历两次的节点,在第一次遍历时输出

代码:

    public static void morris(Node head) {
    
    
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
    
    
            mostRight = cur.left; // mostRight是cur的左孩子
            if (mostRight != null) {
    
    
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
    
    
                    mostRight = mostRight.right;
                } // 找到了左子树的mostRight
                if (mostRight.right == null) {
    
     // 第一次来到cur
                    System.out.println(cur.val);
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
    
     // mostRight.right==cur
                    // 这是第二次来到cur
                    // 不打印
                    mostRight.right = null;
                }
            } else {
    
     // 没有左树
                System.out.println(cur.val);
            }
            cur = cur.right;
        }
    }

改造为中序遍历:
1、只遍历一次的节点,这一次的时候输出(或别的操作)
2、遍历两次的节点,在第2次遍历时输出

    public static void morris(Node head) {
    
    
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
    
    
            mostRight = cur.left; // mostRight是cur的左孩子
            if (mostRight != null) {
    
    
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
    
    
                    mostRight = mostRight.right;
                } // 找到了左子树的mostRight
                if (mostRight.right == null) {
    
     // 第一次来到cur
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
    
     // mostRight.right==cur
                    // 这是第二次来到cur
                    mostRight.right = null;
                }
            }
            System.out.println(cur.val); // 只加了这一行
            cur = cur.right;
        }
    }

改造为后序遍历:
1、不管只遍历1次的节点,对于能够遍历2次的节点,第二次遍历时,逆序打印它左子树的右边界
2、来到最后一个节点后,逆序打印整棵树的右边界

逆序打印怎么实现?
先把每个结点的右指针指向父节点,打印后,再把指针还原回来

public static void morrisPos(Node head) {
    
    
        if (head == null) return;
        Node cur = head;
        Node mostRight = null;
        while (cur != null) {
    
    
            mostRight = cur.left; // mostRight是cur的左孩子
            if (mostRight != null) {
    
    
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
    
    
                    mostRight = mostRight.right;
                } // 找到了左子树的mostRight
                if (mostRight.right == null) {
    
     // 第一次来到cur
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
    
     // mostRight.right==cur
                    // 这是第二次来到cur
                    mostRight.right = null;
                    printEdge(cur.left);
                }
            }
            cur = cur.right;
        }
        printEdge(head);
        System.out.println();
    }

    // 逆序打印以x为root的树的右边界
    public static void printEdge(Node x) {
    
    
        Node tail = reverseEdge(x);
        Node cur = tail;
        while (cur != null) {
    
    
            System.out.println(cur.val + " ");
            cur = cur.right;
        }
        reverseEdge(tail);
    }

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

morris遍历的应用

判断搜索二叉树

将中序遍历改造,维护一个变量preValue,在遍历过程中,当发现preValue有下降,就返回false,若遍历完毕都没有下降,就返回true

    public static boolean isBST(Node head) {
    
    
        if (head == null) return true;
        Node cur = head;
        Node mostRight = null;
        int preValue = Integer.MIN_VALUE;
        while (cur != null) {
    
    
            mostRight = cur.left; // mostRight是cur的左孩子
            if (mostRight != null) {
    
    
                // 有左子树
                while (mostRight.right != null && mostRight.right != cur) {
    
    
                    mostRight = mostRight.right;
                } // 找到了左子树的mostRight
                if (mostRight.right == null) {
    
     // 第一次来到cur
                    mostRight.right = cur;
                    cur = cur.left;
                    continue;
                } else {
    
     // mostRight.right==cur
                    // 这是第二次来到cur
                    mostRight.right = null;
                }
            }
            if (cur.val <= preValue) {
    
    
                return false;
            }
            preValue = cur.val;
            cur = cur.right;
        }
        return true;
    }

判断BST问题实际上是一个遍历问题,morris遍历省掉了额外的栈空间

总结:

当问题要求我们获得某节点左右子树的完整信息,就使用普通的递归遍历方法,否则就使用morris遍历,效率最高

猜你喜欢

转载自blog.csdn.net/weixin_48288539/article/details/125059238