按级别顺序打印二叉树,每个节点仅使用一个额外指针

时间:2014-05-09 19:49:01

标签: algorithm binary-tree tree-traversal

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

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

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

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

3 个答案:

答案 0 :(得分:6)

这个想法非常简单 - 一个队列可以表示为一个链表,所以使用foo来表示链表中的下一个节点,然后跟踪最后一个节点进行排队。 / p>

伪代码:

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.foo = null更改时设置lastNode

如果我们希望任何给定级别中的最后一个节点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

答案 1 :(得分:0)

我认为关键是如何使用指针foo。对于最左边的孩子,你可以使用这个指针指向右侧的兄弟节点,那么最右边的孩子呢?我认为你可以用它来指向右侧的节点,虽然它不是他的兄弟姐妹。让我用例子来表达我的想法:

一棵树

1   
     

3 5

     

2 nil 4 7

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

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

  

1

     

3 5

     

2#4 7

当我们进行反序列化时,我们只需浏览文件并创建树。关键部分是连接节点'#'和节点'4'。

答案 2 :(得分:0)

像往常一样,Dukeling在这里触及了关键点。我们的想法是使用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