0. 问题描述
在数学当中,由斐波那契数字(Fibonacci number,记作
)构成的序列,被称为斐波那契数列(Fibonacci sequence)。该数列中的每一个数字等于排在它前面的两个数字之和。
- 数列从0和1开始:
,
- 数列第n个(n>1)数字为:
按照上述公式,计算得到斐波那契数列为:
随着n的增大,斐波那契数列增长比例逐渐趋近于黄金分割比例。
本文将尝试使用三种时间复杂度不同的算法求解该序列的第n个数字,分别为:
- 递归法:时间复杂度
- 循环法:时间复杂度
- 矩阵连乘法:时间复杂度
(?有待商榷)
1. 递归解法
从斐波那契数列的计算公式:
,可自然想到该问题适合使用递归法求解,下面直接给出python代码。
def
时间复杂度分析
细心的读者应该能观察到递归求解的过程如同构造一棵二叉树,例如求解
依赖
和
,我们把
作为树的根节点,
和
作为左右两个叶子节点,继续向下递归,左节点
继续向下分解为
和
,右节点
继续向下分解为
和
,依此类推,如下图所示:
因此,该算法的时间复杂度为
。
性能优化
指数级的复杂度太高,在
较大时响应时长无法接受,幸好存在优化的空间。从上面的二叉树中可以发现,存在大量重复的节点。优化的方法是把计算过的结果存放在字典中,每次计算前查看字典中是否已经存在相同的节点,如果有则直接返回,否则将此新节点存放在字典中,优化后的程序如下。
dic
优化后的程序相当于给之前的递归树做了剪枝操作,相同的节点仅执行一次,因此时间复杂度降为
。
2. 循环解法
如果说前面的递归解法是自顶向下将大问题拆解成小问题求解,那么循环解法则是逆向思维,自底向上,先求出小问题的解,再向上一步一步向上求取最终问题的解。
求解过程分为
步,将每一步的结果保存在列表中对应下标的位置,代码如下。
def
时间复杂度分析
单层循环,时间复杂度为
,与优化后的递归解法复杂度相当。
性能优化
时间复杂度已经没有优化空间了,但可以使用两个临时变量替换掉长度为
的列表,使空间复杂度从
降为
。代码如下:
def
3. 矩阵连乘法
根据斐波那契数列自身的性质,我们可以构造如下方等式关系:
使用矩阵表示上述等式关系,即
那么
依次乘下去,可得一般形式
我们要求解的
即为向量
的第一个分量。代码如下:
import
时间复杂度分析
从程序上看,结果result几乎是一条语句返回的,这样看时间复杂度为
。但语句涉及到乘方运算,即n个矩阵相乘,时间复杂度似乎又是
。这一结论有待商榷。
性能优化
将矩阵对角化后,再进行连乘操作,可大大提升运算效率。对角化方法为
,其中
是要被对角化的矩阵,
为由
的特征向量组成的矩阵,
为由
的特征值构成的对角矩阵。
,python代码如下:
import
问题
NumPy在处理矩阵乘法时会进行大量的浮点运算,在
时,结果的精度有损失。
4 小结
- 经实际运行测试,不使用字典的递归算法在
时几乎慢到不可用的程度。
- 使用字典优化后的递归算法与循环算法性能相当,使用
测试运行时长大概在万分之一秒。
- 矩阵连乘算法虽看似简单,但运行效率不如循环算法,运行时长大概在千分之一秒,且矩阵对解化操作并没有带来明显的性能提升。
- 最终的运行效率排序为:循环法
递归(带字典)矩阵连乘递归(不带字典)。
参考
[1] WikiPedia: Fibonacci number
[2] MIT线性代数: 矩阵对角化(网易公开课)