【问题标题】:Walking a tree, parent first走一棵树,父母第一
【发布时间】:2010-12-09 15:15:01
【问题描述】:

访问链接树的所有节点的最佳方法是什么(所有节点都引用父节点和所有子节点,根节点的父节点为 null),这样在其任何祖先之前都不会访问节点?非递归的布朗尼点。

【问题讨论】:

标签: algorithm tree hierarchy


【解决方案1】:

伪代码:

NodesToVisit = some stack or some list
NodesToVisit.Push(RootNode)

While NodesToVisit.Length > 0
{
   CurNode = NodesToVisit.Pop()
   For each Child C in CurNode
        NodesToVisit.Push(C)
   Visit(CurNode) (i.e. do whatever needs to be done)
}

编辑:是否递归?
为了在技术上正确,正如 AndreyT 和其他人在这篇文章中指出的那样,这种方法是一种递归算法,其中使用显式管理的堆栈代替CPU 堆栈的位置以及递归发生在 While 循环级别的位置。也就是说,它与递归实现本身在一些微妙但重要的方面有所不同:

  • 只有“变量”被压入堆栈;堆栈上没有“堆栈帧”和相关的返回地址,唯一的“返回地址”隐含在 while 循环中,并且只有一个实例。
  • “堆栈”可用作列表,从而可以在列表中的任何位置获取下一个“帧”,而不会以任何方式中断逻辑。

【讨论】:

  • 好的。这不是一个学术问题。这是一个实际的问题。这以令人满意的方式回答了它,而不会让我思考或做任何进一步的阅读。谢谢。 (当我有时间时,它可能会让我稍后再想,但这很好......很有用,甚至......)工作完成了。再次感谢:)
【解决方案2】:

【讨论】:

    【解决方案3】:

    您正在寻找预购遍历。我认为你可以非递归地做到这一点 一个队列:。在伪代码中:

    创建一个空队列,然后推送根节点。

    while nonempty(q)
      node = pop(q)
      visit(node)
      foreach child(node)
        push(q,child)
    

    【讨论】:

    • 这将是递归算法非递归实现。用显式堆栈替换隐含堆栈不会将递归算法变成非递归算法。事实上,它根本不会改变算法。您上面的内容显然是递归的(就算法而言)。
    • 通常当人们说他们不想要递归时,他们指的是函数级递归。这满足该条件。
    • 嗯,有时是的。但是,我们在这里考虑的问题是专门为允许真正的非递归解决方案(即非递归算法)而设计的。死的赠品是父指针的存在。您的“非递归”解决方案不使用父指针。你不好奇他们为什么会在那里吗?它们专门用于实现真正的非递归解决方案,即具有 O(1) 内存要求的解决方案。
    • 但大多数时候 - 没有。通常,当人们说他们不想要递归时,他们的意思是他们不想要递归的内存要求,即他们不希望有一个非常量大小的数据结构来存储“子问题”,无论是 FIFO , LIFO 或其他任何东西。
    • @AndreT:我不认为我听说过“非递归”曾经意味着“恒定空间要求”,正如你所相信的那样。所以我不同意你的用法是“典型的”。
    【解决方案4】:

    如果您有指向所有子节点和父节点的链接,那么非递归算法就相当简单。忘记你有一棵树。可以把它想象成一个迷宫,其中每个父子链接都是从一个路口到另一个路口的普通双向走廊。走完整个迷宫所需要做的就是在每个路口转入左侧的下一个走廊。 (或者,把它想象成用你的左手总是接触左侧的墙壁走迷宫)。如果您从根结点开始(并向任何方向移动),您将走完整棵树,总是先于孩子拜访父母。在这种情况下,每个“走廊”都将被行驶两次(一个方向和另一个方向),每个“路口”(节点)将被访问的次数与加入它的“走廊”数量一样多。

    【讨论】:

    • 这是我提到的常量内存算法。
    【解决方案5】:

    使用一组节点。将根放入集合中以启动。然后在一个循环中,从集合中拉出一个节点,访问它,然后将其子节点放入集合中。当集合为空时,您就完成了。

    【讨论】:

    • 您希望数据结构是先进先出的,而不是任何旧容器,以保证预购条件。
    • 问题中没有这个要求。
    【解决方案6】:

    在伪代码中:

    currentList = list( root )
    nextList = list()
    while currentList.count > 0:
        foreach node in currentList:
            nextList.add(node.children)
        currentList = nextList
    

    【讨论】:

      【解决方案7】:

      如果您从根节点开始,并且只访问您已经访问过的节点的父/子,则 没有办法 遍历树,以便您在访问其祖先之前访问一个节点.

      任何类型的遍历、深度优先(基于递归/堆栈)、广度优先(基于队列)、深度限制或只是将它们拉出无序集合,都可以。

      “最佳”方法取决于树。广度优先适用于一棵树枝很少的非常高的树。深度优先适用于有许多树枝的树。

      由于节点实际上有指向其父节点的指针,因此还有一个常量内存算法,但速度要慢得多。

      【讨论】:

      • Teh op 说“no 节点在其祖先之前被访问”。所以情况正好相反。
      • 也许不是。我认为在您的第一句话中您声称问题无法解决,因为访问顺序要求(我认为您误解了)是不可能满足的。
      • 我的意思是任何遍历(从根节点开始)都会满足要求。
      【解决方案8】:

      我不同意广度优先搜索,因为空间复杂度通常是特定搜索算法的祸根。可能使用迭代深化算法是此类使用的更好替代方案,它涵盖与广度优先搜索相同类型的遍历。在处理广度优先搜索的边缘方面存在细微差别,但(伪)编码应该不会太难。

      参考: http://en.wikipedia.org/wiki/Iterative_deepening

      【讨论】:

      • +1 因为您考虑到空间复杂性 - 但为什么不直接使用深度优先搜索?
      • 实践中的许多树往往比它们“更宽”更深,尤其是。在人工智能决策过程中。该问题并未说明树是否是有限的,但您可能会陷入循环。喜欢迭代深化的原因之一是它是完整的(会找到解决方案)。
      【解决方案9】:

      这是一个真正非递归方法:没有堆栈,恒定空间。此 Python 代码假定每个节点都包含一个子节点列表,并且节点对象不定义相等性,因此“索引”函数正在比较身份:

      def walkTree(root, visit_func):
          cur  = root
          nextChildIndex = 0
      
          while True:
              visit_func(cur)
      
              while nextChildIndex >= len(cur.children) and cur is not root:
                  nextChildIndex = cur.parent.children.index(cur) + 1
                  cur  = cur.parent
      
              if nextChildIndex >= len(cur.children):
                  break
      
              cur = cur.children[nextChildIndex]
              nextChildIndex = 0
      

      我相信它可以稍微完善一下,使其更简洁,更易于阅读,但这就是要点。

      【讨论】:

        【解决方案10】:

        在根节点(级别 0)构建节点列表,依次遍历每个节点并查找直接子节点(其父节点是我们当前正在查找的节点)(级别 1),当完成级别0 继续迭代第 1 级,依此类推,直到没有剩余的未访问节点。

        【讨论】:

          猜你喜欢
          • 2013-12-03
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多