在其中一个C练习中,我必须为给定深度的二叉树遍历创建一个函数。
我的第一个想法是使用for循环(traverse_preorder_bad
)。最后,我可以使用变量初始化+ if
(traverse_preorder_working
)完成任务,但我仍然在努力理解为什么for
解决方案不起作用。
有人可以解释我的区别吗?有优雅的解决方案吗?
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
const int RANGE = 1000;
typedef struct node_t
{
int data;
struct node_t *left;
struct node_t *right;
} node_t;
node_t *node_new(int data);
node_t *node_insert(node_t *tree, int data);
void traverse_preorder_working(node_t *tree, int depth);
void traverse_preorder_bad(node_t *tree, int depth);
int main(int argc, char *argv[])
{
node_t *tree = NULL;
// Random seed
srand((unsigned) time(NULL));
// Create the root node
tree = node_new(rand() % RANGE);
node_insert(tree, 5);
node_insert(tree, 1);
printf("Expected output:\n");
traverse_preorder_working(tree, 10);
printf("Bad output:\n");
traverse_preorder_bad(tree, 10);
return 0;
}
node_t *node_new(int data)
{
node_t *tree;
tree = malloc(sizeof(*tree));
tree->left = NULL;
tree->right = NULL;
tree->data = data;
return tree;
}
node_t *node_insert(node_t *tree, int data)
{
if (!tree)
return node_new(data);
if (data == tree->data)
return tree;
if (data < tree->data)
tree->left = node_insert(tree->left, data);
else
tree->right = node_insert(tree->right, data);
return tree;
}
void traverse_preorder_working(node_t *tree, int depth)
{
int i;
if (!tree)
return;
printf("%d\n", tree->data);
i = 1;
if (tree->left && i <= depth)
{
traverse_preorder_working(tree->left, depth - i);
i++;
}
i = 1;
if (tree->right && i <= depth)
{
traverse_preorder_working(tree->right, depth - i);
i++;
}
}
void traverse_preorder_bad(node_t *tree, int depth)
{
if (!tree)
return;
printf("%d\n", tree->data);
for (int i = 1; tree->left && i <= depth; i++)
traverse_preorder_bad(tree->left, depth - i);
for (int i = 1; tree->right && i <= depth; i++)
traverse_preorder_bad(tree->right, depth - i);
}
答案 0 :(得分:4)
当访问在左子树上递归调用traverse_preorder_working
的节点(然后是右)时,问题是traverse_preorder_working
是正确递归的
相反,traverse_preorder_bad
仍然是递归的,但是没有意义,当您访问某个节点时,您会在同一个具有不同深度的子树上调用traverse_preorder_bad
n次。
如果您检查调用树,请执行以下操作:
a
/ \
b c
/ \ / \
d e f g
您可以看到traverse_preorder_working(a,5)
转traverse_preorder_working(b,4)
,traverse_preorder_working(d,3)
..而其他功能正常
traverse_preorder_bad(a,5),
traverse_preorder_bad(b,4), visit subtree
traverse_preorder_bad(b,3), visit subtree
traverse_preorder_bad(b,2), visit subtree
traverse_preorder_bad(b,1), visit subtree ...
来自相同的递归级别,这意味着每个节点将被访问多次,具有不同的深度限制;这不会发生在第一个正确的版本中。
如果traverse_preorder_bad
的每个调用都应该访问一个节点并开始访问两个子树,但在代码内部,您调用的访问次数超过两次(就是这样,因为你有一个循环)然后出现问题。
答案 1 :(得分:2)
“for”版本毫无意义。您只想为给定节点打印一次树,因此您应该只在每个节点上调用一次遍历。
此外,根据您在帖子中的一条评论,我认为您对自己的工作职能存在一些误解。
您可以多次检查树是否为空(作为当前树或其子项)
i
在使用时只有一个值。你可以简化到
void traverse_preorder_working(node_t *tree, int depth){
if(!tree || depth <= 0){
return;
}
printf("%d\n", tree->data);
traverse_preorder_working(tree->left, depth - 1);
traverse_preorder_working(tree->right, depth - 1);
}
所有检查以查看我们是否应该探索节点 - 无论是因为它不存在还是太深 - 都只进行一次(在函数开始时),并且不会为每个孩子重复两次。没有任何内容的i
变量。
答案 2 :(得分:0)
这里优雅的解决方案(没有递归)是Morris Traversal。我们的想法是从左子树的最右边节点向当前节点添加间接边缘。
该算法的完整说明如下:http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/
当然,你可以修改这个算法,不要比你目前的深度更深。