您写道,递归函数“应该不那么耗时,因为它不会创建新变量,并且它执行的操作更少”。第一个断言毫无意义。局部变量的内存通常在进入函数时由单个减法运算分配,这花费的时间微不足道(这是人类已知的最快分配)。对于您的 C++ 实现,第二个断言完全是错误的。由于您已经测量到使用编译器的递归函数较慢,因此它执行得更多,而不是更少。
现在,为什么。
好吧,每次调用都必须复制一个返回地址和堆栈上的实际参数。这需要时间。此外,为了支持调试和异常,每个函数调用通常都会做一些额外的工作来建立一个漂亮的堆栈框架,本质上存储有关调用之前堆栈的信息。
然而,递归变体不一定要更慢。但几乎自相矛盾的是,在实践中可以与迭代版本一样快的变体似乎做得更多……想法是编写它以便编译器可以将其转换为迭代版本,也就是说,编译器可以用简单的循环替换递归调用(这需要时间)。
唯一的问题是,据我所知,很少有 C++ 编译器进行这种优化。:-(
然而,为了完整起见,我们的想法是确保只有一个递归调用,并且它是最后发生的事情。这称为尾递归。您当前的递归代码,
double factorial( int n )
{
if( n < 2 ) { return 1; }
return n*factorial( n-1 );
}
不是尾递归的,因为在递归调用之后有乘以n
.
为了使其尾递归,您可以将完成应在此处完成的事情所需的信息作为参数传递*n
。为此所需的信息是 的值n
,以及应该完成的事实。这意味着引入一个带有适当形式参数的辅助函数:
double factorialScaledBy( double m, int n )
{
if( n < 2 ) { return m*1; }
// Same as "n*factorialScaledBy( m, n-1 )", but tail-recursive:
return factorialScaledBy( n*m, n-1 );
}
double factorial( int n )
{
return factorialScaledBy( 1, n );
}
现在一个足够聪明的编译器可以注意到在递归调用之后函数执行中没有任何事情发生,因此不使用局部变量,因此它们可以仅用于递归调用,因此可以实现为只是模拟参数传递加上一个跳回到函数的顶部,即循环。
干杯&hth.,