美文网首页
尾递归优化

尾递归优化

作者: 最尾一名 | 来源:发表于2019-06-04 16:46 被阅读0次

    尾调用

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

    function f(x) {
      return g(x);            // 这是尾调用
    }
    
    // 情况一
    function f(x) {
      const y = g(x);       // 这不是尾调用
      return y;         
    }
    
    // 情况二
    function f(x) {
      return g(x) + 1;      // 这也不是尾调用
    }
    

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

    function f(x) {
      if (x > 0) {
        return m(x)
      }
      return n(x);
    }
    

    尾调用优化

    函数在调用时会在内存形成一个“调用记录”,又称“调用帧”,保存调用位置和内部变量等信息。如果在函数A的内部调用函数 B,那么在 A 的调用记录上方,还会形成一个 B 的调用记录。等到 B 运行结束,将结果返回到 A,B 的调用记录才会消失。如果函数 B 内部还调用函数 C,那就还有一个 C 的调用记录栈,以此类推。所有的调用记录,就形成一个"调用栈"
    由于尾调用是函数的最后一步操作,所以不需要保留外层函数的调用记录,因为调用位置、内部变量等信息都不会再用到了,只要直接用内层函数的调用记录,取代外层函数的调用记录就可以了。

    function f() {
      const m = 1;
      const n = 2;
      return g(m + n);
    }
    f();
    
    // 等同于
    function f() {
      return g(3);
    }
    f();
    
    // 等同于
    g(3);
    

    上面代码中,如果函数 g 不是尾调用,函数f就需要保存内部变量 m 和 n 的值、g 的调用位置等信息。但由于调用 g 之后,函数 f 就结束了,所以执行到最后一步,完全可以删除 f() 的调用记录,只保留 g(3) 的调用记录。

    这就叫做"尾调用优化"(Tail call optimization),即只保留内层函数的调用记录。如果所有函数都是尾调用,那么完全可以做到每次执行时,调用记录只有一项,这将大大节省内存。这就是"尾调用优化"的意义。


    尾递归

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

    function fib(n) {
      if (n === 0) return 0;
      if (n === 1) return 1;
      return fib(n - 1) + fib(n - 2);
    }
    

    上述是一个关于斐波那契数列的函数,计算斐波那契数列中下标为 n 的数,最多需要保存 2^n 个调用记录,复杂度为 O(2^n)。
    如果改写成尾递归,只需要保留一个调用记录。

    function fib(n, a = 0, b = 1) {
      if (n === 0) return a;
      return fib(n - 1, b, a + b);
    } 
    

    举个例子,求 fib(5) 会经历以下过程:

    fib(5)
    fib(5, 0, 1)
    fib(4, 1, 1)
    fib(3, 1, 2)
    fib(2, 2, 3)
    fib(1, 3, 5)
    fib(0, 5, 8)
    5
    

    尾递归的实现

    尾递归的实现,往往需要改写递归函数,确保最后一步只调用自身。做到这一点的方法,就是把所有用到的内部变量改写成函数的参数。比如上面的例子,函数 fib 需要用到两个中间变量 a、b ,那就把这个中间变量改写成函数的参数。这样做的缺点就是不太直观,第一眼很难看出来。

    解决办法:

    • 函数柯里化

    • 使用参数的默认值

    参考链接

    http://www.ruanyifeng.com/blog/2015/04/tail-call.html

    相关文章

      网友评论

          本文标题:尾递归优化

          本文链接:https://www.haomeiwen.com/subject/dmfrxctx.html