Fibonacci数列是一个非常美丽、和谐的数列,有人说它起源于一对繁殖力惊人、基因非常优秀的兔子,也有人说远古时期的鹦鹉就知道这个规律。每一个学理工科的学生都知道斐波那契数列,斐波那契数列由如下递推关系式定义:
本文地址:http://wuyudong.com/2016/07/22/2237.html,转载请注明源地址。
F(0)=0, F(1)=1, n>1时,F(n)=F(n-1)+F(n-2)。
每一个上过算法课的同学都能用递归的方法求解斐波那契数列的第n+1项的值,即F(n)。
int Fibonacci(int n) { if (n <= 0) return 0; else if (n == 1) return 1; else return Fibonacci(n - 1) + Fibonacci(n - 2); }
我们的问题是:有没有更加优化的解法?
解法一:递推关系式的优化
例如在计算F[5]=F[4]+F[3]的时候,计算F[4]需要计算F[3],后面还需要计算一次F[3],存在重复计算问题,可以使用变量存储已计算过的项。这样可以减少计算次数,代码如下:
int fibonacci(int n) { int i, s, t, ret; s = 0, t = 1, ret = 0; if (n == 0 || n == 1) return n; for (i = 2; i <= n; i++) { ret = s + t; s = t; t = ret; } return ret; }
解法二:求通项公式
如果我们知道一个数列的通项公式,使用公式来计算会更加容易。
特征方程为:x2=x+1,有两个特征根x1,x2。
则通项为F(n)=Ax1n+Bx2n,其中A,B可以通过F(0)和F(1)计算出来。
通过通项公式,我们可以在O(1)时间内求出F(n)。但公式中引入了无理数,所以不能保证结果的精度。
int Fibonacci(int n) { double s = sqrt(5); // floor 返回小于或者等于指定表达式的最大整数 return floor((pow((1 + s) / 2, n) - pow((1 - s) / 2, n)) / s + 0.5); }
解法三:分治策略
存在2*2的矩阵A,使得:
[Fn Fn-1] = [Fn-1, Fn-2]*A
通过递推可以求得A={{1, 1}{1, 0}}
且:[Fn Fn-1] = [Fn-1, Fn-2]*A = [Fn-2, Fn-3]*A2= … = [F1, F0]*An-1
剩下的问题就是求解矩阵A的方幂。
核心代码如下:
struct Mat { double mat[N][N]; }; Mat Mut(Mat a, Mat b) //计算矩阵的乘积 { Mat c; memset(c.mat, 0, sizeof(c.mat)); int i, j, k; for (k = 0; k < n; ++k) { for (i = 0; i < n; ++i) { if (a.mat[i][k] <= 0) continue; for (j = 0; j < n; ++j) { if (b.mat[k][j] <= 0) continue; c.mat[i][j] += a.mat[i][k] * b.mat[k][j]; } } } return c; } Mat matrix_pow(Mat a, int k) //计算矩阵的k次幂 { Mat c; int i, j; for (i = 0; i < n; ++i) for (j = 0; j < n; ++j) c.mat[i][j] = (i == j); for (; k; k >>= 1) { if (k & 1) c = c * a; a = a * a; } return c; }
Comments