我无法理解函数的递归。这个怎么运作?如何存储值以及所有值?

时间:2012-07-26 08:20:24

标签: c recursion

我无法理解函数的递归。这个怎么运作?如何存储值以及所有值?

int tree_size(struct node* node) { 
  if (node==NULL) {
    return(0); 
  } else { 
    return(tree_size(node->left) + tree_size(node->right) + 1); 
  } 
}

4 个答案:

答案 0 :(得分:2)

进入函数时,会创建一个新的堆栈帧(在内存中的堆栈上)。堆栈帧跟踪该函数内的本地数据,例如本地定义的变量和传入的参数。 (以及其他一些事情,例如返回地址和以前存储的必须保留的寄存器值。但这与此问题无关。)

使用递归,当您从同一函数中调用函数时,您将创建一个新的堆栈帧(与任何调用一样),并且新的堆栈帧将存储新调用的局部变量。

正如C. Stoll指出的那样,由于+运算符,两个调用的顺序未指定。

答案 1 :(得分:0)

考虑以下三个

     1
  2     3
 4 5   6
7

1有两个孩子(2和3) 2有两个孩子(4和5) .. 4有一个孩子(7)
你想知道1:

树的大小
tree_size(tree1);

因为tree1不为NULL,if-condition不为true,所以else语句将被执行:

tree_size(tree1): return tree_size( tree_size(tree2) + tree_size(tree3) + 1 )

对于tree2和tree3

tree_size(tree2): return tree_size( tree_size(tree4) + tree_size(tree5) + 1 )
tree_size(tree3): return tree_size( tree_size(tree6) + tree_size(NULL) + 1 )

等等。现在,如果我们在tree_size(tree1)中替换tree_size(tree2)和tree_size(tree3)的返回值,我们将得到:

tree_size(tree1): return tree_size( tree_size(tree4) + tree_size(tree5) + 1 + tree_size(tree6) + tree_size(NULL) + 1 + 1 )

现在你可以看到术语1 + 1 + 1,这是前两个niveau树的大小,如果我们继续替换tree_size调用,w将得到n次1的一些,n为树的大小

答案 2 :(得分:0)

我认为最令你困惑的是......

return(tree_size(node->left) + tree_size(node->right) + 1);

如果你在顶层节点上调用了这个函数,它会告诉你树中节点的数量是左边的节点数加上右边的数字,加上你刚调用函数的节点上的节点数上。

现在,我不认为这是令你困惑的递归,如果是,请留下评论,我可以解释一下。

我认为你遇到的问题是该行的执行顺序。嗯,它遵循“添加”的逻辑数学规则。注意,我们知道我们不必关心运算符重载,因为tree_size正在返回int,所以我们有

intA + intB + intC;

我相信我不需要告诉你,添加这三个值的顺序无关紧要,你会得到相同的结果。

然而,这些添加的顺序是明确定义的。如果你认为+运算符是它的功能,它会更清楚一些。我们基本上(我希望我做对了):

operator+(intA , operator+( intB, intC);

所以,你可以看到,我们需要首先计算B + C才能添加A,或者如果我们把它带回到有问题的代码行,我们首先得到tree_size ,添加一个,然后添加左侧的tree_size。这是一个需要注意的重要事项,你可以做一些非常奇怪的事情,例如当你获得值时编辑树的大小...比如说,如果你将节点从一侧移动到另一侧。当然,如果获得它的大小不是你可以依赖的东西,那将是一棵非常糟糕的树。

我担心我可能会在这里做得太多,所以如果我稍微错过了标记,请发表评论,我很乐意帮助你改善这个答案。

答案 3 :(得分:0)

使用纸张的类比(@nhahtdh在Q的评论中)非常好。让我详细说明一下。

首先,以更线性的方式重写代码:

int tree_size(struct node* node) { 
  if (node==NULL) {
    return(0); 
  } else { 
    x = tree_size(node->left);
    y = tree_size(node->right);
    z = x + y;
    r = z + 1;
    return( r ); 
  } 
}

想象一下,你面前有一堆厚厚的(几乎取之不尽的)空纸。您桌面左侧还有各种食谱(功能定义),右侧有空白区域。

现在,调用函数意味着将其定义从其配方复制到您面前的纸叠顶部的纸张上,然后将调用的参数值替换为函数参数,然后继续从那里。

当你点击一行函数调用任何函数调用)时,标记那张纸上的那一行在你面前,移动将纸张向右移动(面朝上),然后开始处理你面前的空白纸张。将您需要调用的函数的配方复制到其上,记下参数值,然后根据您面前的配方继续处理 it

如果您遇到另一个函数调用,请重复此操作序列:标记需要接收函数调用的结果的行,将当前的纸张放在右侧的堆上(面朝上),将新食谱复制在你面前的纸上,然后像往常一样从那里继续。

现在,当您在当前食谱中点击 return 时,请将返回的值写在右侧堆中的顶层纸上在那里标记,然后丢弃在你面前的当前纸张,然后从纸叠上移回顶部的纸张到你的右边在你面前的一叠文件。

就是这样。你只需要两张纸,一张放在你面前,另一张放在你的右边,还有一堆纸上写着功能的定义,放在你的左边。

注意,我们不关心我们调用的函数是否与我们执行的先前函数调用之一的相同。它的工作原理是一样的。