给定一个二叉树,其节点具有以下类型:
struct Node {
Node* left;
Node* right;
int data;
Node* foo; // uninitialized - use it any way you like
};
按级别顺序打印树数据,并让每个节点的foo
成员指向下一个兄弟节点。
我们不允许使用任何其他存储,除了具有非数组类型的常量变量(特别是不允许外部队列)。
答案 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
。
如果我们希望任何给定级别中的最后一个节点foo
为null
而不是指向下一级别的第一个节点(这可能是要求的一部分 - 它'有点不清楚),我们可以相当容易地跟踪下一级别的第一个节点,并在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)
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