1、斐波那契数列
斐波那契数列(意大利语:Successione di Fibonacci),又译为菲波拿契数列、菲波那西数列、斐氏数列、黄金分割数列。
在数学上,斐波那契数列是以递归的方法来定义:
用文字来说,就是斐波那契数列由0和1开始,之后的斐波那契数就是由之前的两数相加而得出。首几个斐波那契数是:
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233……(OEIS中的数列A000045)
特别指出:0 不是第一项,而是第零项,第一项是第一项!!
- 以斐波那契数为边的正方形拼成的近似的黄金矩形(1:1.618)
- 以斐波那契数为半径的圆拼成的近似的黄金螺旋螺旋(1:1.618)
2、斐波那契数列之源起
2.1 兔子学说
公元1150年印度数学家Gopala和金月在研究箱子包装对象长宽刚好为1和2的可行方法数目时,首先描述这个数列。在西方,最先研究这个数列的人是比萨的列奥那多(意大利人斐波那契Leonardo Fibonacci),他描述兔子生长的数目时用上了这数列:
- 第一个月初有一对刚诞生(先得长大两月,第三月才能生)的兔子
- 第二个月之后(第三个月初)它们可以生育
- 每月每对可生育的兔子会诞生下一对新兔子
- 兔子永不死去
如下图,兔子对 的数量 就是斐波那契数列。值得注意的是,假设兔子是永不会死去的,其中带红蓝点的兔子代表可以生小兔,反之不能。(兔兔在诞生的第三个月才能生小兔)
假设在n月有兔子总共a对,n+1月总共有b对。在n+2月必定总共有a+b对:因为在n+2月的时候,前一月(n+1月)的b对兔子可以存留至第n+2月(在当月属于新诞生的兔子尚不能生育)。而新生育出的兔子对数等于所有在n月就已存在的a对
如下图,斐波纳契数是帕斯卡三角形的每一条红色对角线上数字的和
2.2 数列表达式
为求得斐波那契数列的一般表达式,可以借助线性代数的方法。高中的初等数学知识也能求出。
3、斐波那契数列之编程
需求:用三种方法实现斐波那契数列的第1-10项,注意这里不包括第0项。
3.1 方法一:循环实现 Loop
coding:
def fibonacci_loop(n):
first, second, count, fib = 0, 1, 2, []
if n == 1:
fib.append(second)
else:
fib.append(second)
while count <= n:
tmp = second
second = first + second
first = tmp
fib.append(second)
# 更新值
count += 1
return fib
res_fib = fibonacci_loop(10)
print(res_fib)
输出:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
3.2 方法二:生成器实现 Genarator
coding:
def fibonacci_genarator(n):
n1, n2, count = 0, 1, 2
yield n2
while count <= n:
tmp = n2
n2 = n1 + tmp
n1 = tmp
yield n2
count += 1
res = fibonacci_genarator(10)
print(list(res))
输出:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
review
def fib_get(n):
first, second, count = 0, 1, 2
while count < n+2:
yield second
first, second = second, first + second
count += 1
ret = fib_get(10)
print(list(ret))
输出:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
3.3 方法三:递归实现 Recursion
coding:
def fibonacci_recursion(n):
if n == 1 or n == 2:
return 1
else:
return fibonacci_recursion(n-1) + fibonacci_recursion(n-2)
res = [fibonacci_recursion(n) for n in range(1, 11)]
print(res)
输出:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
3.4 上述三种方法耗时PK
需求:分别用循环、生成器、递归来实现第1到第40项的斐波那契数列,对比耗时情况,猜猜看哪个耗时最久?
from functools import wraps
import time
# 计时装饰器
def timmer(func):
@wraps(func)
def inner(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
end_time = time.time()
print(f'本次{func.__name__}函数执行共耗时{int(end_time-start_time)}s')
return res
return inner
@timmer
def fibonacci_loop(n):
first, second, count, fib = 0, 1, 2, []
if n == 1:
fib.append(second)
else:
fib.append(second)
while count <= n:
tmp = second
second = first + second
first = tmp
fib.append(second)
# 更新值
count += 1
return fib
@timmer
def fibonacci_genarator(n):
n1, n2, count = 0, 1, 2
yield n2
while count <= n:
tmp = n2
n2 = n1 + tmp
n1 = tmp
yield n2
count += 1
def fibonacci_recursion(n):
if n == 1 or n == 2:
return 1
else:
return fibonacci_recursion(n-1) + fibonacci_recursion(n-2)
if __name__ == '__main__':
# 循环
res_fib = fibonacci_loop(40)
print(res_fib)
# 生成器
res = fibonacci_genarator(40)
print(list(res))
# 递归
start_time = time.time()
res = [fibonacci_recursion(n) for n in range(1, 41)]
print(res)
end_time = time.time()
print(f'本次{fibonacci_recursion.__name__}函数执行共耗时{int(end_time-start_time)}s')
如下,递归耗时最久,竟然耗时50秒,循环和生成器耗时不到1秒,所以说递归需慎用~