C语言用递归求斐波那契数,让你发现递归的缺陷和效率瓶颈

分享到:
QQ空间
新浪微博
腾讯微博
豆瓣
人人网
递归是一种强有力的技巧,但和其他技巧一样,它也可能被误用。

一般需要递归解决的问题有两个特点:

  • 存在限制条件,当符合这个条件时递归便不再继续;
  • 每次递归调用之后越来越接近这个限制条件。

递归使用最常见的一个例子就是求阶乘,具体描述和代码请看这里:C语言递归和迭代法求阶乘

但是,递归函数调用将涉及一些运行时开销——参数必须压到堆栈中,为局部变量分配内存空间(所有递归均如此,并非特指求阶乘这个例子),寄存器的值必须保存等。当递归函数的每次调用返回时,上述这些操作必须还原,恢复成原来的样子。所以, 基于这些开销,对于递归求阶乘而言,它并没有简化问题的解决方案。

迭代求阶乘使用简单循环的程序,看上去不甚符合前面阶乘的数学定义,但它却能更为有效地计算出结果。如果你仔细观察递归函数,你会发现递归调用是函数所执行的最后一项任务。这个函数是尾部递归(tail recursion)的一个例子。由于函数在递归调用返回之后不再执行任何任务,所以尾部递归可以很方便地转换成一个简单循环,完成相同的任务。

提示:许多问题是以递归的形式进行解释的,这只是因为它比非递归形式更为清晰。但是,这些问题的迭代实现往往比递归实现效率更高,虽然代码的可读性可能稍差一些,当一个问题相当复杂,难以用迭代形式实现时,此时递归实现的简洁性便可以补偿它所带来的运行时开销。

这里有一个更为极端的例子,菲波那契数就是一个数列,数列中每个数的值就是它前面两个数的和。 这种关系常常用递归的形式进行描述:

同样,这种递归形式的定义容易诱导人们使用递归形式来解决问题。这里有一个陷牌:它使用递归步骤计算Fibonacci(n-1)和Fibonacci(n-2)。但是,在计算Fibonacci(n-1)时也将计算Fibonacci(n-2)。这个额外的计算代价有多大呢?

答案是,它的代价远远不止一个冗余计算:每个递归调用都触发另外两个递归调用,而这两个调用的任何一个还将触发两个递归调用,再接下去的调用也是如此。这样,冗余计算的数量增长得非常快。例如,在递归计算Fibonacci(10)时,Fibonacci(3)的值被计算了21次。但是,在递归计算 Fibonacci(30)时,Fibonacci(3)的值被计算了317811次。当然,这317811次计算所产生的结果是完全一样的,除了其中之一外,其余的纯属浪费。这个额外的开销真是相当恐怖!

如果使用一个简单循环来代替递归,这个循环的形式肯定不如递归形式符合前面菲波那契数的抽象定义,但它的效率提高了几十万倍!

当你使用递归方式实现一个函数之前,先问问你自己使用递归带来的好处是否抵得上它的代价。 而且你必须小心:这个代价可能比初看上去要大得多。

不信请看下面的代码,分别用递归和迭代计算斐波那契数,效率差距真是大的惊人。

