前言
前阶段看博客,突然发现尾递归的概念,刚开始想,不就是递归吗,后来仔细看了看不是那么回事。虽然没有深入研究,但是通过一个经典的斐波那契数列实现可以看出尾递归和普通递归的区别。
什么是尾递归
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。(From 百度词条)
当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。编译器可以做到这点,因为递归调用是当前活跃期内最后一条待执行的语句,于是当这个调用返回时栈帧中并没有其他事情可做,因此也就没有保存栈帧的必要了。通过覆盖当前的栈帧而不是在其之上重新添加一个,这样所使用的栈空间就大大缩减了,这使得实际的运行效率会变得更高(From百度词条)
斐波那契数列
这个数列相信很多人都熟悉,面试也经常会问到。
1 1 2 3 5 8 13 21 34 55 89。。。。。。
用一个普通算法实现是酱紫的:
public int F(int n) { return n < 2 ? 1 : F(n - 1) + F(n - 2); }
由于是递归调用,每次调用F函数的时候,会导致F(n)重复计算。因为,每个值最终被拆解为 F(1)+F(0).
正如上图,F(5)= F(1)+F(0)+F(1)+F(0)+F(1)+F(1)+F(0)+F(1) = 8
在看一下尾递归的实现:
public static int F(int n,int a1,int a2) { return n == 0 ? a1 : F(n - 1, a2, a1 + a2); }
在递归过程中,直接把计算结果作为参数传入到递归方法中,也就是说,递归过程中不需要保存之前的计算值。其实这个方法也可以理解为一个方法的转换。
int add(int x,int y)=>x+y; int add(s)=>s;
如上述代码: add(1,2)=add(3);
我们在看尾递归的调用:F(5,1,1)=F(4,1,2)=F(3,2,3)=F(2,3,5)=F(1,5,8)=F(0,8,13)
所以,当我们调用F(5,1,1)的时候相当于变相的调用了F(0,8,13),正如上文中所说 :当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。 因为后续的方法并不依赖于之前的方法。
总结
通过斐波那契数列能简单的区分一下普通递归和尾递归的不同之处,当然这只是我浅层次的理解。有解释不当之处还请来打我。