如何在没有递归或堆栈但使用父指针的情况下按顺序遍历 BST?

Posted

技术标签:

【中文标题】如何在没有递归或堆栈但使用父指针的情况下按顺序遍历 BST?【英文标题】:How to do in-order traversal of a BST without recursion or stack but using parent pointers? 【发布时间】:2012-05-09 10:53:53 【问题描述】:

是否可以在不使用visited 标志或stack 的情况下对其节点具有父指针(根的父节点是null)的BST 执行迭代顺序遍历?

我用谷歌搜索并没有找到回复。关键是,我怎么知道 - 在某个节点 - 我刚刚到达它而不是我已经完成了它下面的所有内容?

【问题讨论】:

递归?虽然这是对堆栈的间接使用。 这听起来像是那些愚蠢的面试问题之一。递归很可能是预期的答案。 [@Shubham, @pablochan] 如果你再读一遍这个问题,你会发现iterative这个词写得很明确。 那么答案是否定的(除非您可以将访问过的节点保存在某处) @pablochan,你确定吗?我认为您可以这样做,请参阅我的回答。 【参考方案1】:

你可以这样做,你只需要记住最后访问的节点和当前节点。问题陈述不允许这样做:每个节点上的visited 标志和stack 都是(最坏情况)O(n),记住最后一个节点只是O(1)。

在 C# 中,算法可能如下所示:

static void Walk(Node node)

    Node lastNode = null;
    while (node != null)
    
        if (lastNode == node.Parent)
        
            if (node.Left != null)
            
                lastNode = node;
                node = node.Left;
                continue;
            
            else
                lastNode = null;
        
        if (lastNode == node.Left)
        
            Output(node);

            if (node.Right != null)
            
                lastNode = node;
                node = node.Right;
                continue;
            
            else
                lastNode = null;
        
        if (lastNode == node.Right)
        
            lastNode = node;
            node = node.Parent;
        
    

【讨论】:

此代码处于无限循环中。当它试图访问一个叶子时,从左边上来后,最后一个节点为空,当它打算从右边向下遍历时,它会进入(last == node.left)分支,并重新做同样的事情。 @Wilhelm 不,不会。请注意,右侧的分支位于if,而不是else if。因此,当它从左侧(lastNode == node.Left 分支)上来后,它立即查看右侧(lastNode == node.Right),然后才循环。 那么你需要一个父指针,那是不是就不可能了? @bneil,你也可以使用线程树。如果你想避免堆栈和父指针。【参考方案2】:

这是另一种方法。我认为它本质上等同于 svick 的答案,但避免了额外的变量。这个版本是用 Python 实现的:

node=root
if node is not None:
  while node.left is not None:
    node=node.left
  while node is not None:
    output(node)
    if node.right is not None:
      node=node.right
      while node.left is not None:
        node=node.left
    else:
      while node.parent is not None and node.parent.right is node:
        node=node.parent
      node=node.parent

您最后访问的任何节点都决定了您需要访问的下一个节点。如果您刚刚访问过节点 X,那么您需要访问 X 右侧的最左侧节点。如果 X 没有右孩子,则下一个节点是节点 X 不是来自右侧的第一个祖先一边。

【讨论】:

Vaughn Cato 提供的代码确实按顺序遍历了树,但据我所知,一旦到达树的末端,它就会无限循环运行。有谁知道解决这个问题的方法吗? @Nathan:我没有看到您所说的无限循环。当它遍历到最右边的节点时,最后一个循环应该将它发送回根节点,最后将节点设置为根节点的父节点,它应该是无。然后外部的while循环将终止。 漂亮干净的实现 +1 测试了一棵二叉树,根=2,左=1,右=3,不行,只输出左节点。 @Patrick:我无法重现该问题。它似乎工作正常:codepad.org/njxp5tba【参考方案3】:

使用svick 的正确想法(参见他的answer),这是C++ 中的测试 代码。请注意,我没有测试他的代码,甚至没有看它,我只是采纳了他的想法并实现了我自己的功能。

void in_order_traversal_iterative_with_parent(node* root) 
node* current = root;
node* previous = NULL;

while (current) 
    if (previous == current->parent)  // Traversing down the tree.
        previous = current;
        if (current->left) 
            current = current->left;
         else 
            cout << ' ' << current->data;
            if (current->right)
                current = current->right;
            else
                current = current->parent;
        
     else if (previous == current->left)  // Traversing up the tree from the left.
        previous = current;
        cout << ' ' << current->data;
        if (current->right)
            current = current->right;
        else
            current = current->parent;
     else if (previous == current->right)  // Traversing up the tree from the right.
        previous = current;
        current = current->parent;
    


