漫谈递归:从斐波那契开始了解尾递归
尾递归(tail recursive),看名字就知道是某种形式的递归。简单的说递归就是函数自己调用自己。那尾递归和递归之间的差别就只能体现在参数上了。
尾递归wiki解释如下:
尾部递归是一种编程技巧。递归函数是指一些会在函数内调用自己的函数,如果在递归函数中,递归调用返回的结果总被直接返回,则称为尾部递归。尾部递归的函数有助将算法转化成函数编程语言,而且从编译器角度来说,亦容易优化成为普通循环。这是因为从电脑的基本面来说,所有的循环都是利用重复移跳到代码的开头来实现的。如果有尾部归递,就只需要叠套一个堆栈,因为电脑只需要将函数的参数改变再重新调用一次。利用尾部递归最主要的目的是要优化,例如在Scheme语言中,明确规定必须针对尾部递归作优化。可见尾部递归的作用,是非常依赖于具体实现的。
我们还是从简单的斐波那契开始了解尾递归吧。
用普通的递归计算Fibonacci数列:
01
|
#include "stdio.h"
|
02
|
#include "math.h"
|
03
|
04
|
int factorial( int n);
|
05
|
06
|
int main( void )
|
07
|
{
|
08
|
int i, n, rs;
|
09
|
10
|
printf ( "请输入斐波那契数n:" );
|
11
|
scanf ( "%d" ,&n);
|
12
|
13
|
rs = factorial(n);
|
14
|
printf ( "%d \n" , rs);
|
15
|
16
|
return 0;
|
17
|
}
|
18
|
19
|
// 递归
|
20
|
int factorial( int n)
|
21
|
{
|
22
|
if (n <= 2)
|
23
|
{
|
24
|
return 1;
|
25
|
}
|
26
|
else
|
27
|
{
|
28
|
return factorial(n-1) + factorial(n-2);
|
29
|
}
|
30
|
}
|
程序员运行结果如下:
1
|
请输入斐波那契数n:20
|
2
|
6765
|
3
|
4
|
Process returned 0 (0x0) execution time : 3.502 s
|
5
|
Press any key to continue .
|
在i5的CPU下也要花费 3.502 秒的时间。
下面我们看看如何用尾递归实现斐波那契数。
01
|
#include "stdio.h"
|
02
|
#include "math.h"
|
03
|
04
|
int factorial( int n);
|
05
|
06
|
int main( void )
|
07
|
{
|
08
|
int i, n, rs;
|
09
|
10
|
printf ( "请输入斐波那契数n:" );
|
11
|
scanf ( "%d" ,&n);
|
12
|
13
|
rs = factorial_tail(n, 1, 1);
|
14
|
printf ( "%d " , rs);
|
15
|
16
|
return 0;
|
17
|
}
|
18
|
19
|
int factorial_tail( int n, int acc1, int acc2)
|
20
|
{
|
21
|
if (n < 2)
|
22
|
{
|
23
|
return acc1;
|
24
|
}
|
25
|
else
|
26
|
{
|
27
|
return factorial_tail(n-1,acc2,acc1+acc2);
|
28
|
}
|
29
|
}
|
程序员运行结果如下:
1
|
请输入斐波那契数n:20
|
2
|
6765
|
3
|
Process returned 0 (0x0) execution time : 1.460 s
|
4
|
Press any key to continue .
|
快了一倍有多。当然这是不完全统计,有兴趣的话可以自行计算大规模的值,这里只是介绍尾递归而已。
我们可以打印一下程序的执行过程,函数加入下面的打印语句:
01
|
int factorial_tail( int n, int acc1, int acc2)
|
02
|
{
|
03
|
if (n < 2)
|
04
|
{
|
05
|
return acc1;
|
06
|
}
|
07
|
else
|
08
|
{
|
09
|
printf ( "factorial_tail(%d, %d, %d) \n" ,n-1,acc2,acc1+acc2);
|
10
|
return factorial_tail(n-1,acc2,acc1+acc2);
|
11
|
}
|
12
|
}
|
程序运行结果:
01
|
请输入斐波那契数n:10
|
02
|
factorial_tail(9, 1, 2)
|
03
|
factorial_tail(8, 2, 3)
|
04
|
factorial_tail(7, 3, 5)
|
05
|
factorial_tail(6, 5, 8)
|
06
|
factorial_tail(5, 8, 13)
|
07
|
factorial_tail(4, 13, 21)
|
08
|
factorial_tail(3, 21, 34)
|
09
|
factorial_tail(2, 34, 55)
|
10
|
factorial_tail(1, 55, 89)
|
11
|
55
|
12
|
Process returned 0 (0x0) execution time : 1.393 s
|
13
|
Press any key to continue .
|
从上面的调试就可以很清晰地看出尾递归的计算过程了。acc1就是第n个数,而acc2就是第n与第n+1个数的和,这就是我们前面讲到的“迭代”的精髓,计算结果参与到下一次的计算,从而减少很多重复计算量。
fibonacci(n-1,acc2,acc1+acc2)真是神来之笔,原本朴素的递归产生的栈的层次像二叉树一样,以指数级增长,但是现在栈的层次却像是数组,变成线性增长了,实在是奇妙,总结起来也很简单,原本栈是先扩展开,然后边收拢边计算结果,现在却变成在调用自身的同时通过参数来计算。
小结
尾递归的本质是:将单次计算的结果缓存起来,传递给下次调用,相当于自动累积。
在Java等命令式语言中,尾递归使用非常少见,因为我们可以直接用循环解决。而在函数式语言中,尾递归却是一种神器,要实现循环就靠它了。
很多人可能会有疑问,为什么尾递归也是递归,却不会造成栈溢出呢?因为编译器通常都会对尾递归进行优化。编译器会发现根本没有必要存储栈信息了,因而会在函数尾直接清空相关的栈。
漫谈递归:从斐波那契开始了解尾递归相关推荐
- C语言以递归求斐波那契数列(附完整源码)
递归求斐波那契数列 递归求斐波那契数列完整源码(定义,实现,main函数测试) 递归求斐波那契数列完整源码(定义,实现,main函数测试) #include <locale.h> #inc ...
- C语言用递归求斐波那契数,让你发现递归的缺陷和效率瓶颈
C语言用递归求斐波那契数,让你发现递归的缺陷和效率瓶颈 分享到: QQ空间 新浪微博 腾讯微博 豆瓣 人人网 递归是一种强有力的技巧,但和其他技巧一样,它也可能被误用. 一般需要递归解决的问题有两个特 ...
- 使用递归解决斐波那契数列的性能问题
使用递归解决斐波那契数列的性能问题 参考文章: (1)使用递归解决斐波那契数列的性能问题 (2)https://www.cnblogs.com/mlw1814011067/p/9439651.html ...
- scala递归求斐波那契数列
object RecursiveFnb {def main(args: Array[String]): Unit = {var count = BigInt(0)//1 1 2 3 5 ?printl ...
- 用递归和非递归求斐波那契数列
递归的方法 #include<stdio.h> #include<stdlib.h> 递归的方式计算斐波那契数 int FeiBo(int f) {int n = 0;if ( ...
- 递归实现斐波那契数列 1 1 2 3 5 8 13 21 ......
/*** 递归实现斐波那契数列* @author XHQ**/ public class Demo6 {public static void main(String[] args) {int arra ...
- 算法—递归生成斐波那契数列
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 文章目录 前言 一.递归生成斐波那契数列 二.使用步骤 1.伪代码 2.c 总结 前言 提示:这里可以添加本文要记录的大概内容: 例如 ...
- python中使用递归实现斐波那契数列
python中使用递归实现斐波那契数列 python中使用递归实现斐波那契数列 先来了解一下 斐波那契数列(Fibonacci sequence),又称黄金分割数列.因数学家莱昂纳多·斐波那契(Leo ...
- 斐波那契递归调用次数_递归求解斐波那契数列的时间复杂度——几种简洁证明...
TL:DR: 暴力递归求解斐波那契数列的时间复杂度的紧界不是 ,而是 . 本文将给出几个简洁证明 用最暴力的方法求解斐波那契数列,时间复杂度是多少?具体地说,就是求下面这个程序的复杂度: def fi ...
最新文章
- $‘\r‘: command not found解决方法
- ICCV 2017 UCT:《UCT: Learning Unified Convolutional Networks forReal-time Visual Tracking》论文笔记
- android 帐户管理,Android开发之帐户管理
- Authentication和Authorization的区别
- 反转链表-剑指offer-16
- 搞笑日常:有位程序员的老爸是个什么感觉?过程你绝对意想不到!
- VB模拟指针模块mPoint.bas
- Windows 下安装 nvm 管理 nodejs 版本
- GridControl中进行多选数据,可以做多行删除
- 捷联惯导系统(SINS)误差模型
- ARC有效的工程中导人非ARC的代码/liberary的设置方法
- 大数据开发培训课程:Hive的静态分区与动态分区
- Only老K说-Java设计模式之原型模式(Prototype)
- 2021/11/16 Andriod Studio安装经验总结
- svnadmin load 遇到E125005 的错误
- 深度学习-BP曲线拟合(预测)
- 云班课蓝墨云网页版资源如何下载
- 微信小程序使用echarts
- (MC维嘉)JAVA 面向对象(3)
- 电压放大器的作用原理是什么
热门文章
- 【错误记录】Mac 中 IntelliJ IDEA 运行 Python 程序报错 ( pip 21.0 will drop support for Python 2.7 in January 20 )
- 【Android Protobuf 序列化】Protobuf 服务器与客户端通信 ( TCP 通信中使用 Protobuf )
- 【错误记录】Flutter 组件报错 ( No Directionality widget found. | RichText widgets require a Directionality )
- 【组合数学】生成函数 ( 线性性质 | 乘积性质 )
- springboot 常用的异常处理方式
- python - 线程
- JavaSript模块规范 - AMD规范与CMD规范介绍[转]
- java网络编程之TCP通讯
- Yii自定义验证规则
- 游戏玩家行为基本模式的初探小汇