【问题标题】:Stackless pre-order traversal in a binary tree二叉树中的无​​堆栈前序遍历
【发布时间】:2012-02-17 01:07:30
【问题描述】:

是否可以在不使用节点堆栈或“已访问”标志的情况下在二叉树上执行 迭代 *预排序* 遍历?

据我所知,这种方法通常要求树中的节点具有指向其父节点的指针。现在,可以肯定的是,我知道如何使用父指针访问标志执行预排序遍历,从而消除迭代遍历对节点堆栈的任何要求。

但是,我想知道是否真的需要访问标志。如果树有很多节点,它们会占用大量内存。此外,如果二叉树的许多前序树遍历同时并行进行,那么拥有它们也没有多大意义。

如果可以执行此操作,一些伪代码或更短的 C++ 代码示例将非常有用。

编辑:我确实想使用递归进行前序遍历。我的问题的上下文是我在 GPU 上构建了一个八叉树(就像二叉树)。我想启动许多线程,每个线程都独立且并行地进行树遍历。

首先,CUDA 不支持递归。 其次,已访问标志的概念仅适用于单次遍历。由于许多遍历同时进行,因此在节点数据结构中具有visited-flags 字段是没有用的。它们仅在所有独立树遍历都/可以序列化的 CPU 上才有意义。更具体地说,在每次树遍历之后,我们可以在执行另一个预排序树遍历之前将访问标志设置为 false

【问题讨论】:

  • 递归算不算使用栈?
  • 感谢您指出这一点。请参阅编辑。我也不想使用递归。
  • 为什么不能对访问的标志使用外部数据结构(即哈希表,访问后添加节点)?
  • 树是否包含信息,对于每个节点,它的哪个子节点是“左”,哪个是“右”?
  • @max 是的,每个树节点都包含此信息。

标签: algorithm


【解决方案1】:

您可能要考虑的一个方向是在遍历树的节点时删除它们并将这些节点插入到新树中。如果您按顺序插入节点,则新树将完全相同。但这里的问题是如何在删除项目时保持原始树的完整性。

【讨论】:

    【解决方案2】:

    使用 {->left,->right} 指针的绝对值每个节点一位进行编码是一种黑客行为。它需要第一次通过才能获得正确的初始指针-“极性”。 它似乎被称为DSW。 您可以在 https://groups.google.com/group/comp.programming/browse_thread/thread/3552ea0af2006b28/6323076923faec26?hl=nl&q=tree+transversal&lnk=nl& usenet 线程中找到更多信息。

    我不知道它是否可以扩展到四叉树或八叉树,我严重怀疑它是否可以扩展到多线程访问。添加父指针可能更容易......

    【讨论】:

      【解决方案3】:

      可以使用这种算法,只需要父指针,不需要额外的存储:

      对于内部节点,前序遍历中的下一个节点是其最左边的子节点。

      对于叶节点:在树中继续向上,直到您来自具有两个孩子的节点的左孩子。该节点的右孩子将成为下一个要遍历的节点。

      function nextNode(node):
          # inner node: return leftmost child
          if node.left != null:
              return node.left
          if node.right != null:
              return node.right
      
          # leaf node
          while (node.parent != null)
              if node == node.parent.left and node.parent.right != null:
                  return node.parent.right
              node = node.parent
      
          return null  #no more nodes
      

      【讨论】:

      • 使用父指针会占用额外的内存,不是吗?相反,您可以存储“下一个要访问的预订节点”。这将使算法更加简单:p
      • 跟进:想想吧。常规的前序遍历需要在堆栈中存储 O(log(n)) 个指针(树的每一级都有一个指针)。您的算法需要 O(n) 个额外的指针(每个节点一个)。恕我直言,这不是一个“正确”的解决方案。
      • @Elkamina:没错,但父指针也有其他用途。如果您只需要预购遍历,那么我同意有更多内存效率更高的方法。
      【解决方案4】:

      如果您坚持这样做,您可以对树中每条可能的路径进行编号,然后设置每个工人遵循该路径。

      您的编号方案可以简单地是每个零位表示取左孩子,每个一位表示取右孩子。要执行深度优先搜索,请将您的数字从最低有效位处理到最高有效位。

      虽然没有必要提前知道树的深度,但如果您不知道,您将需要处理在该数字被完全消耗之前所有进一步的数字都命中叶子的情况。

      【讨论】:

        【解决方案5】:

        你可以给每个叶节点一个指针,指向下一个根据前序遍历进入的节点。

        例如,给定二叉树:

                  A
                 / \
                B   C
               / \
              D   E
                   \
                    F
        

        D 需要存储一个指向 E 的指针,而 F 需要存储一个指向 C 的指针。然后您可以简单地迭代遍历树,就像它是一个链表一样。

        您可以通过在左右子树节点中存储相同的指针来做到这一点,而无需额外的存储空间。由于树中不允许这样的结构(这将使其成为 DAG),因此您可以安全地推断所有“子”指针指向同一位置的任何节点都是叶节点。

        【讨论】:

          【解决方案6】:

          您可以在每个节点添加一个位,表示第一个子分支添加是向左还是向右...然后,遍历树允许在每个分支选择原始方向。

          【讨论】:

          • 我认为为每个标志添加 1 位正是他想要避免的那种存储。
          • 我知道 OP 打算避免“访问标志”,这不是我在这里的意思。这些单比特标志仅指示原始插入顺序。而且一个比特也不算贵吧?
          • @smilingbuddha,你刚刚在这个评论插入上与我比赛:)
          猜你喜欢
          • 2014-05-08
          • 1970-01-01
          • 2021-03-08
          • 2012-05-10
          • 2023-01-09
          • 1970-01-01
          • 2012-09-23
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多