cout << endl;

【讨论】:

【参考方案4】:
public void inorderNoStack() 
    if (root == null) 
        return;
    

    // use the previous to always track the last visited node
    // helps in deciding if we are going down/up
    Node prev = null;

    Node curr = root;

    while (curr != null) 
        // going down
        if (prev == null || prev.left == curr || prev.right == curr) 
            if (curr.left != null) 
                prev = curr;
                curr = curr.left;
                continue;
             else 

                visitn(curr);

                if (curr.right != null) 
                    prev = curr;
                    curr = curr.right;
                    continue;
                 else 
                    // swap states
                    prev = curr;
                    curr = prev.parent;
                
            
        

        // going up after left traversal
        if (curr != null && prev == curr.left) 

            visitn(curr);

            if (curr.right != null) 
                prev = curr;
                curr = curr.right;
                continue;
             else 
                // swap states
                prev = curr;
                curr = prev.parent;
            
        

        // going up after right traversal
        if (curr != null && prev == curr.right) 
            // swap states
            prev = curr;
            curr = prev.parent;
        
    

【讨论】:

只包含代码而没有解释的答案不是很有用。【参考方案5】:

我的 Java 解决方案没有在现有树上引入任何标志。也没有父指针。这种方法将使节点保持在树的高度。请看一下。

https://github.com/skanagavelu/Algorithams/blob/master/src/tree/InOrderTraversalIterative.java

【讨论】:

【参考方案6】:

第一步:编写一个返回有序后继的函数

第2步:从最左边的节点开始,找到有序的后继,直到没有

    public class TreeNode 
      int data;
      TreeNode left;
      TreeNode right;
      TreeNode parent;
    

    public class TreeUtility 
      public void inorderNoRecursion(TreeNode root) 
        TreeNode current = leftmostNode(root);
        while(current != null) 
          System.out.println(current.data);
          current = inorderSuccessor(current);
        
      

      public TreeNode inorderSuccessor(TreeNode node) 
        if (node.right!=null) 
          return leftmostNode(node.right);
        

        TreeNode p = node.parent;
        TreeNode c = node;
        while(p!=null && c != p.left) 
          c = p;
          p = p.parent;
        
        return p;
      

      private TreeNode leftmostNode(TreeNode node) 
        while (node.left != null) 
          node = node.left;
        
        return node;
      
    

【讨论】:

【参考方案7】:

关键是父指针(或改变树的能力),但您需要恒定数量的额外状态(例如,以下协程的程序计数器)。

    将 v 设置为根。 当 v 有左孩子时,将 v 设置为其左孩子。 产量诉 如果 v 是根,则返回。 将 p 设置为 v 的父级。 如果 p 的右孩子是 v,则将 v 设置为 p 并转到步骤 4。 产量p. 如果 p 有一个右孩子,则将 v 设置为 p 的右孩子并转到步骤 2。 将 v 设置为 p 并转到第 4 步。

【讨论】:

您的代码未能通过太多测试用例。我已经将它翻译成 C++ 中的意大利面条代码,并针对我的测试套件运行它。【参考方案8】:

这是用 C++ 编写的:

void InOrder(Node *r)

   if(r==NULL)
         return;

   Node *t=r;

   while(t!=NULL)
       t=t->left;

  while(t!=r)
  
     if(t==(t->parent->left))
     
        cout<<t->parent->data;
        t=t->parent->right;
       if(t!=NULL)
      
       while(t!=NULL)
          t=t->left;
       
      if(t==NULL)
          t=t->parent;
     
     if(t==t->parent->right)
     
        t=t->parent;
     
  

【讨论】:

以上是关于如何在没有递归或堆栈但使用父指针的情况下按顺序遍历 BST?的主要内容,如果未能解决你的问题,请参考以下文章

在没有递归的情况下遍历非二叉树的算法是什么(使用堆栈)[重复]

在C中没有递归和堆栈的遍历树

(精)(递归遍历的延伸)交换二叉树的左右子树

使用按顺序遍历成员函数时引发异常(堆栈溢出)的问题

R:如何在不使用循环的情况下按唯一向量顺序查找所有重复向量值的索引?

如何在忽略大小写的情况下按字母顺序排序?