morris traversal-建好线索再行遍历 2020-09-25(未允禁转)

1.morris traversal

莫里斯遍历,是在O(n)时间复杂度和O(1)空间复杂度下实现的二叉树遍历,带有一定的线索二叉树思想
其根本思想是借助【线索】来替代一般递归写法产生的栈结构,把节点信息存在了【线索】中,从而避免了使用栈带来的空间开销。这也是莫里斯遍历和一般遍历写法【在本质上的唯一区别】

用一句话概括morris traversal就是,

建好线索再行遍历


2.莫里斯遍历剖析与模板总结

以前序遍历为例,对于一般的前序遍历递归写法,我们有

class Solution:
    def preorderTraversal(self, root: TreeNode):
        if not root:
            return 
        
        visit root
        self.preorderTraversal(root.left)
        self.preorderTraversal(root.right)

上面代码中,进入self.preorderTraversal(root.left)前,root会压入递归栈保存,等self.preorderTraversal(root.left)返回时root从递归栈弹出;然后在当前函数获取root.right,并执行self.preorderTraversal(root.right)

对于root(如下图结点1)而言,当它的左子树root.left被访问完毕后,也就是左子树root.left的最右端结点mostright(如下图结点4)被访问后,需要从mostright回到root,以顺利获得root.right。这个回到root的过程是通过递归栈不断出栈结点实现的,包括非递归的前序遍历写法也只是主动用栈模拟了这个过程而已

morris遍历

2.1 建立线索

如果我们能在4和1直接建立一条【线索】,如上图,不就避免了栈实现了吗

因此,对于当前结点root:

  • step1. 建立mostright->root线索。对于root 1,在访问左子树root.left 2之前,我们先找到root的前序结点 4并且建立线索4->1。这样我们就可以保证在访问完左子树root.left后能够顺着线索回到root。线索建立完后,才可以考虑访问root.left
  • step2. 访问左子树root.left。对于root.left的每个结点,重复step1。如果不存在左子树root.left或者从左子树的mostright回到root,那么就访问右子树root.right
  • step3. 访问右子树root.right。对于root.right的每个结点,重复step1 2

2.2 删除线索

但是,我们建好线索之后,就会形成环路,如果不及时删除线索,

  • 既会导致树结构的改变
  • 又会导致程序一直在环内跑产生死循环

因此,必须考虑及时删除不需要的线索,恢复树的正常结构。我们知道,程序从左子树的mostright回到root之后,mostright->root线索就完成了使命不再需要了,应该删除

综上所述,莫里斯遍历的整体思路是

  • step1. 建立mostright->root线索。对于root 1,在访问左子树root.left 2之前,我们先找到root的前序结点 4并且建立线索4->1。这样我们就可以保证在访问完左子树root.left后能够顺着线索回到root。线索建立完后,才可以考虑访问root.left
  • step2. 访问左子树root.left。对于root.left的每个结点,重复step1。如果不存在左子树root.left或者从左子树的mostright回到root,说明左子树访问完毕,那么就删除对应线索,回到root
  • step3. 访问右子树root.right。对于root.right的每个结点,重复step1 2

上面的整个过程和递归长得太像了,唯二不同就在于先建好当前结点root与其前序结点mostright的线索,再开始访问左子树和右子树,并且从左子树回到root时,删除对应的mostright->root线索。先建后删,不过如此

2.3 莫里斯遍历模板

核心!!!重点!!!从1到2.3弄懂后面水到渠成。看这多么优美的模板

def morrisTraversalModel(root) -> None:
        while root:
            # 1.root有左子树,就要建立mostright->root的线索
            if root.left:
                mostright = root.left
                while mostright.right and mostright.right != root:
                    mostright = mostright.right
                
                # mostright to root没有线索
                if not mostright.right:
                    # 建立线索
                    mostright.right = root
                    # 线索建好了访问左子树
                    root = root.left
                # 能够从mostright回到root,说明左子树访问完毕,要删除线索,然后访问右子树
                else:
                    mostright.right = None
                    root = root.right

            # 2.root没有左子树,那么访问右子树
            else:
                root = root.right

3.莫里斯前序遍历

基于2.3模板,控制一下访问结点的时机,很快就写出来了

class Solution:
    def preorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        
        while root:
            # 1.root有左子树,就要建立mostright->root的线索
            if root.left:
                mostright = root.left
                while mostright.right and mostright.right != root:
                    mostright = mostright.right
                
                # mostright to root没有线索
                if not mostright.right:
                    # 建立线索
                    mostright.right = root

                    ### 访问自己 ###
                    res.append(root.val)
                    # 线索建好了访问左子树
                    root = root.left
                # 能够从mostright回到root,要删除线索,然后访问右子树
                else:
                    mostright.right = None
                    root = root.right

            # 2.root没有左子树,那么访问右子树
            else:
                ### 访问自己 ###
                res.append(root.val)
                root = root.right
        return res

建线索总是第一步,这与访问结点的顺序无关,是独立的必须完成的第一步。建完后再考虑访问结点的时机,前序遍历就是在模板的基础上新增两处

### 访问自己 ###
res.append(root.val)

4.莫里斯中序遍历

class Solution:
    def inorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        
        while root:
            # 1.root有左子树,就要建立mostright->root的线索
            if root.left:
                mostright = root.left
                while mostright.right and mostright.right != root:
                    mostright = mostright.right
                
                # mostright to root没有线索
                if not mostright.right:
                    # 建立线索
                    mostright.right = root
                    # 线索建好了访问左子树
                    root = root.left
                # 能够从mostright回到root,要删除线索,然后访问右子树
                else:
                    mostright.right = None
                    ### 访问自己 ###
                    res.append(root.val)
                    root = root.right

            # 2.root没有左子树,那么访问右子树
            else:
                ### 访问自己 ###
                res.append(root.val)
                root = root.right
        return res

建线索总是第一步,这与访问结点的顺序无关,是独立的必须完成的第一步。建完后再考虑访问结点的时机,中序遍历也是在模板的基础上新增两处

### 访问自己 ###
res.append(root.val)
morris-inorder-traversal

5.莫里斯后续遍历

由于后序遍历的实际栈操作相对复杂,因此转换为中右左的倒置,而中右左又是反序前序遍历,如此得解

class Solution:
    def postorderTraversal(self, root: TreeNode) -> List[int]:
        res = []
        
        while root:
            # 1.root有右子树,就要建立mostleft->root的线索
            if root.right:
                mostleft = root.right
                while mostleft.left and mostleft.left != root:
                    mostleft = mostleft.left
                
                # mostleft to root没有线索
                if not mostleft.left:
                    # 建立线索
                    mostleft.left = root

                    ### 访问自己 ###
                    res.append(root.val)
                    # 线索建好了访问右子树
                    root = root.right
                # 能够从mostleft回到root,要删除线索,然后访问左子树
                else:
                    mostleft.left = None
                    root = root.left

            # 2.root没有右子树,那么访问左子树
            else:
                ### 访问自己 ###
                res.append(root.val)
                root = root.left
        return res[::-1]

你可能感兴趣的:(morris traversal-建好线索再行遍历 2020-09-25(未允禁转))