背景:今天看网课提到了斐波那契数列使用递归效率过低的问题,于是自己写代码使用递归求第100项,出去吃完早饭回来还没跑完。。。好吧,需求来了,我们来开始优化吧!😅
1、原代码
def fibonacci(n):
if n == 1 or n == 2:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
2、改进
两种办法,本质上都是把先前函数运行的结果存起来,下一次需要调用时候直接使用。
第一种是装饰器,args[0]即是被装饰函数的参数(因为fibonacci函数只有一个参数,故可以这么写,也可以用关键字参数的方式,或者直接命名为n,都是可以的),可以看到每次被装饰函数被调用后都会把结果以 param:result 的方式存与字典,后续调用可以直接从字典获取,从而避免了重复的计算。
from functools import wraps
def cache(func):
d = dict()
@wraps(func)
def wrapper(*args):
if args[0] not in d:
res = func(args[0])
d[args[0]] = res
return d[args[0]]
return wrapper
第二种是使用队列,每次计算时将队头两个元素出队,计算其和,即为本次运行的结果。最后将上一次运算结果和本次结果追加到队尾,作为下一次运算的参数,执行n-2次后,队尾巴元素即为所求。
from collections import deque
def fibonacci_no_recursion(n):
q = deque([1, 1])
if n <= 2:
return q.pop() + q.pop()
for i in range(n - 2):
a1 = q.popleft()
a2 = q.popleft()
q.append(a2)
q.append(a1 + a2)
return q.pop()
3、运行
可见非递归最快,递归加缓存的次之,二者在输入为30时的耗时尚在同一量级,而改进前的则是成千上万倍的效率差距,随着数字增大,这个差距会呈指数级别的增大。
4、完整代码
from collections import deque
from functools import wraps
import time
def cache(func):
d = dict()
@wraps(func)
def wrapper(*args):
if args[0] not in d:
res = func(args[0])
d[args[0]] = res
return d[args[0]]
return wrapper
def time_delta(func):
@wraps(func)
def wrapper(*args, **kwargs):
t1 = time.time() * 1000
res = func(*args, **kwargs)
t2 = time.time() * 1000
print(': '.join([func.__name__, str(t2 - t1) + 'ms']))
return res
return wrapper
# 原始递归数列
def fibonacci_2(n):
if n == 1 or n == 2:
return 1
else:
return fibonacci_2(n - 1) + fibonacci_2(n - 2)
@time_delta
def fibonacci_original(n):
return fibonacci_2(n)
# 加缓存的递归数列
@cache
def fibonacci(n):
if n == 1 or n == 2:
return 1
else:
return fibonacci(n - 1) + fibonacci(n - 2)
@time_delta
def fibonacci_with_cache(n):
return fibonacci(n)
# 队列实现的非递归数列
@time_delta
def fibonacci_with_queue(n):
q = deque([1, 1])
if n <= 2:
return q.pop() + q.pop()
for i in range(n - 2):
a1 = q.popleft()
a2 = q.popleft()
q.append(a2)
q.append(a1 + a2)
return q.pop()
if __name__ == '__main__':
num = int(input("请输入n: "))
print('result: ', fibonacci_original(num), end='\n\n')
print('result: ', fibonacci_with_cache(num), end='\n\n')
print('result: ', fibonacci_with_queue(num))