斐波那契是一个神奇的东西,0,1,1,2,3,5,8,13,21等等,有一个递推公式:
f(N+1)=f(N)+f(N-1)
所以要实现一个取第一百万的斐波那契数字,最简单的方法是不停的加。
这其中有两个问题:1 效率,2 超大整数。
时间效率自不必说 O(N) 跑不了了,而另一个棘手的问题是超大整数。这个超大整数变量的运算是不能忽略效能的。其时间效能绝对不是O(1)。
所以需要优化算法,同时,需要找一个极致优化的大整数库轮子。
优化算法是百度来的,有无数人实现过的矩阵快速幂法。
已知如下方程成立,其中N>=1,如何实现求 f(N):
[ f ( N + 1 ) f ( N ) f ( N ) f ( N − 1 ) ] = [ 1 1 1 0 ] N \begin{bmatrix} f(N+1) & f(N) \\ f(N) & f(N-1) \end{bmatrix} = \begin{bmatrix} 1 & 1 \\ 1 & 0 \end{bmatrix} ^N [f(N+1)f(N)f(N)f(N−1)]=[1110]N
使用快速幂法:
[
1
1
1
0
]
N
=
(
[
1
1
1
0
]
N
/
2
)
2
\begin{bmatrix} 1&1\\1&0\end{bmatrix} ^N =\left ( \begin{bmatrix} 1&1\\1&0\end{bmatrix} ^ {N / 2} \right )^2
[1110]N=([1110]N/2)2
比如原N为8,需要进行32次相乘运算,可以变为 ( ( ( X 2 ) 2 ) 2 ) (((X^2 ) ^2 )^2) (((X2)2)2)三次运算即可完成,时间复杂度变为O(logN)。当然,由于N不等于2的整数次幂的情况比如 9 ,可以变成 8 + 1 相乘的形式, ( ( ( X 2 ) 2 ) 2 ) × X (((X^2 ) ^2 )^2) \times X (((X2)2)2)×X 所以时间复杂度会稍微变大一点点。
我当初看到这种算法时,真是佩服的不要不要的,算法思路清奇,由递推公式的计算和矩阵联系在一起,作为没有选修过线代的人,无法想象,两个字,神奇,太TM神奇了。
嘎吹完毕,接着解决第二个问题,C++超大数整数库。对于这种常规的问题,我总是第一个求助于boost,事实上boost确实由cpp_int类,也确实可以完成任务。
但是,效率,还是效率问题。boost明显不是一个专门的数学库,所以其效率可能会打折扣。不卖关子了,咱们继续。
通过各种百度,了解到GMP库,#include <gmpxx>,编译参数加上 -lgmp -lgmpxx,可以有令人惊叹的效率提升。
以下代码:
//矩阵快速幂生成超大数斐波那契数列
mpz_class bigFibonacciFast(int N)
{
assert(N >= 0);
if (N == 0)
{
return 0;
}
Liner::Mat<mpz_class> basic(2, 2);
basic(0, 0) = 1;
basic(0, 1) = 1;
basic(1, 0) = 1;
basic(1, 1) = 0;
//矩阵中的1就是[1,0][0,1],任何矩阵乘以它都是矩阵自身。
Liner::Mat<mpz_class> rest(2, 2);
rest(0, 0) = 1;
rest(0, 1) = 0;
rest(1, 0) = 0;
rest(1, 1) = 1;
while (N)
{
//当N为奇数,则有(basic)^(N-1)*basic, 将basic项乘给rest
//其余项为(basic^2)^((N-1)/2)*rest
//依次递推,如幂为奇数则将当时的basic^x拿出乘以rest,剩下的为偶数幂
//再将偶数幂除以2,依次类推,直至幂为1,将此时的basic^x乘以rest即可
if (1 & N) //位操作,判断是否为偶数
{
//初始rest相当于 “ 1 ”
rest = basic * rest;
}
basic = basic * basic;
//位操作,向右移动1位,相当于除以2
N >>= 1;
}
return rest(0, 1);
}
当然,上述代码缺了一个矩阵库,我是自己实现的,其实效率不高,就不贴出来献丑了,需要高效率的,请搜索专用的线性代数库,比如eigen3,openblas(我没用过,但我下过库),等等。