斐波那契数列传说起源于一对非常会生的兔子。定义:
这个数列有很多奇妙的性质(比如 F(n+1)/F(n) 的极限是黄金分割率)
解法一:递归
这是最最最直观的想法,是每个人都能编写的简单程序,优点是非常明显的:简单易懂,清晰明了。但是缺点就是效率非常低,时间复杂度是指数级的。举个例子,比如要计算F(5),那么就要就算F(4)+F(3),而在计算F(4)的时候又要计算F(3),导致了 F(3)的重复计算,如果n越来越大,重复的计算量是无比巨大的,这就是瓶颈所在。
代码:
那么怎么克服这个问题?这就引出了解法二。
解法二:动态规划
解法一的缺点是因为重复计算,那么我们只需要把一些已经计算过的答案存放起来,那这个缺点就解决了。我们用一维数组来实现,比如 F(5)就存放在数组下标为5的数据单元里。
代码:
这个算法的时间复杂度是 O(n),空间复杂度也是O(n)。复杂度来到了线性,这是我们所高兴的,但是,是否还有比线性更好的复杂度?
解法三:求解通项公式
如果我们知道了通项公式,那么我们就能在 O(1)的时间内得到F(n)。这是一个完美的时间复杂度。
这里只介绍一种求解通项公式的技巧——矩阵。矩阵作为一个强大的数学工具有太多不为人知的应用。当然还有其它方法,比如高中数学竞赛里面的特征方程,有兴趣的读者可以自行搜索一下。
我们很容易发现:
所以剩下的问题就是只要求出了就求出了F(n)。
求这个矩阵的 n次方的解法也有很多,这里介绍一种方法——相似对角化。
令
于是
上述方程的解为
于是解得
的基础解系为
的基础解系为
所以令
我们有:
所以,
两边取n次方,我们得到:
最后,做矩阵运算(实际上我们只需要 An 里左下角的数据),便可以得到:
通项公式的计算就完成了。(推导过程需线性代数基础)
时间复杂度是完美了,那么有没有缺点呢?当然有,公式里引入了无理数,所以不能保证运算结果的精度。
解法四:分治
解法三的缺点是精度无法保证,那么我们自然就想到,然计算机自己去计算,进行n-1次矩阵乘法不就行了。这是最直观的想法,虽然是线性的,但复杂度还是不令人满意,有没有更好的复杂度?比如 log2 (n)?答案是有的。
先来看一个背景知识:一个十进制正数 n的用二进制表示要用floor( log2(n) )+1 位。(floor(x)返回不大于 x的最大整数)
用二进制方式表示 n:
所以
如果能得到的值就可以经过 log2 (n)次乘法得到
。
显然可以通过递推得到:
代码:
时间复杂度仅为O(log2 (n))。
解法五:矩阵乘法+空间换时间
减少乘法,取模运算
数列的递推公式为:f(1)=1,f(2)=2,f(n)=f(n-1)+f(n-2)(n>=3)
用矩阵表示为:
进一步,可以得出直接推导公式:
由于矩阵乘法满足结合律,在程序中可以事先给定矩阵的64,32,16,8,4,2,1次方,加快程序的执行时间。(有些题目需要取模运算,也可以事先进行一下)。给定的矩阵次幂,与二进制有关是因为,如下的公式存在解,满足Xi={0或1}:
为了保证解满足 Xi={0或1},对上述公式的求解从右向左,即求解顺序为Xn,Xn-1,Xn-2,....,X1,X0。
完整代码实现如下:
///求解fac(n)%100000,其中n为大于等于3的正整数 #include<stdio.h> #include<math.h> long long fac_tmp[6][4]={ ///存放矩阵次幂 ///位置:00 01 10 11 {24578,78309,78309,46269}, ///32次幂%100000 {1597,987,987,610}, ///16次幂%100000 {34,21,21,13}, ///8次幂%100000 {5,3,3,2}, ///4次幂%100000 {2,1,1,1}, ///2次幂%100000 {1,1,1,0}, ///1次幂%100000 }; void fac(int); int main() { int n; scanf("%d",&n); fac(n); return 1; } void fac(int k) ///k>=3 { int i; long long t00=1,t01=1,t10=1,t11=0; ///表示矩阵的1次幂 long long a,b,c,d; k=k-3; ///公式中是n-2次幂,(t00,t01,t10,t11)表示1次幂。所以一共减3次 for(i=k;i>=32;i=i-32) ///对于大于等于32的k; { a=(t00*fac_tmp[0][0]+t01*fac_tmp[0][2])%100000; b=(t00*fac_tmp[0][1]+t01*fac_tmp[0][3])%100000; c=(t10*fac_tmp[0][0]+t11*fac_tmp[0][2])%100000; d=(t10*fac_tmp[0][1]+t11*fac_tmp[0][3])%100000; t00=a; t01=b; t10=c;t11=d; } i=4; while(i>=0) ///对于小于32的k(16,8,4,2,1); { if(k>=(long long)pow(2,i)) ///如果k大于某一个2的次幂 { a=(t00*fac_tmp[5-i][0]+t01*fac_tmp[5-i][2])%100000; ///(5-i):矩阵的2的i次幂在数组fac_tmp中的位置为fac_tmp[5-i] b=(t00*fac_tmp[5-i][1]+t01*fac_tmp[5-i][3])%100000; c=(t10*fac_tmp[5-i][0]+t11*fac_tmp[5-i][2])%100000; d=(t10*fac_tmp[5-i][1]+t11*fac_tmp[5-i][3])%100000; t00=a; t01=b; t10=c;t11=d; k=k-(int)pow(2,i); } i--; } a=(t00*2+t01*1)%100000; printf("%lld\n",a); }