【问题标题】:Dynamic programming: recursion+memoization vs loop+list动态编程:递归+记忆 vs 循环+列表
【发布时间】:2018-12-12 10:03:52
【问题描述】:

@functools.lru_cache 的文档提供了一个使用缓存计算斐波那契数以实现动态编程技术的示例:

@lru_cache(maxsize=None)
def fib(n):
    if n < 2:
        return n
    return fib(n-1) + fib(n-2)

我已经看到这种方法用于解决各种编程难题。它是否具有与“标准”迭代动态编程方法相同的时间/空间复杂度,例如:

def fib(n):
    if n < 2:
        return n
    dp = [0]*(n+1)
    dp[1] = 1
    for i in range(2 , n+1):
        dp[i] = dp[i-1] + dp[i-2]
    return dp[n]

另外,使用递归方法有什么缺点吗?

【问题讨论】:

    标签: python recursion dynamic-programming memoization


    【解决方案1】:

    它应该与memoization(自上而下)类型的动态编程具有相同的复杂性。

    逐步填表的迭代方法(自下而上的动态规划)的复杂度可能略有不同,因为记忆只记住参数集,需要构建最终解决方案

    这种差异对于斐波那契或阶乘示例并不重要,但对于具有稀疏子问题表的任务可能会发生(当难以预测以后将使用哪些条目时)

    【讨论】:

    • 所以如果记忆空间稀疏,迭代应该比自顶向下更糟糕。
    【解决方案2】:

    下面比较了您展示的功能。一般来说,这些观察结果不一定适用于任何用于计算斐波那契数的递归或迭代方法,而仅适用于您在问题中显示的实现。

    时间复杂度:

    第一次调用

    • 递归方法:O(n)
    • 迭代方法:O(n)

    后续调用

    • 递归方法:O(1)
    • 迭代方法:O(n)

    常数因子

    常数因子可能完全不同。即使两者都是O(n),迭代方法很可能(第一次调用)会比递归方法更快。这只是一个猜测,根据我的经验(不是实际测试),列表索引比调用函数要快得多。

    内存复杂度:

    两者都需要 O(n) 额外内存,但是递归方法将保留内存(因此它是永久分配的),而迭代方法将在函数完成后释放内存。

    其他区别

    Python 的递归限制。当时间变得太大并且缓存没有被填满时,递归方法会因为这个限制而失败,例如fib(500)。列表索引没有这样的限制(除非内存不足)。

    【讨论】:

      猜你喜欢
      • 2020-12-10
      • 2010-10-15
      • 1970-01-01
      • 2012-08-21
      • 1970-01-01
      • 2021-03-24
      • 2012-11-12
      • 2012-09-27
      相关资源
      最近更新 更多