Explain Morris inorder tree traversal without using stacks or recursion

前端 未结 8 1082
别跟我提以往
别跟我提以往 2020-11-27 09:15

Can someone please help me understand the following Morris inorder tree traversal algorithm without using stacks or recursion ? I was trying to understand how it works, but

相关标签:
8条回答
  • 2020-11-27 09:16

    The recursive in-order traversal is : (in-order(left)->key->in-order(right)). (this is similar to DFS)

    When we do the DFS, we need to know where to backtrack to (that's why we normally keep a stack).

    As we go through a parent node to which we will need to backtrack to -> we find the node which we will need to backtrack from and update its link to the parent node.

    When we backtrack? When we cannot go further. When we cannot go further? When no left child's present.

    Where we backtrack to? Notice: to SUCCESSOR!

    So, as we follow nodes along left-child path, set the predecessor at each step to point to the current node. This way, the predecessors will have links to successors (a link for backtracking).

    We follow left while we can until we need to backtrack. When we need to backtrack, we print the current node and follow the right link to the successor.

    If we have just backtracked -> we need to follow the right child (we are done with left child).

    How to tell whether we have just backtracked? Get the predecessor of the current node and check if it has a right link (to this node). If it has - than we followed it. remove the link to restore the tree.

    If there was no left link => we did not backtrack and should proceed following left children.

    Here's my Java code (Sorry, it is not C++)

    public static <T> List<T> traverse(Node<T> bstRoot) {
        Node<T> current = bstRoot;
        List<T> result = new ArrayList<>();
        Node<T> prev = null;
        while (current != null) {
            // 1. we backtracked here. follow the right link as we are done with left sub-tree (we do left, then right)
            if (weBacktrackedTo(current)) {
                assert prev != null;
                // 1.1 clean the backtracking link we created before
                prev.right = null;
                // 1.2 output this node's key (we backtrack from left -> we are finished with left sub-tree. we need to print this node and go to right sub-tree: inOrder(left)->key->inOrder(right)
                result.add(current.key);
                // 1.15 move to the right sub-tree (as we are done with left sub-tree).
                prev = current;
                current = current.right;
            }
            // 2. we are still tracking -> going deep in the left
            else {
                // 15. reached sink (the leftmost element in current subtree) and need to backtrack
                if (needToBacktrack(current)) {
                    // 15.1 return the leftmost element as it's the current min
                    result.add(current.key);
                    // 15.2 backtrack:
                    prev = current;
                    current = current.right;
                }
                // 4. can go deeper -> go as deep as we can (this is like dfs!)
                else {
                    // 4.1 set backtracking link for future use (this is one of parents)
                    setBacktrackLinkTo(current);
                    // 4.2 go deeper
                    prev = current;
                    current = current.left;
                }
            }
        }
        return result;
    }
    
    private static <T> void setBacktrackLinkTo(Node<T> current) {
        Node<T> predecessor = getPredecessor(current);
        if (predecessor == null) return;
        predecessor.right = current;
    }
    
    private static boolean needToBacktrack(Node current) {
        return current.left == null;
    }
    
    private static <T> boolean weBacktrackedTo(Node<T> current) {
        Node<T> predecessor = getPredecessor(current);
        if (predecessor == null) return false;
        return predecessor.right == current;
    }
    
    private static <T> Node<T> getPredecessor(Node<T> current) {
        // predecessor of current is the rightmost element in left sub-tree
        Node<T> result = current.left;
        if (result == null) return null;
        while(result.right != null
                // this check is for the case when we have already found the predecessor and set the successor of it to point to current (through right link)
                && result.right != current) {
            result = result.right;
        }
        return result;
    }
    
    0 讨论(0)
  • 2020-11-27 09:17

    Python Solution Time Complexity : O(n) Space Complexity : O(1)

    Excellent Morris Inorder Traversal Explanation

    class Solution(object):
    def inorderTraversal(self, current):
        soln = []
        while(current is not None):    #This Means we have reached Right Most Node i.e end of LDR traversal
    
            if(current.left is not None):  #If Left Exists traverse Left First
                pre = current.left   #Goal is to find the node which will be just before the current node i.e predecessor of current node, let's say current is D in LDR goal is to find L here
                while(pre.right is not None and pre.right != current ): #Find predecesor here
                    pre = pre.right
                if(pre.right is None):  #In this case predecessor is found , now link this predecessor to current so that there is a path and current is not lost
                    pre.right = current
                    current = current.left
                else:                   #This means we have traverse all nodes left to current so in LDR traversal of L is done
                    soln.append(current.val) 
                    pre.right = None       #Remove the link tree restored to original here 
                    current = current.right
            else:               #In LDR  LD traversal is done move to R  
                soln.append(current.val)
                current = current.right
    
        return soln
    
    0 讨论(0)
  • 2020-11-27 09:22

    I hope the pseudo-code below is more revealing:

    node = root
    while node != null
        if node.left == null
            visit the node
            node = node.right
        else
            let pred_node be the inorder predecessor of node
            if pred_node.right == null /* create threading in the binary tree */
                pred_node.right = node
                node = node.left
            else         /* remove threading from the binary tree */
                pred_node.right = null 
                visit the node
                node = node.right
    

    Referring to the C++ code in the question, the inner while loop finds the in-order predecessor of the current node. In a standard binary tree, the right child of the predecessor must be null, while in the threaded version the right child must point to the current node. If the right child is null, it is set to the current node, effectively creating the threading, which is used as a returning point that would otherwise have to be on stored, usually on a stack. If the right child is not null, then the algorithm makes sure that the original tree is restored, and then continues traversal in the right subtree (in this case it is known that the left subtree was visited).

    0 讨论(0)
  • 2020-11-27 09:25

    I found a very good pictorial explanation of Morris Traversal.

    0 讨论(0)
  • 2020-11-27 09:25

    PFB Explanation of Morris In-order Traversal.

      public class TreeNode
        {
            public int val;
            public TreeNode left;
            public TreeNode right;
    
            public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null)
            {
                this.val = val;
                this.left = left;
                this.right = right;
            }
        }
    
        class MorrisTraversal
        {
            public static IList<int> InOrderTraversal(TreeNode root)
            {
                IList<int> list = new List<int>();
                var current = root;
                while (current != null)
                {
                    //When there exist no left subtree
                    if (current.left == null)
                    {
                        list.Add(current.val);
                        current = current.right;
                    }
                    else
                    {
                        //Get Inorder Predecessor
                        //In Order Predecessor is the node which will be printed before
                        //the current node when the tree is printed in inorder.
                        //Example:- {1,2,3,4} is inorder of the tree so inorder predecessor of 2 is node having value 1
                        var inOrderPredecessorNode = GetInorderPredecessor(current);
                        //If the current Predeccessor right is the current node it means is already printed.
                        //So we need to break the thread.
                        if (inOrderPredecessorNode.right != current)
                        {
                            inOrderPredecessorNode.right = null;
                            list.Add(current.val);
                            current = current.right;
                        }//Creating thread of the current node with in order predecessor.
                        else
                        {
                            inOrderPredecessorNode.right = current;
                            current = current.left;
                        }
                    }
                }
    
                return list;
            }
    
            private static TreeNode GetInorderPredecessor(TreeNode current)
            {
                var inOrderPredecessorNode = current.left;
                //Finding Extreme right node of the left subtree
                //inOrderPredecessorNode.right != current check is added to detect loop
                while (inOrderPredecessorNode.right != null && inOrderPredecessorNode.right != current)
                {
                    inOrderPredecessorNode = inOrderPredecessorNode.right;
                }
    
                return inOrderPredecessorNode;
            }
        }
    
    0 讨论(0)
  • 2020-11-27 09:28
    public static void morrisInOrder(Node root) {
            Node cur = root;
            Node pre;
            while (cur!=null){
                if (cur.left==null){
                    System.out.println(cur.value);      
                    cur = cur.right; // move to next right node
                }
                else {  // has a left subtree
                    pre = cur.left;
                    while (pre.right!=null){  // find rightmost
                        pre = pre.right;
                    }
                    pre.right = cur;  // put cur after the pre node
                    Node temp = cur;  // store cur node
                    cur = cur.left;  // move cur to the top of the new tree
                    temp.left = null;   // original cur left be null, avoid infinite loops
                }        
            }
        }
    

    I think this code would be better to understand, just use a null to avoid infinite loops, don't have to use magic else. It can be easily modified to preorder.

    0 讨论(0)
提交回复
热议问题