基本递归
先看一下计算n的阶乘的定义式:
n! = (n)(n-1) (n-2)...(1)
使用递归可以将其表达成:
F(n) = 1 如果 n=0, n=1
F(n) = nF(n-1) 如果 n>1
实现代码如下:
int fact(int n) {
/*****************************************************************************
* *
* Compute a factorial recursively. *
* *
*****************************************************************************/
if (n < 0)
return 0;
else if (n == 0)
return 1;
else if (n == 1)
return 1;
else
return n * fact(n - 1);
}
使用栈维护每个函数的调用信息是需要占用很大空间的,尤其是在程序中使用了许多递归调用的情况下。除此之外,因为有大量的信息需要保存和恢复,因此生成和销毁活跃记录需要消耗一定的时间。使用尾递归则可以避免上述缺点。
尾递归
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个函数是尾递归的。当递归调用是整个函数体中最后一个执行的语句且它的返回值不属于表达式中的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活跃记录而不是在栈中创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其它事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。
现在使用尾递归的形式来表达n!,函数可以定义成如下形式:
F(n,a) = a 如果n=0, n=1
F(n,a) = F(n-1, na) 如果n>1
这种定义还需要接受第二个参数a,除此之外并没有太大区别。a(初始化为1)维护递归层次的深度。这就让我们避免了每次还需要将返回值再乘以n。然而,在每次递归调用中,令a=na并且n=n-1。继续递归调用,直到n=1,这满足结束条件,此时直接返回a即可。
实现代码如下:
int facttail(int n, int a) {
/*****************************************************************************
* *
* Compute a factorial in a tail-recursive manner. *
* *
*****************************************************************************/
if (n < 0)
return 0;
else if (n == 0)
return 1;
else if (n == 1)
return a;
else
return facttail(n - 1, n * a);
}
主函数调用代码如下:
/*****************************************************************************
* *
* --------------------------------- main --------------------------------- *
* *
*****************************************************************************/
int main(int argc, char **argv) {
int n;
/*****************************************************************************
* *
* Computer the factorials of several numbers. *
* *
*****************************************************************************/
for (n = 0; n <= 13; n++) {
fprintf(stdout, "%2d! recursive: %-10d ", n, fact(n));
fprintf(stdout, "tail recursive: %-10d\n", facttail(n, 1));
}
return 0;
}