尾递归优化到底是什么?
学数据结构时就知道这个概念,一直没有研究过。
同样一个求阶乘的函数,首先是平时我们最熟悉的版本,也就是普通递归版本:
对于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 其实被消除了(因为直接使用当前栈帧,不需要压栈和出栈,函数体一般也能内联),生成的机器码和循环是一样的。
这样造成的结果就是尾递归在优化后,call / ret 其实被消除了(因为直接使用当前栈帧,不需要压栈和出栈,函数体一般也能内联),生成的机器码和循环是一样的。
什么是尾递归? - 孙竟的回答 - 知乎 https://www.zhihu.com/question/20761771/answer/19144609
进击的小🐴农