斐波那契数列由于在生活以及自然界中的诸多应用,在数学学科中占据着重要的地位。我们可以用如下递推公式来表示斐波那契数列$F$的第$n$项:
$$
F_n =
\begin{cases}
0,&n=0\
1,&n=1\
F_{n-1}+F_{n-2},&n \geq 2
\end{cases}
$$
本文将会介绍五种计算斐波那契数列的算法,最快的算法复杂度为$O(\log (n))$。首先,我们先来回顾一下在入门学习编程时都会提到的递归和迭代两种基本方法来计算斐波那契数列。为了简化说明,在接下来给出的代码中,我们均不考虑不合法输入的情况(例如$n$为负数)。
1 递归算法
递归计算斐波那契数列的Python代码如下:
def Fibonacci(n):
if n == 0:
return 0
if n == 1:
return 1
return Fibonacci(n - 1) + Fibonacci(n - 2)
递归的计算简洁并且直观,但是由于存在大量重复计算,实际运行效率很低,并且会占用较多的内存。
2 迭代算法
为了避免递归效率低下的问题,我们可以采用迭代循环计算斐波那契数列,时间复杂度为$O(n)$,空间复杂度为$O(1)$,Python代码如下:
def Fibonacci_iteration(n):
a, b = 0, 1
if n == 0:
return a
for _ in xrange(n - 1):
a, b = b, a + b
return b
3 公式算法
对于斐波那契数列这个常见的递推数列,其第$n$项的值的通项公式如下:
$$ F_n=\frac{1}{\sqrt{5}} \left[ \left(\frac{1+\sqrt{5}}{2}\right)^n-\left(\frac{1-\sqrt{5}}{2}\right)^n\right] $$
该公式可以通过构造等比数列或者特征方程法等方法求出,也可以利用数学归纳法进行证明,在这里不多介绍。如果将公式转化成Python代码则有以下程序:
def Fibonacci_formula(n):
root_five = 5**0.5
result = (((1 + root_five) / 2)**n - ((1 - root_five) / 2)**n) / root_five
return int(result)
该方法虽然看起来高效,但是由于涉及大量浮点运算,在$n$增大时浮点误差不断增大会导致返回结果不正确甚至数据溢出。由于这种不可靠性,一般不采用这种方法进行计算。
4 矩阵算法
为了正确高效的计算斐波那契数列,我们首先需要了解以下这个矩阵等式:
$$ \begin{bmatrix} F_{n+1} & F_n \ F_n & F_{n-1} \end{bmatrix} = \begin{bmatrix} 1 & 1 \ 1 & 0 \end{bmatrix} ^ n $$
为了推导出这个等式,我们首先有
$$ \left[\begin{smallmatrix} F_{n+1} \ F_n \end{smallmatrix}\right] = \left[\begin{smallmatrix} 1 & 1 \ 1 & 0 \end{smallmatrix}\right] \left[\begin{smallmatrix} F_{n} \ F_{n-1} \end{smallmatrix}\right] $$
随即得到:
$$ \left[\begin{smallmatrix} F_{n+1} \ F_n \end{smallmatrix}\right] = \left[\begin{smallmatrix} 1 & 1 \ 1 & 0 \end{smallmatrix}\right] ^n \left[\begin{smallmatrix} F_1 \ F_0 \end{smallmatrix}\right] $$
同理可得:
$$ \left[\begin{smallmatrix} F_n \ F_{n-1} \end{smallmatrix}\right] = \left[\begin{smallmatrix} 1 & 1 \ 1 & 0 \end{smallmatrix}\right] ^n \left[\begin{smallmatrix} F_0 \ F_{-1} \end{smallmatrix}\right] $$
所以,
$$ \left[\begin{smallmatrix} F_{n+1} & F_n \ F_n & F_{n-1} \end{smallmatrix}\right] = \left[\begin{smallmatrix} 1 & 1 \ 1 & 0 \end{smallmatrix}\right] ^ n \left[\begin{smallmatrix} F_1 & F_0 \ F_0 & F_{-1} \end{smallmatrix}\right] $$
又由于$F(1)=1$,$F(0)=0$,$F(-1)=1$,则我们得到了开始给出的矩阵等式。当然,我们也可以通过数学归纳法来证明这个矩阵等式。等式中的矩阵$$\begin{bmatrix} 1 & 1 \ 1 & 0 \end{bmatrix}$$被称为斐波那契数列的$Q$-矩阵。
通过$Q$-矩阵,我们可以利用如下公式进行计算$F_n$:
$$ F_n=\left(Q^{n-1}\right)_{1,1} $$
如此一来,计算斐波那契数列的问题就转化为了求$Q$的$n-1$次幂的问题。我们使用矩阵快速幂的方法来达到$O(\log(n))$的复杂度。借助分治的思想,快速幂采用以下公式进行计算:
$$ A^{n}= \begin{cases} A\cdot(A^{2})^{\frac{n-1}{2}},&{\mbox{if }}n{\mbox{ is odd}}\ (A ^{2})^{\frac {n}{2}},&{\mbox{if }}n{\mbox{ is even}} \end{cases} $$
采用递归可以在Python中快速实现上速算法:
def Fibonacci_matrix(n):
def mul(a, b):
return ((a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]),
(a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]))
def power(x, n):
if n == 1:
return x
ans = power(mul(x, x), n / 2)
if n % 2:
ans = mul(x, ans)
return ans
if n == 0:
return 0
if n == 1:
return 1
q = ((1, 1), (1, 0))
return power(q, n - 1)[0][0]
上述程序中mul
函数执行两个$2\times2$矩阵的乘法,power
函数递归计算矩阵的快速幂。为了进一步提高效率,我们修改power
函数舍弃递归转而采用循环来计算快速幂。修改后的Python代码如下:
def Fibonacci_matrix_iteration(n):
def mul(a, b):
return ((a[0][0] * b[0][0] + a[0][1] * b[1][0], a[0][0] * b[0][1] + a[0][1] * b[1][1]),
(a[1][0] * b[0][0] + a[1][1] * b[1][0], a[1][0] * b[0][1] + a[1][1] * b[1][1]))
def power(x, n):
if n == 1:
return x
y = ((1, 0), (0, 1))
while n > 1:
if n % 2:
y = mul(x, y)
x = mul(x, x)
n /= 2
return mul(x, y)
if n == 0:
return 0
if n == 1:
return 1
q = ((1, 1), (1, 0))
return power(q, n - 1)[0][0]
5 升级递归算法
对于斐波那契数列,我们还有以下这样的递推公式:
$$ \begin{split} F_{2n-1}&=F_n^2+F_{n-1}^2 \ F_{2n}&=\left(2F_{n-1}+F_n\right)\cdot F_n \end{split} $$
为了得到以上递归式,我们依然需要利用$Q$-矩阵。由于$Q^mQ^n=Q^{m+n}$,展开得到$F_mF_n+F_{m-1}F_{n-1}=F_{m+n-1}$,将该式中$n$替换为$n+1$可得$F_mF_{n+1}+F_{m-1}F_n=F_{m+n}$。在如上两个等式中令$m=n$,则可得到开头所述递推公式。利用这个新的递归公式,我们计算斐波那契数列的复杂度也为$O(\log(n))$,并且实现起来比矩阵的方法简单一些:
def Fibonacci_recursion_fast(n):
if n == 0:
return 0
if n == 1:
return 1
k = (n + 1) / 2 if n % 2 else n / 2
fib_k = Fibonacci_recursion_fast(k)
fib_k_1 = Fibonacci_recursion_fast(k - 1)
return fib_k**2 + fib_k_1**2 if n % 2 else (2 * fib_k_1 + fib_k) * fib_k
6 总结
图1中展示了随着$n$的不断增大,四种方法计算在计算斐波那契数列的第$n$项所需的时间。为使结果更准确,每次计算都被重复了10次。可以看出,在这几种方法中,利用矩阵结合快速幂算法来计算斐波那契数列的表现较为突出。
除此之外,本为中谈到的几种算法同样也可以扩展到除斐波那契数列外的其它线性递归数列。
Comments