递归和尾递归的区别

以递归方式实现阶加函数的实现: 

int recsum(int n) {
    if (n < 0)
        return 0;
    else if(n == 0 || n == 1)
        return 1;
    else
        return n + recsum(n - 1);
}

以尾递归方式实现阶加函数的实现:

int tailrecsum(int n, int res=0)
{
    if (n < 0)
        return 0;
    else if(n == 0)
        return res;
    else
        return tailrecsum(n - 1, n + res);
}
  • 非尾递归,下一个函数结束以后此函数还有后续,所以必须保存本身的环境以供处理返回值。
  • 尾递归,进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回。

递归(迭代):

recsum(5)
5 + recsum(4)
5 + (4 + recsum(3))
5 + (4 + (3 + recsum(2)))
5 + (4 + (3 + (2 + recsum(1))))
5 + (4 + (3 + (2 + 1)))
5 + (4 + (3 + 3))
5 + (4 + 6)
5 + 10
15

尾递归:

tailrecsum(5, 0)
tailrecsum(4, 5)
tailrecsum(3, 9)
tailrecsum(2, 12)
tailrecsum(1, 14)
tailrecsum(0, 15)
15

 

尾递归的判断标准是函数运行最后一步是否调用自身,而不是是否在函数的最后一行调用自身。

这是尾递归:

function f(x) {
   if (x === 1) return 1;
   return f(x-1);
}

这不是尾递归:

function f(x) {
   if (x === 1) return 1;
   return 1 + f(x-1);
}

 

在来看一个例子:

计算斐波那契数列第n项

//递归
int fibonacci(int n)
{
    if (n == 0) 
        return 0;
    else if (n == 1) 
        return 1;
    else 
        return fibonacci(n-1)+fibonacci(n-2);
}
//尾递归
int fibonacci_tail(int n, int ret1, int ret2)
{
    if (n == 0)
        return ret1;
    else 
        return fibonacci_tail(n-1, ret2, ret1 + ret2);
}

如果拿fib(6)=6+fib(5)作为例子的话,普通的递归算到最后,内存中需要储存6+5+4+3+2+fib(1),而尾递归则是20+fib(1)。前者耗费了大量内存。

通常递归都是在栈上根据调用顺序依次申请空间进行运算,然后层层回调,这是基于上一层运算依赖于下一层的运算结果(或者说上一层的运算还没用做完,需要下一层返回的结果)。

而尾递归的情况是下层计算结果对上层“无用”(上一层运算已经做完,不依赖后续的递归),为了效率,直接将下一层需要的空间覆盖在上一层上。

所以,尾递归,比线性递归多一个参数,这个参数是上一次调用函数得到的结果;

所以,关键点在于,尾递归每次调用都在收集结果,避免了线性递归不收集结果只能依次展开消耗内存的坏处。

使用尾递归可以带来一个好处:因为进入最后一步后不再需要参考外层函数(caller)的信息,因此没必要保存外层函数的stack,递归需要用的stack只有目前这层函数的,因此避免了栈溢出风险。 

  • 26
    点赞
  • 58
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值