【问题标题】:Memoization fibonacci algorithm in pythonpython中的记忆斐波那契算法
【发布时间】:2015-06-16 17:30:45
【问题描述】:

我有这种记忆技术来减少获取斐波那契序列号的调用次数:

def fastFib(n, memo):
    global numCalls
    numCalls += 1
    print 'fib1 called with', n
    if not n in memo:
        memo[n] = fastFib(n-1, memo) + fastFib(n-2, memo)
        return memo[n]


def fib1(n):
    memo = {0:1, 1:1}
    return fastFib(n, memo)


numCalls = 0
n = 6
res = fib1(n)
print 'fib of', n,'=', res, 'numCalls = ', numCalls

但我被困在这里:memo[n] = fastFib(n-1, memo) + fastFib(n-2, memo) 和这里memo = {0:1, 1:1}。每次我想得到一个数字的 fib 时,它是如何准确减少调用次数的?

【问题讨论】:

  • 顺便说一句 - 你的备忘录应该是:memo = {0:0, 1:1} 而不是 memo = {0:1, 1:1}

标签: python algorithm memoization


【解决方案1】:

可以在单个类或函数中完成,如下所示

class Solution:
    def __init__(self):
        # initialize memo
        self.memo = {}


    def fib(self, n: int) -> int:
        # base case
        if n < 2:
            return n

        # check if fib(n) is already in memo - f(n) was calculated before
        if n in self.memo:
            return self.memo[n]
        else:
            f = self.fib(n - 1) + self.fib(n - 2)

        # store the value of fib(n) when calculated 
        self.memo[n] = f 
   
        return f
    

【讨论】:

    【解决方案2】:

    以下方法使用最小缓存大小(2 个条目),同时还提供 O(n) 渐近:

    from itertools import islice
    def fibs():
        a, b = 0, 1
        while True:
            yield a
            a, b = b, a + b
    
    def fib(n):
        return next(islice(fibs(), n-1, None))
    

    此实现来自斐波那契的经典核心递归定义(类似于 Haskell 的 https://wiki.haskell.org/The_Fibonacci_sequence#Canonical_zipWith_implementation)。

    有关更直接(核心递归)的翻译,请参阅https://gist.github.com/3noch/7969f416d403ba3a54a788b113c204ce

    【讨论】:

      【解决方案3】:

      可以使用 Python 3.2+ 中的 functools 库来完成

      import functools
      
      @functools.lru_cache(maxsize=None) #128 by default
      def fib(num):
          if num < 2:
              return num
          else:
              return fib(num-1) + fib(num-2)
      

      【讨论】:

        【解决方案4】:

        您应该始终返回memo[n],而不仅仅是在不安全的查找时(fastFib() 的最后一行):

        def fastFib(n, memo):
            global numCalls
            numCalls += 1
            print 'fib1 called with', n
            if not n in memo:
                memo[n] = fastFib(n-1, memo) + fastFib(n-2, memo)
            #this should be outside of the if clause:
            return memo[n] #<<<<<< THIS
        

        这样可以减少调用次数,因为对于n 的每个值,您实际上最多计算和递归一次,从而限制了对O(n) 的递归调用次数(2n 调用的上限),而不是一遍又一遍地重新计算相同的值,从而有效地进行指数级的递归调用。

        fib(5) 的一个小例子,其中每一行都是一个递归调用:

        天真的方法:

        f(5) = 
        f(4) + f(3) = 
        f(3) + f(2) + f(3) =
        f(2) + f(1) + f(2) + f(3) =
        f(1) + f(0) + f(1) + f(2) + f(3) = (base clauses) = 
        1 + f(0) + f(1) + f(2) + f(3) = 
        2 + f(1) + f(2) + f(3) =
        3 + f(2) + f(3) = 
        3 + f(1) + f(0) + f(3) = 
        3 + 1 + f(0) + f(3) = 
        5 + f(3) = 
        5 + f(2) + f(1)  =
        5 + f(1) + f(0) + f(1) =
        5 + 1 + f(0) + f(1) =
        5 + 2 + f(1) =
        8
        

        现在,如果你使用 memoization,你不需要重新计算很多东西(比如f(2),计算了 3 次),你会得到:

        f(5) = 
        f(4) + f(3) = 
        f(3) + f(2) + f(3) =
        f(2) + f(1) + f(2) + f(3) =
        f(1) + f(0) + f(1) + f(2) + f(3) = (base clauses) = 
        1 + f(0) + f(1) + f(2) + f(3) = 
        2 + f(1) + f(2) + f(3) =
        3 + f(2) + f(3) =  {f(2) is already known}
        3 + 2 + f(3) = {f(3) is already known}
        5 + 3  = 
        8
        

        如您所见,第二个比第一个短,数字 (n) 越大,这种差异就越显着。

        【讨论】:

        • 如果你有足够的时间,你能逐步解释一下它所做的整个计算吗?谢谢
        • @MdSakib 我刚刚做了,例如 f(5)。
        • 这真的很棒。我看到它如何减少冗余计算。谢谢
        猜你喜欢
        • 2019-06-04
        • 2015-10-19
        • 2011-12-14
        • 1970-01-01
        • 2020-08-19
        • 2021-11-17
        • 2018-11-09
        • 1970-01-01
        • 2021-05-21
        相关资源
        最近更新 更多