尾调用、尾递归

尾调用

指某个函数的最后一步是调用另一个函数

1:
function f(x){
  return g(x);
}

2:
function f(x){
  let y = g(x);
  return y;
}

3:
function f(x){
  return g(x) + 1;
}

 1是尾调用,2/3不是

 注意:尾调用不一定出现在函数尾部,只要是最后一步操作即可

function f(x) {
  if (x > 0) {
    return a(x)
  }
  return b(x);
}

函数a和b都属于尾调用,因为它们都是函数f的最后一步操作

函数调用会在内存形成一个"调用记录",又称"调用帧"(call frame),保存调用位置和内部变量等信息。如果在函数A的内部调用函数B,那么在A的调用记录上方,还会形成一个B的调用记录。等到B运行结束,将结果返回到A,B的调用记录才会消失。如果函数B内部还调用函数C,那就还有一个C的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"(call stack)。

尾调用由于是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了

尾递归

函数调用自身,称为递归。如果尾调用自身,就称为尾递归。

递归非常耗费内存,因为需要同时保存成千上百个调用记录,很容易发生"栈溢出"错误(stack overflow)。但对于尾递归来说,由于只存在一个调用记录,所以永远不会发生"栈溢出"

例:阶乘

常规的计算阶乘的递归:

int f(int n) {
    if (n == 1) {
        return 1;
    }
    return f(n-1) * n;
}

空间复杂度为O(n)

尾递归:

int f(int n,int sum) {
    if (n == 1) {
        return sum;
    }
    return f(n-1, n * sum);  //即:把sum独立出来,当做参数传递
}

空间复杂度为O(1)

斐波那契数列的尾递归

1 1 2 3 5 8 13 21 34 55 89。。。。。。

求斐波那契数列的第n的数(第0个数为1,第1个数为1,第2个数为2...)

一般递归代码:

public int F(int n) {
    return n < 2 ? 1 : F(n - 1) + F(n - 2);
}

//F(5)

由于是递归调用,每次调用F函数的时候,会导致F(n)重复计算。因为,每个值最终被拆解为 F(1)+F(0).

  

尾递归代码:

public static int F(int n,int a1,int a2) {
    return n == 0 ? a1 : F(n - 1, a2, a1 + a2);
}

//F(5,1,1)

我们在看尾递归的调用:F(5,1,1)=F(4,1,2)=F(3,2,3)=F(2,3,5)=F(1,5,8)=F(0,8,13)

所以,当我们调用F(5,1,1)的时候相当于变相的调用了F(0,8,13),正如上文中所说 :当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。 因为后续的方法并不依赖于之前的方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值