【问题标题】:Recursive function: for vs if递归函数:for vs if
【发布时间】:2016-01-08 00:40:52
【问题描述】:

在其中一个 C 练习中,我必须为给定深度的二叉树遍历创建一个函数。

我的第一个想法是使用 for 循环 (traverse_preorder_bad)。最后,我可以通过变量初始化 + if (traverse_preorder_working) 完成任务,但我仍然难以理解为什么 for 解决方案不起作用。

有人可以解释一下区别吗?有没有优雅的解决方案?

Code on Ideone

#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);
}

【问题讨论】:

  • 优雅是什么意思?递归绝对优雅。递归的主要问题是调用激活帧的内存/时间要求,但除此之外,递归是完全优雅的。
  • @Jack,递归很棒!我只是不喜欢我的解决方案的样子。必须设置一个辅助变量两次,为了一件简单的事情要花很多行......

标签: c binary-search-tree


【解决方案1】:

问题在于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 的每次调用都应该访问一个节点并开始访问两个子树但在代码中你递归调用访问的次数超过了两次(就是这种情况,因为你有一个循环)那么有问题。

【讨论】:

    【解决方案2】:

    “for”版本没有意义。您只想为给定节点打印一次树,因此您应该只在每个节点上调用一次 traverse。

    另外,根据你帖子中的一位cmet,我认为你对自己的工作职能有一些误解。

    您有多个检查树是否为空(作为当前树或其子树)

    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 变量什么都不做。

    【讨论】:

    • 现在,这很优雅。谢谢!
    【解决方案3】:

    这里的优雅解决方案(没有递归)是 Morris Traversal。这个想法是从左子树的最右边节点添加间接边到当前节点。

    算法的完整解释在这里:http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/

    当然,您可以修改此算法,使其不再比当前深度更深。

    【讨论】:

    • 我认为这个算法完全是错误的,OP 并没有试图比正确更聪明
    • 不,你可以用手评估这个算法。这也是众所周知的算法,只是谷歌。
    猜你喜欢
    • 2014-06-04
    • 2012-07-12
    • 1970-01-01
    • 1970-01-01
    • 2022-01-11
    • 2011-08-18
    • 2015-07-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多