我正在尾部递归上阅读此post。
我会复制发布的解决方案:
unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return f( a - 1 ); // tail recursion
}
我想知道,如果结果取决于几个递归函数调用呢? 例如:
unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return f(a -1) + f( a - 1 );
}
上面的代码是否会被编译器优化?
答案 0 :(得分:10)
目前看,尾递归不适用。但是如果你看一下你所链接问题的second answer的结尾,你就可以看到如何恰当地重写这个函数。从
开始unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return f(a-1) + f(a-1);
}
重写如下:
unsigned int f( unsigned int a ) {
if ( a == 0 ) {
return a;
}
return 2 * f(a-1);
}
即使是现在,尾递归仍然不能直接应用。我们需要确保返回的格式严格为return f(....)
。再次重写该功能:
unsigned int f( unsigned int a, unsigned int multiplicative_accumulator = 1 ) {
if ( a == 0 ) {
return multiplicative_accumulator * a;
}
return f(a-1, multiplicative_accumulator * 2 );
}
现在,尾递归适用。这使用multiplicative_accumulator的默认值(感谢@Pubby),以便第一次调用f
只能f(x)
,否则你必须写一些f(x,1)
。
感谢@SteveJessop的几个最后的笔记:
f(a+1)+f(a+1)
更改为2*f(a+1)
是安全的,因为f没有副作用(打印,修改堆,这种事情)。如果f确实有副作用,则重写无效。(2*(2*(2*a))
(或更确切地说,(((a+a)+(a+a))+((a+a)+(a+a)))
),而当前版本更像(((2*2)*2)*a)
。这很好,特别是对于整数,因为乘法是关联的和分配的。但这与float
不完全相同,在a*b*c
中你可能会得到小的舍入差异。对于浮点运算,有时c*b*a
可能与{{1}}略有不同。答案 1 :(得分:2)
第二个函数不是尾递归的,不能用循环轻易替换,所以很可能编译器不会这样做。
答案 2 :(得分:2)
这不是尾递归(函数的结果是递归调用的结果):在递归(加法)之后有一个操作要做。有一个更复杂的转换(取决于加法的交换性)来获得尾递归:用累加器添加辅助函数:
unsigned int f_helper(unsigned int a, unsigned int acc)
{
if (a == 0) {
return acc;
}
return f_helper(a-1, f(a-1)+acc);
}
unsigned int f(unsigned int a) {
if (a == 0) {
return a;
}
return f_helper(a-1, f(a-1));
}
你可以转换成循环
unsigned int f_helper(unsigned int a, unsigned int acc)
{
while (a != 0) {
acc += f(a-1);
a = a-1;
}
return acc;
}
unsigned int f(unsigned int a) {
if (a == 0) {
return a;
}
return f_helper(a-1, f(a-1));
}
然后把它放回f
unsigned int f( unsigned int a ) {
if (a == 0) {
return a;
}
unsigned acc = f(a-1);
a = a-1;
while (a != 0) {
acc += f(a-1);
a = a-1;
}
return acc;
}
答案 3 :(得分:0)
我希望它不会像其他人所说的那样进行优化,但通常可以通过使用memoization来解决这些类型的问题,memoization使用内存进行交易,而不是进行另一次递归调用。
有关使用C ++的精彩解释,您可以查看http://marknelson.us/2007/08/01/memoization/。
在您的示例中,最后一次调用可以替换为
return 2 * f(a - 1);
这将是可以优化的。
在很多情况下,尾部递归似乎不起作用,可能只需要查看算法,并进行一些更改以帮助实现此优化。