正文

Fibonacci数列的计算2006-11-06 17:43:00

【评论】 【打印】 【字体: 】 本文链接:http://blog.pfan.cn/rickone/20040.html

分享到:

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[i]=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的代码:
#include <iostream>
using namespace std;

struct Matrix22
{
 int a,b,c;
 void operator*=(const Matrix22 &m);
 void display();
};

void Matrix22::operator *=(const Matrix22 &m)
{
 int p=b*m.b;
 int aa=a*m.a+p;
 int bb=a*m.b+b*m.c;
 int cc=p+c*m.c;
 a=aa%10000;
 b=bb%10000;
 c=cc%10000;
}

void Matrix22::display()
{
 printf("{%d,%d,%d},\n",a,b,c);
}
int main(void)
{
 Matrix22 base[31]={
{1,1,0},{2,1,1},{5,3,2},
{34,21,13},{1597,987,610},{4578,8309,6269},
{7565,7723,9842},{3954,4261,9693},{237,9867,370},
{3858,9269,4589},{8525,5243,3282},{4674,4101,573},
{4477,7947,6530},{8338,2629,5709},{3885,9563,4322},
{4194,3541,653},{8317,3227,5090},{6018,4389,1629},
{9645,2683,6962},{4514,6581,7933},{5757,3707,2050},
{4898,549,4349},{1805,6603,5202},{7634,7221,413},
{797,7387,3410},{2978,7109,5869},{6365,3323,3042},
{5554,9461,6093},{7437,2267,5170},{8258,69,8189},
{9325,4843,4482}
 };
 int n;
 while(1)
 {
  scanf("%d",&n);
  if(n==-1)
   break;
  if(n<2)
   printf("%d\n",n);
  else
  {
   Matrix22 x={1,0,1};
   --n;
   for(int i=0;n;++i,n>>=1)
   {
    if(n & 0x1)
     x*=base[i];
   }
   printf("%d\n",x.a);
  }

 }
 return 0;
}

最后我们要观察这样的矩阵方法可否推广到一般情形,我们暂且称这样的递推序列为‘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

阅读(18235) | 评论(15)


版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!

评论

loading...
您需要登录后才能评论,请 登录 或者 注册