斐波那契数列及其对数时间算法
前些天做IEEE校内算法赛的时候,遇到了一道关于斐波那契数列的题,要求是对数时间;今天在牛课网上刷leetcode,看到爬楼梯问题,于是在网上搜索了一下,自己参考并总结了下斐波那契数列及其算法。主要参考了知乎这个问题下的回答最高赞回答。
斐波那契数列大家应该都很熟悉:0 1 1 2 3 5 8… ,递推公式如下:
f(n)={f(n−1)+f(n−2)if2≤n;f(0)=0,f(1)=1else.
f
(
n
)
=
{
f
(
n
−
1
)
+
f
(
n
−
2
)
i
f
2
≤
n
;
f
(
0
)
=
0
,
f
(
1
)
=
1
e
l
s
e
.
斐波那契数列的同向可以使用特征根法或者矩阵的乘积来解决:
f(n)=15√((1+5√2)n−(1−5√2)n).
f
(
n
)
=
1
5
(
(
1
+
5
2
)
n
−
(
1
−
5
2
)
n
)
.
下面介绍利用矩阵快速幂的对数时间的斐波那契数列算法:
首先从递推公式我们很容易得到:
[f(n+1)f(n)]=[1110][f(n)f(n−1)]=[1110]n[f(1)f(0)].
[
f
(
n
+
1
)
f
(
n
)
]
=
[
1
1
1
0
]
[
f
(
n
)
f
(
n
−
1
)
]
=
[
1
1
1
0
]
n
[
f
(
1
)
f
(
0
)
]
.
不妨设
A=[1110]
A
=
[
1
1
1
0
]
,那么我们的目标就是求矩阵A的n次幂。
在介绍矩阵的快速幂算法之前,我们先介绍一下数的快速幂算法,数的快速幂算法一般有递归和位运算两种:
//递归版本
int my_power(int x, int n) {
//n >= 0
if(n == 0) return 1;
return n % 2 ? x * my_power(x, n / 2) : my_power(x, n / 2);
}
//位运算版本
int my_power(int x, int n) {
//n >= 0
int result = 1;
while(n) {
if(n & 1) result *= x;
x *= x;
n >>= 1;
}
return result;
}
例如求
210
2
10
,
10=(1010)(2)
10
=
(
1010
)
(
2
)
,分二进制位上为1和为0两种情况讨论。
所以可以用同样的方法求矩阵的幂,我们声明了一个二阶矩阵类,只给了矩阵的四个元素、默认构造函数和重载operator*
:
//Definition for 2-dimension matrix
struct Matrix {
/*[m00, m01]
[m10, m11]*/
int m00, m01, m10, m11;
Matrix(int a = 0, int b = 0, int c = 0, int d = 0) :
m00(a), m01(b), m10(c), m11(d) { }
Matrix& operator*(const Matrix& rhs) {
//若要对大数取余,可以对下面的a,b,c,d取余运算。
int a = this->m00 * rhs.m00 + this->m01 * rhs.m10; //% mod
int b = this->m00 * rhs.m01 + this->m01 * rhs.m11; //% mod
int c = this->m10 * rhs.m00 + this->m11 * rhs.m10; //% mod
int d = this->m10 * rhs.m01 + this->m11 * rhs.m11; //% mod
this->m00 = a; this->m01 = b;
this->m10 = c; this->m11 = d;
return *this;
}
};
/*对数算法,利用矩阵的快速幂*/
Matrix fast_multi(int n) {
Matrix m(1, 1, 1, 0), result(1, 0, 0, 1);
while(n) {
if(n & 1) result = result * m;
m = m * m;
n >>= 1;
}
return result;
}
因此斐波那契数列的第n项的算法为:
//这里没有考虑int溢出
int Fibonacci(int n) {
Matrix m = fast_multi(n);
return m.m10;
}
若斐波那契数列的初值为f0和f1:
//这里没有考虑int溢出
int Fibonacci(int n, int f0, int f1) {
Matrix m = fast_multi(n);
return (m.m10 * f1 + m.m11 * f0);
}
最开始说到IEEE校内算法赛的那道题是要求斐波那契数列的前n项和:
Sn=f(n+2)−f(1);
S
n
=
f
(
n
+
2
)
−
f
(
1
)
;
这个结论用数学归纳法是非常容易证明的。