关注微信公众号“酸痛鱼”,获得更多最新最全的文章。
本文中所涉及的代码,在未特殊声明的情况下,都是基于Python3程序设计语言编写的。
建议您在PC浏览器中阅读本文,以获得更好的阅读体验。
如果您未掌握知识提要中的内容,建议您先掌握这些内容之后再阅读本文。
知识提要
1、斐波那契函数
2、函数的定义、递归
3、时间复杂度、空间复杂度
4、作用域、关键字global
0
什么是斐波那契函数
斐波那契数列,是指这样一个数列:
1、1、2、3、5、8、13、21、24、55……
其特点是,n>=3时,第n项的值为第n-2项和第n-1项的和。我们用f(n)表式其递推公式,即第n项的值,则:
f(n) = f(n-2) + f(n-1) ; n>=3, f(1)=1, f(2)=1
我们将这个递推公式称为斐波那契函数。
1
递归方式实现斐波那契函数
斐波那契函数的定义是递归定义,我们很容易想到递归的方式。话不多说,我们直接看代码。
def fib(n):
# 递归结束条件
# 为了处理方便,n <= 0 时,直接返回 1
if n <= 2:
return 1
return fib(n - 1) + fib(n - 2)
没错,四行代码,我们就实现了一个算法题目。但这个代码的效率是非常低的。
使用递归的方式,会出现很多重复的运算。例如,当我们计算fib(5)的时候,会递归调用fib(4)和fib(3),其中fib(4)又会重复计fib(3)。
事实上,这种实现方式的时间复杂度O(2^n),空间复杂度为O(n)。
巧合的是,对于一次fib(n)调用,假设其实返回值为An,函数fib被递归调用的次数为2 * An - 1。我们可以写个代码验证一下。至于为什么有这个巧合,感兴趣的读者可以运用数学的方式推算一下。
calls = 0 # 全局变量,用于记录fib被调用的次数
def fib(n):
global calls # 声明使用全局的calls,而非重新创建局部的变量
calls += 1
if n <= 2: return 1
return fib(n - 1) + fib(n - 2)
def test(n):
global calls
calls = 0
an = fib(n)
print("n:{}, fib(n):{}, calls:{}".format(n, an, calls))
if __name__ == "__main__":
test(1)
test(2)
test(3)
test(10)
test(20)
test(30)
以上代码的运行结果如下:
n:1, fib(n):1, calls:1
n:2, fib(n):1, calls:1
n:3, fib(n):2, calls:3
n:10, fib(n):55, calls:109
n:20, fib(n):6765, calls:13529
n:30, fib(n):832040, calls:1664079
2
线性方式实现斐波那契函数
斐波那契数列的特点是,第N项的值为第N-1项和第N-2项的和。同理,N+1项的值为第N项和N-1项的和。
所以,在程序实在的层面上,我们可以通过不停的记录N和第N-1,来求得新最新的第N项的和。通过这种方式,我们可以时间复杂O(n)的方式实现斐波那契函数,同时,其实空间复杂度也仅为O(1)。
def fib(n):
an_1 = 1 # 第n-1项
an = 1 # 第n项 (初始为第2项)
# 至少从第3轮才循环迭代
i = 3
while i <= n:
i += 1
# 左边为最新值,右边为上一轮的值
an_1, an = an, an + an_1
return an
3
通项公式
斐波那契数列的通项公式为:
理论上,根据通项公式实现斐波那契函数,时间复杂度为O(1)。但事实上,如果我们按照这个公式去写代码,并不能得到正确的解。这是因为计算在处理浮点数的时候,并不总能保证其实精度。
计算机领域,有一个学科叫《科学计算》,就涉及到浮点数精密计算的问题,感兴趣的读者可以去了解一下。微信扫码关注我嗄嘎嘎
酸痛鱼,与你分享快乐的代码