基本递归和尾递归

基本递归

    先看一下计算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);

}


    当程序中调用了一个函数时,栈中会分配一块空间来保存与这个调用相关的信息。每一个调用都被当做是活跃的。栈上的那块存储空间称为活跃记录,或者称为栈帧。栈帧由5个区域组成:输入参数、返回值空间、计算表达式时用到的存储空间、函数调用时保存的状态信息以及输出参数。输入参数是传递到活跃记录中的参数;输出参数是传递给在活跃记录中调用的函数所使用的。一个活跃记录中的输出参数就成为栈中下一个活跃记录的输入参数。函数调用产生的活跃记录将一直存在于栈中直到这个函数调用结束。

    使用栈维护每个函数的调用信息是需要占用很大空间的,尤其是在程序中使用了许多递归调用的情况下。除此之外,因为有大量的信息需要保存和恢复,因此生成和销毁活跃记录需要消耗一定的时间。使用尾递归则可以避免上述缺点。


尾递归

    如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个函数是尾递归的。当递归调用是整个函数体中最后一个执行的语句且它的返回值不属于表达式中的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。

    当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活跃记录而不是在栈中创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其它事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高。

    现在使用尾递归的形式来表达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;

}















 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值