尾递归优化到底是什么?

学数据结构时就知道这个概念,一直没有研究过。

同样一个求阶乘的函数,首先是平时我们最熟悉的版本,也就是普通递归版本:

对于func(5)的递归调用如下:

然后是尾递归版本的:

 调用图是这样的:

 看起来,二者递归的栈都是五层嘛,有什么区别呢?

最大的区别是:对于第一种普通递归,每次函数的n*f(n-1)都要等f(n-1)调用返回后,再做乘法返回。也就是说,直到f(n-1)返回前,变量n的值都必须保存在栈上。

对于尾递归来说,函数加了一个参数来记录之前函数计算的结果(有点类似动态规划有没有?)。所以当前函数f(n,1)最终即将调用f(n-1,n)的时候,该栈(f(n,1)函数的栈)内的变量(比如n和cur_mul)都不再需要保存了。

所谓的尾递归优化不是说尾递归这种写法是一种优化方法,而是说我们的代码如果使用了尾递归那么编译器会自动将代码栈中的不再需要的空间优化掉,优化指的是编译器对尾递归代码的优化。说明白点,如果编译器不做其他事情,我们的尾递归代码和普通递归代码的性能差距不大,都是要递归n层的。只不过一旦编译器识别到尾递归代码,就会将内部递归的函数直接开在之前的栈上(具体会更复杂,这样只是简单理解原理),这样每层递归都是使用同一块栈空间,防止了递归层数过高爆栈的可能。最终返回的时候,会直接把最深层的函数结果一步返回给最开始调用的函数。(实际上就是变成了循环!不再是递归了!)

贴两个知乎回答:

1.尾递归,比线性递归多一个参数,这个参数是上一次调用函数得到的结果;
所以,关键点在于,尾递归每次调用都在收集结果,避免了线性递归不收集结果只能依次展开消耗内存的坏处。

什么是尾递归? - Frankie杨的回答 - 知乎 https://www.zhihu.com/question/20761771/answer/57214778

 
2.由于尾递归调用发生在函数末尾,它自己的栈帧中已经没有需要被使用的东西了,也就可以让下次递归调用直接覆盖使用当前的栈帧。
这样造成的结果就是尾递归在优化后,call / ret 其实被消除了(因为直接使用当前栈帧,不需要压栈和出栈,函数体一般也能内联),生成的机器码和循环是一样的。
什么是尾递归? - 孙竟的回答 - 知乎 https://www.zhihu.com/question/20761771/answer/19144609

猜你喜欢

转载自www.cnblogs.com/FdWzy/p/12556145.html
今日推荐