用程序算斐波那契数列最常见的就是迭代与递归算法。
为了评价各算法程序效率如何
安装 profiler 来计时和能看到每行的运行次数
先在系统下输入命令pip install line_profiler安装
使用也很简单,在要计时的函数定义前加上@profile这个装饰符
然后在系统下输入命令kernprof -l -v 被计时的源程序.py
开始看第一个程序,输入参数n算第n项的Fibonacci数。以下皆是在Python 3.7环境下运行
1. 非递归的迭代解法
def Fibonacci_sequence_01 (n: int) -> int: #参数n是表示求第n项Fibonacci数
'返回单项的for迭代解法'
assert isinstance(n, int), 'n is an error of non-integer type.'
if n>=2:
prev_num, current_num = 0, 1
for i in range(2, n+1):
prev_num, current_num = current_num, prev_num+current_num
return current_num
elif n==1:
return 1
elif n==0:
return 0
else:
return None
Fibonacci_sequence_01(1200)
用算到第1200项Fibonacci数来测量下用时
在我的电脑上Total time: 0.002855秒
2. 典型的递归解法,算法可读性很好
def Fibonacci_sequence_02 (n: int) -> int: #参数n是表示求第n项Fibonacci数
assert isinstance(n, int), 'n is an error of non-integer type.'
def Calculate_Fibonacci_sequence (n: int) -> int:
'返回单项的递归解法'
if n>=2:
return Calculate_Fibonacci_sequence(n-1) + Calculate_Fibonacci_sequence(n-2)
elif n==1:
return 1
elif n==0:
return 0
if n>=0:
Calculate_Fibonacci_sequence(n)
else:
return None
然而,时间复杂度是:O(1.618 ^ n),没有实用价值。这个时间复杂度的详解见“算法的时间复杂度”这篇文章
3. 尾递归解法
递归解法的一个缺点是递归调用会一层一层压栈,占用栈空间达O(n)。特别是Python默认只设1000层,超过就抛出异常了
尾递归算法相当于迭代的变形,理论上尾递归不需要保留调用前信息,栈空间只需要O(1)。但是Python没有针对尾递归形式做识别,尾递归调用时还是有一层一层压栈动作,超出默认栈深度就不行了。对于不支持尾递归优化的语言,这时要用一个叫“蹦床(Trampoline)”的技巧
“蹦床”也就是对递归函数进行包装,蹦床拦截了传给递归函数的参数,递归函数代码的加载过程都透过这个蹦床。每次的递归调用,实际都被蹦床转化为蹦床去拿拦截的参数调用一次递归函数,这样栈空间占用一直为O(1)
来看装饰器样式,样子如:
先对尾递归函数加上装饰器
@proper_tail_call
def Fibonacci_sequence(n):
然后使用时就是原样不变的函数调用样式
Fibonacci_sequence(n)
好了,下面就是完整的尾递归装饰器及尾递归解法斐波那契数
import functools
import inspect
class TailCallException(Exception):
def __init__(self, *args, **kwargs):
self.args = args
self.kwargs = kwargs
def proper_tail_call(func):
'用于return式尾递归的蹦床装饰器代码。使用方法:在定义尾递归函数语句前一行写装饰器“@proper_tail_call”'
@functools.wraps(func)
def wrapper(*args, **kwargs):
frame = inspect.currentframe()
if frame.f_back and frame.f_back.f_back and frame.f_code ==frame.f_back.f_back.f_code: #先判断当前是否为递归调用(递归的话是_wrapper->被装饰函数->_wrapper),再判断是否存在前级和前前级调用
raise TailCallException(*args, **kwargs)
else:
while True:
try:
return func(*args, **kwargs)
except TailCallException as e:
args = e.args
kwargs = e.kwargs
return wrapper
def Fibonacci_sequence_03 (n: int) -> int: #参数n是表示求第n项Fibonacci数
assert isinstance(n, int), 'n is an error of non-integer type.'
@proper_tail_call
def Calculate_Fibonacci_sequence (n: int, prev_num: int =0, current_num: int =1) -> int:
'返回单项的return式尾递归解法'
if n>=2:
return Calculate_Fibonacci_sequence(n-1, current_num, prev_num+current_num)
elif n==1:
return current_num
if n>=1:
return Calculate_Fibonacci_sequence (n)
elif n==0:
return 0
else:
return None
Fibonacci_sequence_03(1200)
还是用算到第1200项Fibonacci数来测量下用时,Total time: 0.011484秒
未完待续……