复制纯文本复制

  1. #include <stdio.h>
  2. #include <time.h>
  3. #include <windows.h>
  4. // 递归计算斐波那契数
  5. long fibonacci_recursion( int n )
  6. {
  7. if( n <= 2 )
  8. return 1;
  9. return fibonacci_recursion(n-1) + fibonacci_recursion(n-2);
  10. }
  11. // 迭代计算斐波那契数
  12. long fibonacci_iteration( int n )
  13. {
  14. long result;
  15. long previous_result;
  16. long next_older_result;
  17. result = previous_result = 1;
  18. while( n > 2 ){
  19. n -= 1;
  20. next_older_result = previous_result;
  21. previous_result = result;
  22. result = previous_result + next_older_result;
  23. }
  24. return result;
  25. }
  26. int main(){
  27. int N = 45;
  28. // 递归消耗的时间
  29. clock_t recursion_start_time = clock();
  30. long result_recursion = fibonacci_recursion(N);
  31. clock_t recursion_end_time = clock();
  32. // 迭代消耗的时间
  33. clock_t iteration_start_time = clock();
  34. long result_iteration = fibonacci_iteration(N);
  35. clock_t iteration_end_time = clock();
  36. // 输出递归消耗的时间
  37. printf("Result of recursion: %ld \nTime: %fseconds",
  38. fibonacci_recursion(N),
  39. (double)(recursion_end_time-recursion_start_time)/CLOCKS_PER_SEC
  40. );
  41. printf("\n-----------------------\n");
  42. // 输出迭代消耗的时间
  43. printf("Result of iteration: %ld \nTime: %fseconds",
  44. fibonacci_iteration(N),
  45. (double)(iteration_end_time-iteration_start_time)/CLOCKS_PER_SEC
  46. );
  47. return 0;
  48. }
#include <stdio.h>
#include <time.h>
#include <windows.h>// 递归计算斐波那契数
long fibonacci_recursion( int n )
{if( n <= 2 )return 1;return fibonacci_recursion(n-1) + fibonacci_recursion(n-2);
}// 迭代计算斐波那契数
long fibonacci_iteration( int n )
{long result;long previous_result;long next_older_result;result = previous_result = 1;while( n > 2 ){n -= 1;next_older_result = previous_result;previous_result = result;result = previous_result + next_older_result;}return result;
}int main(){int N = 45;// 递归消耗的时间clock_t recursion_start_time = clock();long result_recursion = fibonacci_recursion(N);clock_t recursion_end_time = clock();// 迭代消耗的时间clock_t iteration_start_time = clock();long result_iteration = fibonacci_iteration(N);clock_t iteration_end_time = clock();// 输出递归消耗的时间printf("Result of recursion: %ld \nTime: %fseconds",fibonacci_recursion(N),(double)(recursion_end_time-recursion_start_time)/CLOCKS_PER_SEC);printf("\n-----------------------\n");// 输出迭代消耗的时间printf("Result of iteration: %ld \nTime: %fseconds",fibonacci_iteration(N),(double)(iteration_end_time-iteration_start_time)/CLOCKS_PER_SEC);return 0;
}

运行结果:

Result of recursion: 1134903170
Time: 7.494000 seconds
---------------------------------------
Result of iteration: 1134903170
Time: 0.000000 seconds

注意:上面的程序最好在GCC(Linux下的GCC或Windows下的Code:Blocks)下运行,VC下可能统计不到运行时间。

看吧,用递归花了将近7.5秒的时间,但是用迭代几乎不费吹灰之力,效率快到统计不到运行时间。

