fibnacci数列1,1,2,3,5,8,13,21...,数列第一项和第二项等于1,从第三项开始开始,每一项都等于前两项之和,简单的用数学公式表示:
n=0或n=1时,f(n)=1;
n>1时,f(n) = f(n-1) + f(n-2)
可以看出使用递归算法可解决该问题,python代码如下:
def fibnacci_I(x):
if x < 0:
return 0
if x == 0 or x == 1:
return 1
return fibnacci_I(x-1) + fibnacci_I(x-2)
执行fibnacci_I(20),结果为10946。似乎一切正常。但是当x的值很大时,程序会出现运行错误:RuntimeError: maximum recursion depth exceeded,最大递归深度溢出,这是因为python中递归深度大于等于1000时就会报错。即使python不做限制,当x很大时,程序需要耗费很长的时间才能计算完成,这里就涉及到linux堆栈的问题。因为linux系统为计算机分配的堆栈大小有限制(使用ulimit -a可查看堆栈大小),而程序运行时会将程序放入堆栈,每调用一次递归就会分配一次堆栈,一个堆栈的大小为8K,我的虚拟机的堆栈大小为8092k,可以看出很快就会把堆栈用尽,所以时间复杂度就很大。该算法的时间复杂度为O(2^n)。
仔细看fibnacci数列,可以发现有很多重复运算,比如f(5) = f(4) + f(3);f(4) = f(3) + f(2);这里对f(3)做了重复计算,那么可以对算法做改进,将重复的计算结果缓存起来,用空间来换取时间。改进后的程序代码如下:
cache = {0:1,1:1}
def fibnacci_II(x):
global cache
if x < 0:
return 0
if x == 0 or x == 1:
return 1
if x in cache:
return cache[x]
result = fibnacci_II(x-1) + fibnacci_II(x-2)
if x not in cache:
cache[x] = result
return result
实际运行一下可以看出改进后的程序比原有程序效率高很多。因为对中间结果做了保存,所以实际上是用空间来换取时间。
似乎已经很好了,还有更好的解法吗?答案是肯定的,使用循环可将算法改进为非递归算法,因为f(x) = f(x-1) + f(x-2),那么我们可以在计算f(x-1)和f(x-2)时将结果保存下来,那么计算f(x)的时候就可以直接得到结果了,依次类推,可以用一个for循环解决。该算法的代码如下:
def fibnacci_III(x):
if x < 0:
return 1
if x == 0 or x == 1:
return 1
result = 0
a = 1
b = 1
for i in xrange(x):
if i > 1:
result = a + b
a = b
b = result
return a + b
总结,递归算法的特点是清晰明了,但是递归算法的时间复杂度很大,意味着计算时间会很长,而改为非递归算法后,可以很好的减少时间复杂度。写程序时应该尽量避免递归,将递归算法改为非递归算法。