您的位置:首页 > 其它

斐波那契数列的递归与循环的算法实现

2015-08-22 09:31 357 查看
前半篇转载地址:/article/7661681.html

斐波那契数列,但凡学过编程的童鞋们应该都懂,背景就不介绍了(就是大兔子生小兔子的故事),无论是面试还是实际的运用,常见的一个思路就是先用最先基本的办法实现,然后根据实际要求,一步步改进,优化算法效率。今天就以斐波那契数列这个大家都很熟悉的为例来小小感受一下。

[java] view
plaincopy

Version 1

long Fibonacci(int n)

{

if (n == 0)

return 0;

else if (n == 1)

return 1;

else if (n > 1)

return Fibonacci (n - 1) + Fibonacci (n - 2);

else

return -1;

}

这是最基本的递归思路,大家的第一个斐波那契数列应该都是写成这样的,但是不知道大家有没有测试过它的性能如何,不测不知道,一测吓一跳,N=1000,500,100,50都是黑窗口半天跳不出数据,看CPU是100%(我的电脑是25%,但是因为我的电脑是4核的,大家懂的),最后N=45得到的结果是145秒。 性能为什么会这么低呢?以N=5为例子,该程序的执行过程如下图所示,可以想象,N到一定的数目(其实还是很小的数目,比如45.。。。)就会有很多次的递归调





那么,自然的想法是,有什么办法可以减少递归的次数呢?再观察上图,可以看出,有重复的递归调用。比如F(3)就计算过两次,一个解决的方法就是记录下来已经得到结果的F(n),避免重复多次计算。 Version
2 long tempResult[10001]={0};

[java] view
plaincopy

long Fibonacci2(int n)

{

if (n == 0)

return 0;

else if (n == 1)

return 1;

else if (n > 1)

{

if(tempResult
!= 0)

return tempResult
;

else

{

tempResult
= Fibonacci2 (n - 1) + Fibonacci2 (n - 2);

return tempResult
;

}

}

}

这次优化之后,效率明显提高,N=1000时,运行时间仍趋近于0秒,但是当N=5000时出现了栈溢出的情况。再来分析,栈溢出,那么栈当中有什么呢?调用信息,变量。我们version2的改进,是将version1的调用树砍掉了一半,所以,要真正解决这个问题,还是要放弃递归算法。大家应该了解,常见的改变递归算法的方式是将它变成循环。实际上,递归是从大往小分解问题,循环则是反方向算法。

[java] view
plaincopy

long Fibonacci3(int n)

{

long * temp = new long[n + 1];

temp[0] = 0;

if (n > 0)

temp[1] = 1;

for(int i = 2; i <= n; ++i)

{

temp[i] = temp[i - 1] + temp[i - 2];

}

long result = temp
;

delete[] temp;

return result;

}

现在,当N=1000000的时候,时间仍然小于1秒了。 当然,问题还没有结束,虽然version3看上去已经是一个效率很好的算法了。前面的解决方式都是自然的从算法角度来考虑,但是,数学的力量是伟大的。version3的复杂度是O(n),有没有对数级的算法,或者更好的,常量时间算法呢? 回归到高中数学,发现f(n)=f(n-1)+f(n-2)是一个数列的通项公式,经过化简,我们可以得到它的递推公式,可以一步得出结果,为O(1)的时间复杂度。但是由于最后的递推公式中含有无理数,所以不能保证结果的精度。 还有没有别的解法呢?有没有对数时间的解法?要对数时间,就要使用分治二分策略。有这个方向,但是没有想出来。当然肯定有牛人想出来的,大家感兴趣的给个链接看看http://hi.baidu.com/houtangcaicai/blog/item/aa40e31a6160cc71dab4bdfd.html

后半篇转载地址:http://blog.sina.com.cn/s/blog_a2a6827e01013sbn.html


斐波那契数列:兔子问题及其应用

斐波那契(Fibonacci1170-1250),意大利最杰出的数学家。其父为比萨的商人,他认为数学是有用的,因此送斐波那契向阿拉伯教师们学习数学,掌握了印度数码之一新的记数体系,后来游历埃及、叙利亚、希腊、西西里、法国等地,掌握了不同国家和地区商业的算术体系,1200年回答比萨,潜心研究数学,1202年写成《算盘全集》,此书广为流传,为在欧洲传播印度-阿拉伯数码起了重要的作用。

1228斐波那契在修订《算盘全集》修订本中,增加了一道非常有名的兔子繁殖问题,问题是这样的:如果一对兔子每月生一对兔子;一对新生兔,从第二个月起就开始生兔子;假定每对兔子都是一雌一雄,试问一对兔子,一年能繁殖成多少对兔子?

先看前几个月的情况:第一个月有一对刚出生的兔子,即F(1)=1;第二个月,这对兔子长成成年兔,即F(2)=1;第三个月,这对成年兔生出一对小兔,共有两对兔子,即F(3)=2;第四个月,成年兔又生出一对小兔,原出生的兔子长成成年兔,共有三对兔子,即F(4)=3;第五个月,原成年兔又生出一对小兔,新成年兔也生出一对小兔,共有五对兔子,即F(5)=5;……以此类推,可得每个月的兔子对数,组成数列:1,1,2,3,5,8,13,21,34,55,89,144,…,这就是著名的斐波那契数列,其中的任一个数,都叫斐波那契数。

题中本质上有两类兔子:一类是能生殖的兔子,称为成年兔子;新生的兔子不能生殖;新生兔子一个月就长成成年兔子。求的是成年兔子与新生兔子的总和。每月新生兔对数等于上月成年兔对数。每月成年兔对数等于上个月成年兔对数与新生兔对数之和。最后得关系式:

F(1)=F(2)=1;

F(n)=F(n-1)+F(n-2) (n≥3)。

法国数学家比内(Binet)证明了通项公式为





#include<iostream>

using namespace std;

int main()

{

int i,a[30]={0,1,1};//数组的定义,用来储存数据

for(i=3;i<30;i++)

{

a[i]=a[i-1]+a[i-2];//Fibonacci的算术方程

}

for(i=1;i<30;i++)

{

printf("a[%d]=%d\n",i,a[i]);//输出

}

}




C++基础谭浩强书里的程序

#include<iostream>

#include<iomanip>

using namespace std;

int main()

{

long f1,f2;

int i;

f1=f2=1;

for(i=1;i<20;i++)

{

cout<<setw(12)<<f1<<setw(12)<<f2;

if(i%2==0)cout<<endl;

f1=f1+f2;

f2=f2+f1;

}

return 0;

}

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: