斐波那契额数列有如下定义:F(n)=F(n-1)+F(n-2) n>1F(n)=1 n=1F(n)=0 n=0针对这个问题我想大多数初学算法的人来说,包括我一开始接触的时候都可能一开始想到的解决思路当然是使用递归的思想来解决这个问题。构造一个递归的方法来求出对应n的数列和。参考了网上的一些方法之后我们在这里给出一个很简单易懂的使用递归算法来实现斐波那契数列的方法。
```def Fibonacci(n):
if n<=1:
return n
else:
return(Fibonacci(n-1) + Fibonacci(n-2))
nterms = int(input("Fibonacci输出第几项"))
if nterms <= 0:
print("输入正数")
else:
print("菲波那切数列:")
for i in range(nterms):
print(Fibonacci(i))
if __name__ == '__main__':
pass
```
这里的话没什么好说的很基本的python语法针对定义我们直接使用来进行求和。但是,这里要提到的是稍微接触过算法的同学们可能都知道时间复杂度和空间复杂度的概念。这样递归求和的时间复杂度是和你的遍历树梯度成正比的,如果你尝试把输入的i置成400以上的话你可以看到输出的结果是非常慢的。这里我们可以稍微解释一下。假如我们要求F(10)
在计算F(10)的时候我们要计算F(8)和F(9),以此类推这样要重复计算的值非常多,而且要计算的数量随着N的增大而急剧增大,这个时候实际上它的时间复杂度是随着N的值呈一个指数增长。
那么现在我们能够想到比较好的办法就是在数列每进行一次运算操作的时候我们为什么不可以将上次计算操作的数用一个临时变量保存下来么。这样我们求值就变成了从第一项开始不断的累计下去。由于是从第一项逐个求解这样该算法的时间复杂度为O(n)。下列给出代码片段大家来感受下。(初次写博客很多功能还不会用,还请多多见谅)
def fib2(n):
if n in know:
return know[n]
res = fib2(n - 1) + fib2(n - 2)
know[n] = res
return res
学过数据结构的朋友们可能会对下面这种方式不会感到陌生。
def fib(n):
x,y=0,1
while(n):
x,y,n=y,x+y,n-1
return x
这里也是从树形的递归改为了迭代,相应的效率也会提高不少。
从上面的代码中我们可以看到一定的迭代规律,[x,y] --> [y,x+y]。声明一个二元向量[x,y]T,它通过一个变换得到[y,x+y]T,可以很容易得到变换矩阵是[[0,1],[1,1]],也就是说:[[0,1],[1,1]]*[x,y]T=[y,x+y]T
令二元矩阵A=[[0,1],[1,1]],二元向量x=[0,1]T,容易知道Ax的结果就是下一个Fibonacci数值,即:Ax=[fib(1),fib(2)]T
亦有:
Ax=[fib(2),fib(3)]T
………………
以此类推,可以得到:
Aⁿx=[fib(n),fib(n-1)]T
也就是说可以通过对二元向量[0,1]T进行n次A变换,从而得到[fib(n),fib(n+1)]T,从而得到fib(n)。
import datetime
import numpy
def fib(n):
def m1(a,b):
m=[[],[]]
m[0].append(a[0][0]*b[0][0]+a[0][1]*b[1][0])
m[0].append(a[0][0]*b[0][1]+a[0][1]*b[1][1])
m[1].append(a[1][0]*b[0][0]+a[1][1]*b[1][0])
m[1].append(a[1][0]*b[1][0]+a[1][1]*b[1][1])
return m
def m2(a,b):
m=[]
m.append(a[0][0]*b[0][0]+a[0][1]*b[1][0])
m.append(a[1][0]*b[0][0]+a[1][1]*b[1][0])
return m
return m2(reduce(m1,[[[0,1],[1,1]] for i in range(n)]),[[0],[1]])[0]
if __name__ == '__main__':
print(datetime.datetime.now())
print fib(900)
print(datetime.datetime.now())
reduce内建函数,一个二元操作函数,用传给reduce中的函数func()(必须为一个二元操作函数)先对集合中的第1,2个数据进行操作,得到的结果再与第三个数的数据用func()函数运算最后得到一个结果。在这句话最后为什么会有个[0],因为我们要拿到的当然是fib(n)这个数啦。以为整个操作运算量体现在对[[0,1],[1,1]]的降阶上所以该算法的时间复杂度是线性的O(n)。
其实这个真的就是最优解了么,让我们回到最初对这个题目的定义上去。我们可以构造这样一个矩阵:
1.当N=2的时候我们可以得到:
2.当N=K的时候则有公式:
那么我们不能用数学归纳法得出这样一个式子,当N=K+1的时候等式两边同时乘以[[1,1],[1,0]]:
我们能够得到N=K+1的形式,这样由数学归纳法我们可以得出这个等式是成立的。这样实质上问题就转化为了对[[1,1],[1,0]]的N阶矩阵求它的幂运算问题。我们可以用分治算法的思想来考虑一下求幂运算的问题。
在这里我们可能需要用到python的科学工具包numpy,这也就是为什么我上一遍博客会花那么长的时间没头没尾的来troubleshooting一个导包的问题。
由于只进行矩阵幂运算所以时间复杂度下降到了lgn,这里给出一个ACM上的功能块来供大家欣赏一下算法的魅力:
def fib(n):
lhm=[[0,1],[1,1]]
rhm=[[0],[1]]
em=[[1,0],[0,1]]
#multiply two matrixes
def matrix_mul(lhm,rhm):
#initialize an empty matrix filled with zero
result=[[0 for i in range(len(rhm[0]))] for j in range(len(rhm))]
#multiply loop
for i in range(len(lhm)):
for j in range(len(rhm[0])):
for k in range(len(rhm)):
result[i][j]+=lhm[i][k]*rhm[k][j]
return result
def matrix_square(mat):
return matrix_mul(mat,mat)
#quick transform
def fib_iter(mat,n):
if not n:
return em
elif(n%2):
return matrix_mul(mat,fib_iter(mat,n-1))
else:
return matrix_square(fib_iter(mat,n/2))
return matrix_mul(fib_iter(lhm,n),rhm)[0][0]
在整个研究过程中还是借鉴了Jmilk的一些基础文档作为参考,附上地址:http://blog.youkuaiyun.com/jmilk/article/details/49657569