声振论坛

 找回密码
 我要加入

QQ登录

只需一步,快速开始

查看: 2218|回复: 3

[经典算法] [转贴]Fibonacci数列的计算

[复制链接]
发表于 2007-4-28 06:35 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?我要加入

x
Fibonacci数列F(n)递归地定义为:
        1                n<=2
F(n)={
        F(n-1)+F(n-2)     n>2
列出前几项:1,1,2,3,5,8,13,21,34。。。
注意到这些数字没有,很多都是在大自然中出现的,我知道的少,不过你可以在互联网上搜一下,关键词:黄金分割。
没错,这个数列中体现了黄金分割,用数学方法很容易证明。首先它是发散的,但是不妨假设F(n+1)/F(n)是可以收敛的,设e=limF(n+1)/F(n),由递推方程:F(n)=F(n-1)+F(n-2),两边同除F(n-1)得(在极限情况下):e=1+1/e,解出来就得e=1.618...,同时也得知它近似地相当于指数数列1.618^n,至少会以这样的增涨率增涨。
以下总结一下Fibonacci数列的计算方法。
1,直接递归计算。
int fibonacci(int n)
{
            if(n<=2)
                        return 1;
            return fibonacci(n-1)+fibonacci(n-2);
}
它的计算效率同它的数值增涨效率一样是指数型的(O(1.618^n)),因为它会在递归中重复计算子问题,改进的方法就是‘打表’,把已经计算过的F(n)记录下来,在以后的计算中只管用就是了,也叫备忘录方法,也是DP算法的一个组成部分。
2、备忘录方法。
int fibonacci(int n)
{
            if(mem(n)>0)
                        return mem(n);
            return mem(n)=fibonacci(n-1)+fibonacci(n-1);
}
也可以直接用递推法,先开一个表f(n),然后由f(1)=f(2)=1开始计算。
inf fibonacci(int n)
{
            if(n<=2)
                        return 1;
            int *f=new int[n+1];
            f[1]=f[2]=1;
            for(int i=3;i<=n;++i)
                        f=f[i-1]+f[i-2];
            int result=f[n];
            delete[] f;
            return result;
}
前者是自顶向下,后者是自底向上,其效率是一样的,都是O(n)。从指数型复杂度到线性复杂度,是多大的提高啊。然而效率还可以提高。
3、矩阵的方法。
思想来源上上次PKU的acm warmup
http://acm.pku.edu.cn/JudgeOnline/problem?id=3070
{{F(n+1),F(n)},
{F(n),F(n-1)}}
=
{{1,1}      ^n
{1,0}}
我当时就想,用加法算都只能是O(n)的,换成n个矩阵的连乘不是更废劲吗?其实不然,乘法方法有比加法更好的性质,用加法只能利用前两个的值,而乘法却不同,因为乘法有结合律,可以大幅度下降算法的耗时。
令A(n)表示一个矩阵序列
A(n)=
{{F(n),F(n-1)},
{F(n-1),F(n-2)}
那么A(2)={{1,1},{1,0}},由那个表达式得到:
A(n)=A(2)^(n-1),A(2)是己知的2*2矩阵,现在的问题就是如何求
A(2)^n
因为方阵的乘法有结合律,所以
A(2)^n=A(2)^(n/2)*A(2)^(n/2),不妨设n是偶数
所以求A(n)就可以化成求A(n/2)并作一次乘法,所以递归方程是:
T(n)=T(n/2)+O(1)
它的解是O(lbn),不可思议吧。
这是我AC的代码:
  1. #include <iostream>
  2. using namespace std;
  3. struct Matrix22
  4. {
  5. int a,b,c;
  6. void operator*=(const Matrix22 &m);
  7. void display();
  8. };
  9. void Matrix22::operator *=(const Matrix22 &m)
  10. {
  11. int p=b*m.b;
  12. int aa=a*m.a+p;
  13. int bb=a*m.b+b*m.c;
  14. int cc=p+c*m.c;
  15. a=aa%10000;
  16. b=bb%10000;
  17. c=cc%10000;
  18. }
  19. void Matrix22::display()
  20. {
  21. printf("{%d,%d,%d},\n",a,b,c);
  22. }
  23. int main(void)
  24. {
  25. Matrix22 base[31]={
  26. {1,1,0},{2,1,1},{5,3,2},
  27. {34,21,13},{1597,987,610},{4578,8309,6269},
  28. {7565,7723,9842},{3954,4261,9693},{237,9867,370},
  29. {3858,9269,4589},{8525,5243,3282},{4674,4101,573},
  30. {4477,7947,6530},{8338,2629,5709},{3885,9563,4322},
  31. {4194,3541,653},{8317,3227,5090},{6018,4389,1629},
  32. {9645,2683,6962},{4514,6581,7933},{5757,3707,2050},
  33. {4898,549,4349},{1805,6603,5202},{7634,7221,413},
  34. {797,7387,3410},{2978,7109,5869},{6365,3323,3042},
  35. {5554,9461,6093},{7437,2267,5170},{8258,69,8189},
  36. {9325,4843,4482}
  37. };
  38. int n;
  39. while(1)
  40. {
  41.   scanf("%d",&n);
  42.   if(n==-1)
  43.    break;
  44.   if(n<2)
  45.    printf("%d\n",n);
  46.   else
  47.   {
  48.    Matrix22 x={1,0,1};
  49.    --n;
  50.    for(int i=0;n;++i,n>>=1)
  51.    {
  52.     if(n & 0x1)
  53.      x*=base[i];
  54.    }
  55.    printf("%d\n",x.a);
  56.   }
  57. }
  58. return 0;
  59. }
复制代码
转自算法驿站:http://blog.programfan.com/


[ 本帖最后由 风花雪月 于 2007-4-28 06:39 编辑 ]
回复
分享到:

使用道具 举报

 楼主| 发表于 2007-4-28 06:36 | 显示全部楼层
最后我们要观察这样的矩阵方法可否推广到一般情形,我们暂且称这样的递推序列为‘c阶线性递推方程’吧,它的意思就是说F(n)由与n相距c远的前面的一些F(m)组成的线性组合,比如Fibonacci数列的最大距离就是2,所以这样的递推式需要确定一个序列至少要知道最初的c个己知初值,我们的目的就是要求这样的递归式的一般算法:
F(n)={F(n-1),F(n-2),...F(n-c)}*K
{F(1),F(2),,,F(c)}^T=C
其中K={x1,x2,...xc}^T,C={c1,c2,...cc}^T都是非0的常向量。
我们构造A(n)表示一个c*c的矩阵序列,且形式为
A(n)=
{{F(n),F(n-1),F(n-2),...F(n-c+1)},
{F(n-1),F(n-2),F(n-3),...F(n-c)},
......
{F(n-c+1),F(n-c),F(n-c-c),...F(n-2c+2)}}
为了使它有意义,n-2c+2>=1,所以n>=2c-1,所以A(n)的初始矩阵是A(2c-1),可以按递归式直接构造它,然后我们考虑A(n)的生成矩阵会是什么样子,设为G,于是应满足:
A(n)=A(n-1)G
观察A(n-1)的第一行,它刚好就是{F(n-1),F(n-2),...F(n-c)},由递推式,知G的第一列应为K,而第二列设为x,则要满足{F(n-1),F(n-2),...F(n-c)}x=F(n-1),很简单,x={1,0,0,...},同样第三列是{0,1,0,0,...},所以右边应该是一个(c-1)*(c-1)的单位矩阵I再在最下面补上一行0,于是G可以表示为:
G={K,{I,0}^T}
于是A(n)的通项就是
A(n)=A(2c-1)*G^(n-2c+1)
比如Fibonacci数列的K={1,1}^T,所以
G={K,{I,0}^T}=
{{1,1},
{1,0}}
再举一个例子,也是以前做过的一个题,母牛生小牛的题,在一个农场上有很多母牛,第1年有一只母牛,而且每过4年,母牛会开始生小母牛,小母牛过4年后也会开始生,此后就每年生一只小母年,问第n年时有多少只母牛?
简单例出前几个数字:1,1,1,2,3,4,6,。。。会发现一个规律,就是第n年的母牛数F(n)恰等于前一年的母牛数F(n-1),以及前三年的母牛数F(n-3)之和,其实直接得到这个递推式也不难,因为F(n-3)到F(n)刚好4年(第4年),所以F(n-3)都是一些可以生仔的牛数,它们会添加F(n-3)个小牛,同时F(n-1)是所有的牛妈妈,那当然有F(n)=F(n-1)+F(n-3)。
它的K={1,0,1},c=3,生成矩阵G=
{{1,1,0},
{0,0,1},
{1,0,0}}
所以有A(n)=A(3)*G^(n-2)
A(3)=
{{1,1,1},
{1,1,0},
{1,0,0}}
从O(2^n)到O(n),再到O(lbn),效率提高得也太猛烈了,不过一点要提出来,不管什么样的算法,Fiboacci数列本身数值的递增是指数型的,所以要想算很高阶的结果,也近乎不太可能,所以经常会在一个模数范围内考虑。
rickone 2006/11/06
发表于 2007-4-28 08:53 | 显示全部楼层
在一个农场上有很多母牛,第1年有一只母牛,而且每过4年,母牛会开始生小母牛,小母牛过4年后也会开始生,此后就每年生一只小母年,问第n年时有多少只母牛?

呵呵,这个题目我也刚编过,有不少方法
 楼主| 发表于 2007-5-5 20:57 | 显示全部楼层
原帖由 hao1982 于 2007-4-28 08:53 发表
在一个农场上有很多母牛,第1年有一只母牛,而且每过4年,母牛会开始生小母牛,小母牛过4年后也会开始生,此后就每年生一只小母年,问第n年时有多少只母牛?

呵呵,这个题目我也刚编过,有不少方法


把你写的那出来分享一下,呵呵
您需要登录后才可以回帖 登录 | 我要加入

本版积分规则

QQ|小黑屋|Archiver|手机版|联系我们|声振论坛

GMT+8, 2024-9-21 04:40 , Processed in 0.054377 second(s), 17 queries , Gzip On.

Powered by Discuz! X3.4

Copyright © 2001-2021, Tencent Cloud.

快速回复 返回顶部 返回列表