python 递归优化

斐波那契数列

说到递归问题,大家应该都知道这个经典案例:斐波那契数列

0, 1, 1, 2, 3, 5, 8, 13, 21…

其递归式为 f ( n ) = f ( n − 2 ) + f ( n − 1 ) f(n) = f(n-2) + f(n-1) f(n)=f(n2)+f(n1)

python 实现

def fib1(n: int) -> int:
    if n < 2:  # base case
        return n
    return fib1(n - 2) + fib1(n - 1)  # recursive case

看似问题已经解决了,但是作为一个优秀的程序员,你应该立刻想想:这玩意儿快不快?怎么优化?

我们来测试一下时间,python 代码计时

class Timer(object):
    def __enter__(self):
        self.t0 = time.time()

    def __exit__(self, exc_type, exc_val, exc_tb):
        print('[time spent: {time:.3f}s]'.format(time = time.time() - self.t0))
with Timer():
	print(fib1(5))	# [time spent: 0.000s]
with Timer():
	print(fib1(40))	# [time spent: 26.883s]

可见,当我们用上述递归代码计算斐波那契数列的第40个数,就需要半分钟了,这还得了。

问题的根源在与函数的调用次数是随着输入大小指数增长的!

而且其中有百分之九十九点酒的计算都是冗余的!

如何避免冗余计算?

把中间过程保留下来,以空间换时间!

空间换时间

在这里插入图片描述

from typing import Dict
memory: Dict[int, int] = {0: 0, 1: 1}  # base cases

def fib2(n: int) -> int:
    if n not in memory:
        memory[n] = fib2(n - 1) + fib2(n - 2)  # memoization
    return memory[n]

再来看看时间效率:

with Timer():
	print(fib2(500))	# [time spent: 0.000s]

保留了中间结果,计算量从 O ( 2 n ) O(2^n) O(2n) 立马变成了O(n),只不过以 O ( n ) O(n) O(n) 的空间为代价,何乐而不为呢!

lru_cache

Least Recently Used (LRU) 缓存,在 python 自带的 functools 库中,根据参数缓存每次函数调用结果,对于相同参数的,无需重新函数计算,直接返回之前缓存的返回值

from functools import lru_cache

@lru_cache(maxsize=None)
def fib3(n: int) -> int: 
    if n < 2:  # base case
        return n
    return fib3(n - 2) + fib3(n - 1)  # recursive case
with Timer():
	print(fib3(500))	
'''
139423224561697880139724382870407283950070256587697307264108962948325571622863290691557658876222521294125
[time spent: 0.000s]
'''

递归改迭代

def fib4(n: int):
    yield 0  # special case
    if n > 0: yield 1  # special case
    last= 0  # initially set to fib(0)
    next= 1  # initially set to fib(1)
    for _ in range(1, n):
        last, next = next, last + next
        yield next  # main generation step


if __name__ == "__main__":
    for i in fib4(500):
        print(i)

同样是秒速完成

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值