【问题标题】:Print binary tree in level order using only one extra pointer per node每个节点仅使用一个额外的指针按级别顺序打印二叉树
【发布时间】:2014-05-13 21:13:04
【问题描述】:

给定一棵二叉树,其节点具有以下类型:

struct Node {
  Node* left;
  Node* right;
  int data;
  Node* foo;   // uninitialized - use it any way you like
};

按级别顺序打印树数据,并让每个节点的foo成员指向下一个兄弟节点。

我们不允许使用任何其他存储,除了恒定数量的非数组类型的变量(特别是不允许使用外部队列)。

【问题讨论】:

  • 所以......基本上你得到的是每个级别的链接列表......如果你愿意,这只是一组队列。将其称为“不使用队列”有点武断
  • @NiklasB。我收紧了问题陈述。
  • @DavidEisenstat IMO“下一个兄弟节点”与“按该顺序排列的下一个节点”不同。对于第一种解释,foo 在任何给定级别中的最后一个节点的值都不会被问题陈述指定——这个想法可能是让它成为NULL,这不是一个微不足道的变化。跨度>
  • @Dukeling 我发现这个问题很有趣,并实施了一个似乎运行良好的解决方案。但是你对下一个兄弟姐妹的解释是什么?我假设下一个兄弟节点是该级别中的下一个节点,但它可以是具有相同父节点的下一个节点。这是完全不同的。你说什么?
  • 你为什么要清除大部分问题陈述?

标签: algorithm binary-tree tree-traversal


【解决方案1】:

这个想法很简单——一个队列可以表示为一个链表,所以使用foo来表示链表中的下一个节点,并且只跟踪最后一个节点进行入队。

伪代码:

current = root
lastNode = current
while current != null
   process current
   if current.left != null
      if lastNode != null
         lastNode.foo = current.left
      lastNode = lastNode.foo
   if current.right != null
      if lastNode != null
         lastNode.foo = current.right
      lastNode = lastNode.foo
   current = current.foo

为了简单起见,我假设所有foo 都被初始化为null
如果不是这种情况,只需在lastNode 更改时设置lastNode.foo = null

如果我们希望任何给定级别的最后一个节点的foonull,而不是指向下一个级别的第一个节点(这可能是要求的一部分 - 这有点不清楚),我们可以相当容易地跟踪下一级的第一个节点,并在指向该节点时将current.foo 设置为null

正确的关键是树中永远不会有超过一个级别,因此firstNextLevel 不能跳过一个级别,并且只会在我们处于一个级别的开头时设置.

current = root
lastNode = current
firstNextLevel = null

while current != null
   process current

   if current.left != null
      if lastNode != null
         lastNode.foo = current.left
      lastNode = lastNode.foo
      if firstNextLevel == null
         firstNextLevel = lastNode

   if current.right != null
      if lastNode != null
         lastNode.foo = current.right
      lastNode = lastNode.foo
      if firstNextLevel == null
         firstNextLevel = lastNode

   if current.foo == firstNextLevel
      temp = current
      current = current.foo
      temp.foo = null
      firstNextLevel = null
   else
      current = current.foo

然后是一个极其复杂的解决方案,涉及让foo 仅指向同一级别中的下一个节点。

我们只需要在遍历这个关卡时设置下一个关卡的foo,并跟踪下一个关卡中的第一个节点,这样我们就知道在完成这个关卡后从哪里继续。

伪代码如下所示:

current = root
firstInNextLevel = null
prevInNextLevel = null
while current != null
   // this loop can happen twice for current, so we need this check
   if prevInNextLevel != current.left
      process current

   // pick left first, then right, then nothing
   if current.left != null && prevInNextLevel != current.left
                           && prevInNextLevel != current.right
      currentInNextLevel = current.left
   else if current.right != null && prevInNextLevel != current.right
      currentInNextLevel = current.right
   else
      currentInNextLevel = null

   if currentInNextLevel != null
      // set up foo for previous node
      if prevInNextLevel != null
         prevInNextLevel.foo = currentInNextLevel
      else
         firstInNextLevel = currentInNextLevel
      prevInNextLevel = currentInNextLevel
   else
      // done with current, move on
      current = current.foo
      // no nodes left on this level, move on to the next
      if current == null
         if prevInNextLevel != null
            prevInNextLevel.foo = null
         current = firstInNextLevel
         firstInNextLevel = null
         prevInNextLevel = null

