在不使用堆栈或递归的情况下解释Morris inorder树遍历

时间:2011-03-31 16:11:17

标签: c++ binary-tree tree-traversal

有人可以帮我理解以下Morris inorder树遍历算法而不使用堆栈或递归吗?我试图了解它是如何工作的,但它只是逃避了我。

 1. Initialize current as root
 2. While current is not NULL
  If current does not have left child     
   a. Print current’s data
   b. Go to the right, i.e., current = current->right
  Else
   a. In current's left subtree, make current the right child of the rightmost node
   b. Go to this left child, i.e., current = current->left

我理解树的修改方式使current node成为right childmax node的{​​{1}},并使用此属性进行顺序遍历。但除此之外,我迷失了。

编辑: 找到这个附带的c ++代码。我很难理解修改后树的恢复方式。神奇之处在于right subtree子句,一旦右侧叶子被修改就会被击中。有关详细信息,请参阅代码:

else

8 个答案:

答案 0 :(得分:130)

如果我正确地阅读算法,这应该是它如何工作的一个例子:

     X
   /   \
  Y     Z
 / \   / \
A   B C   D

首先,X是根,因此它被初始化为currentX有一个左子,因此X成为X左子树的最右边的子项 - 在顺序遍历中X的前任。因此,X成为B的正确孩子,然后current设置为Y。树现在看起来像这样:

    Y
   / \
  A   B
       \
        X
       / \
     (Y)  Z
         / \
        C   D
上面的

(Y)指的是Y及其所有子节点,它们在递归问题时被省略。无论如何都列出了重要的部分。 现在树有一个回到X的链接,遍历继续......

 A
  \
   Y
  / \
(A)  B
      \
       X
      / \
    (Y)  Z
        / \
       C   D

然后输出A,因为它没有左子,并且current返回Y,这是A在上一次迭代中的正确子项。在下一次迭代中,Y有两个孩子。但是,循环的双重条件使它在到达自身时停止,这表明它已经遍历了子树。因此,它打印自己,并继续使用其正确的子树,即B

B打印自己,然后current变为X,它经历与Y相同的检查过程,同时也意识到其左子树已被遍历,继续Z。树的其余部分遵循相同的模式。

不需要递归,因为不依赖于通过堆栈的回溯,而是返回到(子)树的根的链接被移动到在递归的inorder树遍历算法中它将被访问的点 - 无论如何 - - 在其左子树完成后。

答案 1 :(得分:8)

递归的有序遍历是:(in-order(left)->key->in-order(right))。 (这与DFS类似)

当我们进行DFS时,我们需要知道在哪里回溯(这就是我们通常保留堆栈的原因)。

当我们经历我们需要回溯到的父节点时 - >我们找到了我们需要回溯的节点,并更新了它与父节点的链接。

当我们回溯?当我们不能走得更远。什么时候我们不能走得更远?没有留下孩子的时候。

我们回溯到哪里?注意:到SUCCESSOR!

因此,当我们沿着子子路径跟随节点时,将每个步骤的前任设置为指向当前节点。这样,前辈就会有到后继者的链接(回溯的链接)。

我们一直向左走,直到我们需要回溯。当我们需要回溯时,我们打印当前节点并按照正确的链接继续。

如果我们刚刚退回 - >我们需要跟随正确的孩子(我们完成了左孩子)。

如何判断我们是否刚刚退回?获取当前节点的前任并检查它是否具有正确的链接(到此节点)。如果有 - 比我们跟着它。删除链接以恢复树。

如果没有左链接=>我们没有回溯,应该继续留下孩子。

这是我的Java代码(抱歉,它不是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;
}

答案 2 :(得分:4)

我发现Morris Traversal的图片说明非常好。

Morris Traversal

答案 3 :(得分:3)

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
            }        
        }
    }

我认为这段代码会更好理解,只需使用null来避免无限循环,不必使用魔法其他。它可以很容易地修改为预订。

答案 4 :(得分:2)

我在这里为算法制作了动画: https://docs.google.com/presentation/d/11GWAeUN0ckP7yjHrQkIB0WT9ZUhDBSa-WR0VsPU38fg/edit?usp=sharing

这应该有助于理解。蓝色圆圈是光标,每张幻灯片是外部while循环的迭代。

这里是遍历莫里斯的代码(我从极客那里复制并修改了极客):

def MorrisTraversal(root):
    # Set cursor to root of binary tree
    cursor = root
    while cursor is not None:
        if cursor.left is None:
            print(cursor.value)
            cursor = cursor.right
        else:
            # Find the inorder predecessor of cursor
            pre = cursor.left
            while True:
                if pre.right is None:
                    pre.right = cursor
                    cursor = cursor.left
                    break
                if pre.right is cursor:
                    pre.right = None
                    cursor = cursor.right
                    break
                pre = pre.right
#And now for some tests. Try "pip3 install binarytree" to get the needed package which will visually display random binary trees
import binarytree as b
for _ in range(10):
    print()
    print("Example #",_)
    tree=b.tree()
    print(tree)
    MorrisTraversal(tree)

答案 5 :(得分:1)

我希望下面的伪代码更具启发性:

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

参考问题中的C ++代码,内部while循环找到当前节点的有序前导。在标准二叉树中,前导者的右子项必须为null,而在线程版本中,右子项必须指向当前节点。如果右子项为null,则将其设置为当前节点,从而有效地创建threading,该{{3}}用作返回点,否则必须存储,通常在堆栈上。如果正确的子项为null,则算法会确保原始树已恢复,然后在右子树中继续遍历(在这种情况下,已知访问了左子树)。

答案 6 :(得分:0)

Python解决方案 时间复杂度:O(n) 空间复杂度: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

答案 7 :(得分:-1)

PFB对莫里斯有序遍历的解释。

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