尾递归为啥能优化?及优化方法(转载摘录)

尾递归为啥能优化? - brambles的文章 - 知乎
https://zhuanlan.zhihu.com/p/36587160

尾递归:
若函数在尾位置调用自身(或是一个尾调用本身的其他函数等等),则称这种情况为尾递归。尾递归也是递归的一种特殊情形。尾递归是一种特殊的尾调用,即在尾部直接调用自身的递归函数。对尾递归的优化也是关注尾调用的主要原因。尾调用不一定是递归调用,但是尾递归特别有用,也比较容易实现。

特点:
尾递归在普通尾调用的基础上,多出了2个特征:

  1. 在尾部调用的是函数自身 (Self-called);
  2. 可通过优化,使得计算仅占用常量栈空间 (Stack Space)。

——维基百科尾调用词条

貌似尾递归函数因为在展开的过程中计算并且缓存了结果,使得并不会像普通递归函数那样展开出非常庞大的中间结果,所以不会爆栈是吗?

当然不是!我看到过很多博客和或者教程都犯有这样的错误。尾递归函数依然还是递归函数,如果不优化依然跟普通递归函数一样会爆栈,该展开多少层依旧是展开多少层。不会爆栈是因为语言的编译器或者解释器所做了“尾递归优化”,才让它不会爆栈的。

尾递归为什么可以优化

上面说了,函数栈的目的是啥?是保持入口环境。那么在什么情况下可以把这个入口环境给优化掉?答案不言而喻,入口环境没意义的情况下为啥要保持入口环境?尾递归,就恰好是这种情况。

因为尾递归的情况下,我们保持这个函数的入口环境没意义,所以我们就可以把这个函数的调用栈给优化掉。比如还是那个阶乘函数把它写成尾递归的形式。

function fact(n, r) {
if (n <= 0) {
return 1 * r;
} else {
return fact(n - 1, r * n); // <= 这里的入口环境没有必要保留。
}
}
这时,当里面这个 fact(n - 1, r * n) 返回的时候,外面的 fact(n, r) 就马上要返回了,所以保存栈是没有任何意义的,既然没意义我们毫无疑问就要优化掉。

猜你喜欢

转载自blog.csdn.net/richand112233/article/details/87879284
今日推荐