【讨论】:

    【解决方案2】:

    我认为关键是如何使用指针foo。对于最左边的孩子,可以用这个指针指向右边的兄弟节点,那么最右边的孩子呢?我认为您可以使用它来指向右侧的节点,尽管它不是他的兄弟节点。让我用例子来说明我的想法:

    一棵树

    1   
    

    3 5

    2 无 4 7

    我们可以在节点 2 和节点 4 之间放置一个“空节点”(没有值但有指针,包括 foo)。使节点 2 的 foo 指向它。并使其 foo 指向节点 4。如果我们有这个结构,那么当我们序列化这棵树时,我们只需要记录这一层的最左边的节点,并将这一层的所有节点输出到文件中。然后从我们记录的节点的左孩子开始遍历下一层。 foo 指针可以帮助我们通过整个关卡。 (最右边的节点有 foo 指向 null)

    通过这种方式,对于上面的例子,我们可以得到以下文件(一行一层)

    1

    3 5

    2#4 7

    当我们反序列化时,我们只是遍历文件并创建树。关键部分是将节点“#”与节点“4”连接起来。

    【讨论】:

    • 这有点作弊 - 我假设面试官不允许你分配新节点。你将如何分配它? malloc?如果给你的树不在动态内存中怎么办?您将插入难以跟踪的内存泄漏,并且修复它们将非常困难。您如何将这些特殊节点与常规节点区分开来?常规节点在data 中可以有任何值,并且它们不能有子节点 - 结构中没有任何东西可以让您进行这种区分。
    • 除了 Filipe 的担忧——你将如何让这个新节点指向 4(在你的例子中)——这很重要。并且序列化树也可能被认为是作弊。
    • 我认为问题变了。最初,问题是关于如何在不使用队列的情况下使用 foo 兄弟指针来完成广度优先遍历的任务。因此,对于上述 cmets 中提到的问题, 我正在尝试使用 foo 指针设计一个新结构,而不是更改当前结构。如果当前结构是固定的,我认为您不需要我的解决方案。 实际上,当您尝试打印树时,您正在执行序列化。无论如何,我仍然不明白为什么这个解决方案会成为作弊。仍然,问题发生了变化并被编辑。
    • 我认为我的问题是试图假设树具有某种我需要的良好结构(我认为这是一个问你设计树结构的问题)。反正Dukeling的解决方案不错。
    【解决方案3】:

    像往常一样,杜克林在这里触及了重点。这个想法是使用foo 作为链表中的“下一个”指针。因此,树中的每一层都被视为节点的链表。

    我的代码逐级遍历树,它为下一级的foo 指针设置了正确的值,从而正确形成了一个链表。同时,它会打印当前级别的值。

    注意:我假设兄弟姐妹是指该级别中的下一个节点。如果您指的是具有相同父节点的另一个节点,那么它会有点棘手,并且需要进行深入的更改。

    void print_bfs_aux(struct Node *);
    
    void print_bfs(struct Node *root) {
      root->foo = NULL;
      print_bfs_aux(root);
    }
    
    void print_bfs_aux(struct Node *root) {
    
      if (!root)
        return;
    
      struct Node *next_level = NULL;
      struct Node *prev = NULL;
    
      for (struct Node *current = root; current != NULL; current = current->foo) {
    
        printf("\t%d", current->data);
    
        struct Node *new_prev = NULL;
    
        if (current->left) {
          new_prev = current->left;
        } else if (current->right) {
          new_prev = current->right;
        } else {
          new_prev = prev;
        }
    
        if (prev && new_prev != prev)
          prev->foo = new_prev;
    
        if (current->left && current->right) {
          /* assert: new_prev = current->left */
          current->left->foo = current->right;
          new_prev = current->right;
        }
    
        prev = new_prev;
    
        if (!next_level) {
          if (current->left)
        next_level = current->left;
          else if (current->right)
        next_level = current->right;
        }
    
      }
    
      if (prev)
        prev->foo = NULL;
    
      printf("\n");
    
      print_bfs_aux(next_level);
    
    }
    

    在每次迭代中,prev 指向的节点是当前正在为下一级构建的链表中的最后一个节点,current 是我们正在从当前级别访问(和打印)的节点。 next_level 是下一级列表的头部。这个想法不是很复杂,但是代码很微妙,因为它需要优雅地处理任意不平衡的树。

    客户端代码应该调用print_bfs(),这是初始化根的foo 的入口点。代码不假定foo 被初始化为NULL。它也(希望)适用于任何类型的不平衡树,以及完美平衡的树。只是在为下一级构造链表的时候注意不要搞砸了。

    顺便说一下,如果你想快速测试一下,这是我用过的:

    int main(void) {
    
      struct Node nodes[100];
    
      int node, left, right;
    
      while (scanf("%d %d %d", &node, &left, &right) == 3) {
        if (left == -1)
          nodes[node].left = NULL;
        else
          nodes[node].left = &nodes[left];
        if (right == -1)
          nodes[node].right = NULL;
        else
          nodes[node].right = &nodes[right];
        nodes[node].data = node;
      }
    
      print_bfs(&nodes[0]);
    
      return 0;
    }
    

    不过有一点建议:如果您的面试官进一步要求您开发一些测试例程,请不要编写这样的代码;这只是执行一些基本测试的一种快速肮脏的方式。

    您可能想尝试的一些输入测试:

    /* Input for test 1 */
    0 1 2
    1 -1 4
    2 5 6
    4 10 11
    5 12 13 
    6 14 15
    10 -1 -1
    11 -1 -1
    12 -1 -1
    13 -1 -1
    14 -1 -1
    15 -1 -1
    
    /* A fully balanced tree */
    0 1 2
    1 3 4
    2 5 6
    3 8 9
    4 10 11
    5 12 13
    6 14 15
    8 -1 -1
    9 -1 -1 
    10 -1 -1
    11 -1 -1
    12 -1 -1
    13 -1 -1
    14 -1 -1
    15 -1 -1
    
    /* An unbalanced tree */
    0 1 2
    1 -1 -1
    2 5 -1
    5 12 13
    12 -1 -1
    13 -1 -1
    
    /* Something a little bit longer */
    0 1 2
    1 3 -1
    2 5 6
    3 8 9 
    5 12 13
    6 14 15
    8 -1 -1
    9 -1 -1
    12 -1 -1
    13 -1 -1
    14 -1 -1
    15 16 -1
    16 17 -1
    17 18 -1
    18 -1 19
    19 -1 20
    20 -1 -1
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-05-14
      • 1970-01-01
      • 1970-01-01
      • 2012-10-22
      • 1970-01-01
      • 1970-01-01
      • 2011-01-15
      相关资源
      最近更新 更多