我想编写函数工具n!像这样:
int fun(int n)
{
if (n <= 1)
return 1;
return n * fun(n-1);
}
我想知道n * fun(n-1)
和fun(n-1)*n
之间的区别。哪一个更好?
答案 0 :(得分:2)
一个是右递归的,一个是左递归的,否则它们是等价的。
让我们观察堆栈如何在每种情况下为事实(3)构建。
事实(n)= n *事实(n-1)
(n * ((n-1) * ((n-2) * ...))) <-- notice stack depth
(3 * (2 * (1)))
3 * 2 * 1
6
事实(n)=事实(n-1)* n
(((... * (n-2)) * (n-1)) * n) <-- stack depth the same, but factors in reverse
(((1) * 2) * 3)
1 * 2 * 3
6
编辑:回复您对堆栈空间的跟进。
不,使用相同的堆栈空间,请参阅上面的显示内容。现在,如果您重写为尾调用,则可以取消堆栈空间。
原始形式都有一个共同点,就是它们不能进行尾调用优化这一事实,因为一旦叶子调用返回,必须在栈上推送的乘法因子才能被评估。用累加器重写可以解决这个问题。
例如,这样的事情。事实(n)可以调用fact_tailcall(n,accum),它只使用累加器变量来避免每次递归调用保存评估上下文。
fact(n) {
return fact_tailcall(n,1)
}
fact_tailcall(n,accum) {
if(n == 1)
return 1
accum = n * accum
return fact_tailcall(n-1,accum)
}
(3)
(3,1) <-- Or you can just call fact_tailcall directly with (n, 1)
(2,3)
(1,6) <-- stack never really grows
6
在后者中,递归调用之后没有额外的计算,因此编译器可以将整个递归调用堆栈优化为单个调用并转换为&#34;迭代&#34;。编译器通过重用参数的堆栈空间而不是保存每个帧来跳过所有中间递归。 &#34; N&#34;可能会占用一个寄存器,因为会&#34; accum&#34;。该函数不必退出嵌套调用,而只是直接返回&#34; goto&#34;在末尾。传递工作(accum)是函数式编程中延续传递风格的本质。
答案 1 :(得分:0)
两种方式都没有区别。有趣的函数必须在它能够乘以当前n之前计算n - 1是什么。这就像说2(3)+4 vs. 4 + 2(3),无论哪种方式你必须在你可以添加之前相乘。