C语言用递归求斐波那契数,让你发现递归的缺陷和效率瓶颈相关推荐

  1. Java递归求斐波那契数求猴子吃桃

    递归求斐波那契数 斐波那契数是指前两位是1,后面的数依次是其前两位的和.即1,1,2,3,5,8-给你一个 n,求其值是多少 public class recursionExercise01{//定义 ...

  2. scala递归求斐波那契数列

    object RecursiveFnb {def main(args: Array[String]): Unit = {var count = BigInt(0)//1 1 2 3 5 ?printl ...

  3. C语言入门——求斐波那契数

    斐波那契数列,又称黄金分割数列,因数学家莱昂纳多·斐波那契以兔子繁殖为例子而引入,故又称为"兔子数列",指的是这样一个数列:1.1.2.3.5.8.13.21.34.--斐波那契数 ...

  4. C语言以递归求斐波那契数列(附完整源码)

    递归求斐波那契数列 递归求斐波那契数列完整源码(定义,实现,main函数测试) 递归求斐波那契数列完整源码(定义,实现,main函数测试) #include <locale.h> #inc ...

  5. 用递归和非递归求斐波那契数列

    递归的方法 #include<stdio.h> #include<stdlib.h> 递归的方式计算斐波那契数 int FeiBo(int f) {int n = 0;if ( ...

  6. 求斐波那契数(递归,非递归)

    目录 一.斐波那契数? 二.递归实现求第n个斐波那契数 2.1代码与运行结果 2.1.1图解递归过程 三.非递归求法 3.1为什么不用递归求法 3.2非递归 一.斐波那契数? 它指的是这样的数列:1, ...

  7. 递归求斐波那契数列第n个数

    斐波那契数列:第一个和第二个是1,从第三个开始每一项都是前两项的和 1 1 2 3 5 8 13 21 34 - 求斐波那契数列的第n项,利用递归思想,除了第一.二位,每一位都是前两项的和.递归函数的 ...

  8. 用C语言求斐波那契数1,1,2,3,5,8......

    斐波那契数规律:第三个数等于前两个数之和 分别设为a,b,c,则 当n<=2时,c=1; 当n>2时,c=a+b 运用循环求,n--是防止程序进入死循环,当n>2时,每循环一次减一, ...

  9. c语言斐波那契数列前20项和,,c语言利用数组求斐波那契数列的前20项

    推荐回答 一.斐波那契数列指的是这样一个数列1,1,2,3,5,8,13,21,34,55,89,144,233,377,610,987,1597,2584,4181,6765,10946,17711 ...

最新文章

  1. 原理图、PCB和实物是如何对应起来的
  2. 你为何要带着我的爱远走
  3. AndroidStudio中如何打开hierarchyviewer.bat
  4. 表单php跳转页面跳转,form表单页面跳转方式提交练习
  5. 基于ViSual Studio 2013 + MYSQL9_5302 + Navicat for MySQL9_5302的ATM自动存取款系统
  6. php多图片上传并压缩,PHP 上传图片并压缩方法详解
  7. win10我的电脑在哪里找到
  8. Golang的工程管理
  9. 写项目经历的注意事项
  10. 通过pip下载python包缓慢的解决方法
  11. patterns practices: Mobile Architecture Pocket Guide
  12. 计算机楼综合布线设计图,现代综合大楼综合布线设计方案
  13. matlab二重积分运算,matlab二重积分
  14. android吸顶效果,RecyclerVIew实现悬浮吸顶效果
  15. 关于编译报错“dereferencing pointer to incomplete type...
  16. Docker学习总结(46)——生产环境中遇到的Docker常见异常错误总结
  17. 2010年度个人工作总结
  18. 男人必学的几样家常炒菜,尤其是面对一个不会做饭的媳妇。
  19. 王者荣耀服务器能不能注销,王者荣耀游戏账号能永久注销吗 永久删除后还能恢复吗...
  20. Json系列之二 json to bean(JSONObject类详解)

热门文章

  1. 金坛区实验幼儿园服务器不稳定,2019年金坛城区部分公办幼儿园服务区划分方案(试行)...
  2. Linux:init0和shutdown -h哪个用来关机比较安全【转载】
  3. mysql的主从复制优缺点_MySQL主从复制原理,超级详细的总结,看完全通了
  4. python分析推特_用Python关注者的关注者抓取Twitter数据的最快方法
  5. 基于matlab实现的云模型计算隶属度,基于MATLAB实现的云模型计算隶属度
  6. python pandas库 画图_python绘图:matplotlib和pandas的应用
  7. 一般项目中哪里体现了数据结构_优秀程序员都应该学习的数据结构与算法项目(GitHub 开源清单)...
  8. mysql进阶知识_Mysql面试知识点总结(进阶篇)
  9. 云计算的概念_近500亿资金汹涌出逃!云计算概念龙头抛压沉重,科技股资金出逃名单出炉...
  10. aws python lambda_python – AWS Lambda发送HTTP请求