序言
在网易公开课《麻省理工-算法导论》的视频课程中,分治算法讲解了斐波那契数列。对于斐波那契数列,简单来看,不就是一个简简单单的计算吗,好像也没有什么深度,但是从应用和算法上开仔细琢磨,还是有很多有意思的地方。
斐波那契作为模型
斐波那契最重要的当然是应用,作为一些应用的模型。最常见的是动态规划中的应用,例如最经典的上楼梯的例子,有N阶楼梯,一个小朋友上楼,他只能一次走一阶或者走两阶,问有多少种不同的走法。
对于这个题目,是可以比较自然的联想到用动态规划的思路的,从最后一阶考虑,到达最后一阶可以有走一步到达或者走两步到达两种方式,所以f(n) = f(n-1) + f(n-2)。很明显,这就是一个斐波那契数列,得到了动态规划公式,按照动态规划的思路继续完成编码。
斐波那契的计算
递归
先用代码描述:
def fib(n):
if n <= 1: return 1
return fib(n-1) + fib(n-2)
这个算法通过递归树分析,其计算复杂度T(n) = O(2^n)
按照这个思路继续,怎么提升效率,因为递归树展开后有大量的重复计算,所以考虑进行缓存,缓存后其时间复杂度T(n) = O(n),而空间复杂度由O(1)上升到O(n)
from functools import lru_cache
@lru_cache(maxsize=1000)
def fib(n):
if n <= 1: return n
return fib(n-1) + fib(n-2)
这里直接使用lru_cache缓存,但是这里maxsize需要设置的和n一样大才能达到O(n)的复杂度。
迭代
在SICP的第一章中,讲解了fib的计算,所有能够用递归(recursion)完成的,都可以用迭代(iteration)的方式完成。
代码如下:
def fib(n):
if n <= 1: return n
a, b = 0, 1
for k in range(2, n):
a, b = b, a + b
return b
这个算法时间复杂度是T(n) = O(n),比上述缓存的算法节省了空间复杂度O(n),肯定是优于递归的做法的。
通项公式
通项公式是从数学的角度来发掘的
这个通项公式好像挺牛逼的,从字面上看是T(n) = O(n)的时间复杂度,但是为何很少用,主要是因为这里有浮点数计算,浮点数计算CPU指令速度是小于整数加法计算的,肯定是没有迭代的方式计算的快。那么还有没有更好一点的做法呢。
矩阵
回到序言中,麻省理工的公开课中,讲解的就是矩阵的做法。离散数学中,矩阵是基本的工具,是集合关系的一种表示。
斐波那契公式,通过矩阵形式表达
继续推导可以得到
通过上述推导,斐波那契数列转换成了矩阵的阶乘计算
按照这个思路进一步的优化,阶乘有阶乘的优化,可以提前把2次方、n次方计算出来缓存起来,这样可以节省计算,获取更高更快性能。
小结
一个简单的斐波那契数列,演变出来真是让人惊叹,从数学的角度,有演变出通项公式和矩阵的算法,实际上递归和迭代是最基础的数学了。我们得出两个结论,一是数学是算法的基础,而是学习算法很有趣。