美文网首页
尾递归优化

尾递归优化

作者: 最尾一名 | 来源:发表于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

相关文章

  • 什么是尾调用?什么是尾递归?尾调用的优化?尾递归优化?

    尾调用优化 尾递归(尾调用优化)

  • Kotlin语言(九):特性

    1、尾递归优化 尾递归:函数在调用自己之后没有再执行其他任何操作就是尾递归 尾递归优化的原理就是将递归转换成迭代,...

  • 第2模块第1章2829递归的作用尾递归优化

    尾递归优化 def cal(n): print(n) return cal(n+1) cal(1) 尾递归优化并不...

  • 尾递归优化

    “尾递归优化”的含义是:如果递归函数属于尾递归,那么运行时会优化其调用过程。优化主要针对调用栈,将多层调用,转化为...

  • 9. 递归函数

    使用递归函数需要注意防止栈溢出解决递归调用栈溢出的方法是通过尾递归优化遗憾的是,大多数编程语言没有针对尾递归做优化...

  • 递归优化-尾递归

    一、定义 在函数内部,可以调用其他函数。如果一个函数在内部调用自身本身,这个函数就是递归函数。 二、利弊 递归函数...

  • 递归优化-尾递归

    尾递归能否起到优化作用跟编译器有关系,并不是用了尾递归就一定能起到优化作用。 定义:函数里的最后一个动作是返回一个...

  • 递归调用优化

    尾递归优化 函数调用自身,称为递归。如果尾调用自身,就称为尾递归。 递归非常耗费内存,因为需要同时保存成千上百个调...

  • python3 尾递归优化装饰器

    python3中没有进行尾递归优化,但是我们可以实现通过一个装饰器实现尾递归优化。 网上常见的尾递归装饰器是基于P...

  • 2018-07-28

    递归函数以及尾递归优化: #利用递归函数计算阶乘 ... #N! = 1 * 2 * 3 * 4 * ... * ...

网友评论

      本文标题:尾递归优化

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