斐波那契数列,说起来大家都很熟悉,在练习编程的时候,也会经常被拿来作为例子
递归
这也是最常见的解法了,在最开始学习编程的时候,老师经常用这个例子。
因为他的定义就是递归的,用它可以显而易见地提出递归的思想,
n=0和n=1
作为递归基,其他的数分别递归求解
fn=fn−1+fn−2
。
int fib(int n)
{
if(n == 0) return 0;
if(n == 1) return 1;
return fib(n-1) + fib(n-2);
}
动规
但是很容易地就会发现,这个思路十分清晰的算法太慢了。因为他们中的很多值在被重复地计算,例如 f5=f4+f3 , f4=f3+f2 , f3 就被重复地计算了,重复的部分增加的非常快,一旦当 n 大于 40 多时,就已经很难进行计算了。
这里我们可以利用动规的思想来进行计算,复杂度都能降到
记忆化
也是最简单的,可以先开一个数组,并且在每次计算出结果后,保存下来,就可以避免重复计算。
int f[10000];
int fib(int n)
{
if(f[n]) return f[n];
if(n == 0) return f[0] = 0;
if(n == 0) return f[1] = 1;
return f[n] = fib(n-1) + fib(n-2);
}
递推
从上面的定义方程就可以看出来,每一个值只与前面的数有关于后面的数无关,这样就可以从前往后依次计算(问题比较简单,想法太顺理成章了,动规得太不明显了,但的确是动规)
int fib(int n)
{
int f[3] = { 0, 1, 1}
if(n<=2) return f[n];
for(int i=3; i<=n; i++)
{
f[0] = f[1];
f[1] = f[2];
f[2] = f[1] + f[0];
}
return f[2];
}
当然平时做题时还是会开一个数组进行保存,以后查询时就是 O(1) 的复杂度了。
int f[10000];
int init()
{
f[0] = 0;
f[1] = 1;
for(int i=2; i<10000; i++) f[i] = f[i-1] + f[i-2];
}
int fib(int n)
{
return f[n];
}
快速幂
上面的算法,已经达到了
O(n)
的复杂度,已经是非常优秀的了,但是当我们需要求解很大的答案是,例如 1000000000 之类,那么也是很耗费时间的,或者说我们需要查询的次数比较少,比较分散,那么我们浪费的空间就比较大。下面利用矩阵就是另外一种求解的方法。
利用矩阵的性质我们可以得到下面的结论
而我们令 n=1, 则
综上,我们可以得出这样的结论
理论有了,我们就可以类推数的快速幂来计算矩阵的快速幂。
// 数的快速幂
int quick_pow(int a, int n)
{
int re = 1;
int base = a;
while(n)
{
if(n&1) re *= base;
base *= base;
n >>= 1;
}
return n;
}
矩阵快速幂,需要自己写矩阵的乘法,然后直接套用数的快速幂的模板就行
// 矩阵快速幂
struct matrix
{
int data[2][2];
// 矩阵乘法
matrix operator*(matrix & rhs)
{
matrix temp;
for(int i=0; i<2; i++)
{
for(int j=0; j<2; j++)
temp.data[i][j] = ( data[i][0]*rhs.data[0][j]
+ data[i][1]*rhs.data[1][j]) % mod;
}
return temp;
}
};
int solve(int n)
{
// 初始化为单位矩阵
matrix re;
for(int i=0; i<2; i++)
for(int j=0; j<2; j++) re.data[i][j] = (i==j);
matrix base;
for(int i=0; i<2; i++)
for(int j=0; j<2; j++) base.data[i][j] = 1;
base.data[1][1] = 0;
// 数的快速幂核心代码
while(n)
{
if(n&1) re = re * base;
base = base * base;
n >>= 1;
}
return re.data[0][1];
}