【问题标题】:Print binary tree in BFS fashion with O(1) space以 BFS 方式打印具有 O(1) 空间的二叉树
【发布时间】:2019-11-15 13:20:37
【问题描述】:

我想知道是否可以在仅使用 O(1) 空间的情况下以广度一阶打印二叉树?

困难的部分是人们必须使用额外的空间来记忆下一个要遍历的级别,并且随着 n 的增长而增长。

由于我们没有对时间部分进行任何限制,也许有一些低效(就时间而言)的方式可以实现这一点?

有什么想法吗?

【问题讨论】:

  • @Ted 这是一个潜在的面试问题。

标签: algorithm data-structures


【解决方案1】:

这将取决于一些更细粒度的定义,例如边缘是否有反向链接。然后就很容易了,因为您只需按照树上的反向链接即可。否则我想不出没有 O(lg number of nodes) 空间的方法,因为你至少需要记住“上面”的节点。

更新

哦等等,当然它可以在 O(1) space 中通过时空交易来完成。在任何你想做反向链接的地方,你保存你的位置并做 BFS,跟踪最近的节点,直到你找到你的。然后备份到最近访问的节点并继续。

问题是,这是 O(1) 空间但 O(n^2) 时间。

另一个更新

假设我们已经到达节点 n_i,并且我们想要到达那个节点的父节点,我们称之为 wlg n_j。我们已经确定了可区分的根节点 n_0。

修改呼吸优先搜索算法,以便当它遵循有向边 (n_x,n_y) 时,存储传出或“传入”节点。因此,当您关注 (n_x,n_y) 时,您会保存 n_x。

当您从 n_0 再次启动 BFS 时,您可以保证(假设它确实是一棵树)在某个点,您将转换边 (n_j,n_i)。那时你观察到你回到了 n_i。您已经存储了 n_j,因此您知道反向边缘是 (n_i,n_j)。

因此,您得到的回溯只有两个额外的单元格,一个用于 n_0,一个用于“已保存”节点。这是 O(1)

我不太确定 O(n^2) - 现在很晚,而且这是艰难的一天,所以我不想编写证明。我确定它是 O((|N|+|E|)^2) 其中 |N|和 |E|分别是顶点集和边集的大小。

【讨论】:

  • 我想不涉及反向链接,否则问题将是微不足道的。
  • 我不熟悉下面提到的这个 IDDFS,但如果它像宣传的那样工作,那就更好了,O(lg lg #nodes)。
  • @CharlieMartin:你能详细说明第二个例子吗?如果您需要为每个反向链接使用 BFS,并且对于该 BFS 中的每个反向链接也使用 BFS,那么您似乎需要超过 O(1) 的空间?
  • 我仍然不明白这应该如何工作。假设您的层次结构中有 20 层深,您需要回溯到第 19 层。如果您所拥有的只是第 20 级节点的传入节点,您如何“再次启动 BFS”和“在某个时候转换边缘”?在到达这里之前您需要遍历的所有其他节点呢?考虑this Wikipedia image,例如:您当前位于节点“e”。您的算法如何知道要打印的下一个节点是“f”?
  • 你知道根节点在哪里,不是吗?如果不这样做,则在启动时更改算法以保存根节点;这只增加了恒定数量的空间,所以它仍然是 O(1)。现在,每次要“回溯”时,都从根节点开始。保证您最终会再次找到您所在的节点(否则您第一次是如何到达那里的?)。
【解决方案2】:

一个有趣的特殊情况是堆。

来自heapq docs

堆是每个父节点的值小于的二叉树 大于或等于其任何子代。此实现使用数组 其中heap[k] <= heap[2*k+1]heap[k] <= heap[2*k+2] 对于所有k, 从零开始计数元素。为了比较,不存在 元素被认为是无限的。一个有趣的属性 heap 是它的最小元素始终是根,heap[0]。 [弗朗索瓦·皮纳尔的解释]

一棵树在内存中的表示方式(数组的索引):

                               0

              1                                 2

      3               4                5               6

  7       8       9       10      11      12      13      14

15 16   17 18   19 20   21 22   23 24   25 26   27 28   29 30

在这种情况下,数组中的节点已经按广度优先顺序存储。

for value in the_heap:
    print(value)

在空间中 O(1)。

【讨论】:

    【解决方案3】:

    我知道这严格来说不是问题的答案,但是可以使用 O(d) 空间来以广度优先顺序访问树的节点,其中 d 是树的深度,通过递归 iterative deepening depth first search (IDDFS)。当然,堆栈需要空间。在平衡树的情况下,d = O(lg n) 其中n 是节点数。老实说,如果没有@Charlie Martin 建议的反向链接,我真的不明白你会如何在恒定空间中做到这一点。

    【讨论】:

      【解决方案4】:

      很容易实现递归方法来获取给定级别的树的所有节点。因此,我们可以计算树的高度并获取所有节点和每个级别。这是树的Level Order Traversal。但是,时间复杂度是O(n^2)。下面是 Java 实现 (source)。

      class Node 
      { 
          int data; 
          Node left, right; 
          public Node(int item) 
          { 
              data = item; 
              left = right = null; 
          } 
      } 
      
      class BinaryTree 
      { 
          Node root; 
      
          public BinaryTree() 
          { 
              root = null; 
          } 
      
          void PrintLevelOrder() 
          { 
              int h = height(root); 
              int i; 
              for (i=1; i<=h; i++) 
                  printGivenLevel(root, i); 
          } 
      
          int Height(Node root) 
          { 
              if (root == null) 
                 return 0; 
              else
              { 
                  int lheight = height(root.left); 
                  int rheight = height(root.right); 
      
              } 
          } 
      
          void PrintGivenLevel (Node root ,int level) 
          { 
              if (root == null) 
                  return; 
              if (level == 1) 
                  System.out.print(root.data + " "); 
              else if (level > 1) 
              { 
                  printGivenLevel(root.left, level-1); 
                  printGivenLevel(root.right, level-1); 
              } 
          }
      }
      

      【讨论】:

      • 这使用 O(h) 空间,其中 h 是树的高度。递归是使用调用堆栈实现的,它算作辅助空间。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-12-26
      • 1970-01-01
      • 1970-01-01
      • 2017-08-25
      • 2016-08-16
      • 1970-01-01
      相关资源
      最近更新 更多