从斐波那契数列看递归和尾递归
前言
前阶段看博客,突然发现尾递归的概念,刚开始想,不就是递归吗,后来仔细看了看不是那么回事。虽然没有深入研究,但是通过一个经典的斐波那契数列实现可以看出尾递归和普通递归的区别。
什么是尾递归
如果一个函数中所有递归形式的调用都出现在函数的末尾,我们称这个递归函数是尾递归的。当递归调用是整个函数体中最后执行的语句且它的返回值不属于表达式的一部分时,这个递归调用就是尾递归。尾递归函数的特点是在回归过程中不用做任何操作,这个特性很重要,因为大多数现代的编译器会利用这种特点自动生成优化的代码。(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),正如上文中所说 :当编译器检测到一个函数调用是尾递归的时候,它就覆盖当前的活动记录而不是在栈中去创建一个新的。 因为后续的方法并不依赖于之前的方法。
总结
通过斐波那契数列能简单的区分一下普通递归和尾递归的不同之处,当然这只是我浅层次的理解。有解释不当之处还请来打我。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?