动态编程用于攀爬步骤的方式

时间:2018-02-03 17:39:37

标签: python dynamic-programming memoization search-tree

问题是使用动态编程编写一个函数,用于攀爬N个步骤的方法数量。考虑到一次只能攀爬一步或两步。

例如,如果N = 3,则函数应返回[(1,1,1),(1,2),(2,1)]。 我已经在python 3中编写了一个代码来计算。代码工作正常,但是当N大到40时需要相同的时间,或者与不使用动态编程的相同递归代码相比,spyder(Anaconda)应用程序崩溃。

它不应该比正常方式更有效吗?

我附上了以下的DP代码

def stepsdyn(N,memo={}):
    """
    L1 is the list of all possibilities in the left side of the search tree,
    that is with 1 as the last element
    L2 is the list of all possibilities in the right side of the search tree
    memo stores the results of corresponding N
    returns memo[N]
    """
    L1=[]
    L2=[]
    if N==1:
        return [(1,)]
    elif N==0:
        return [()]
    else:
        try:
             return memo[N]
        except:
             for items in stepsdyn(N-1,memo):
                 items+=(1,)
                 L1.append(items)
             for items in stepsdyn(N-2,memo):
                 items+=(2,)
                 L2.append(items)
             memo[N]=L1+L2
             return memo[N] 

2 个答案:

答案 0 :(得分:2)

基本理念

在计算机编程中,最基本和最常见的权衡是时间效率和空间效率之间的权衡。记忆可能对时间有利,但对空间有害,这就是这种情况。您的程序崩溃了,因为该memoization字典包含大量数据。马上你的重复关系意味着你永远不需要在N - 3点保存的数据,这样你就可以摆脱它。这有点减轻了记忆负担(但不是很多)。

代码

的问题/疑虑
  1. 不要记住您不需要的值(见上文)。
  2. Python对可变默认参数的处理意味着memo dict只创建一次。有关详细信息,请参阅this SO post。这也意味着字典在函数返回后坐在(在内存中)...不好。通常不要使用可变的默认参数,除非你有令人信服的理由。
  3. list理解可以是a bit faster而不是显式的循环。更重要的是,在这种情况下,它们更具可读性。
  4. 这最后一个更像是风格的东西。您正在将12添加到递归调用返回的项的尾部。通常,这些元素会添加在头部。
  5. 解决方案

    相同的算法,但更多的内存和时间

    def stepsdyn_new(N, memo):
        try:
            return memo[N]
        except KeyError:
            l1 = [(1,) + items for items in stepsdyn_new(N - 1, memo)]
            l2 = [(2,) + items for items in stepsdyn_new(N - 2, memo)]
            memo.pop(N - 2)
            memo[N] = l1 + l2
            return memo[N]
    

    注意:我将基本案例作为参数传递,但如果需要,您可以添加原始if / else

    返回字符串

    def stepsdyn_str(N, memo):
        try:
            return memo[N]
        except KeyError:
            l1 = ['1' + x for x in stepsdyn_str(N - 1, memo)]
            l2 = ['2' + x for x in stepsdyn_str(N - 2, memo)]
            memo.pop(N - 2)
            memo[N] = l1 + l2
            return memo[N]
    

    这将返回一个字符串列表(例如['111','12','21'])而不是元组列表。因为python字符串中的每个字符只使用1个字节(而不是列表/元组中每个元素的8个字节),这会节省大量内存。可以使用以下代码将结果转换回元组列表(尽管这会导致额外的速度/内存损失):

    [tuple(map(int, tuple(x))) for x in stepsdyn_str(N, {0: [''], 1: ['1']})]
    

    效率

    注意steps函数是一个非备忘的解决方案(下面包含完整性)。

    速度

    |--------------|----------------------------|----------------------------|
    |              |           N = 20           |           N = 33           |
    |--------------|----------------------------|----------------------------|
    | steps        | 47 ms ± 7.34 ms per loop   | 41.2 s ± 1.6 s per loop    |
    |--------------|----------------------------|----------------------------|
    | stepsdyn     | 10.1 ms ± 1.23 ms per loop | 9.46 s ± 691 ms per loop   |
    |--------------|----------------------------|----------------------------|
    | stepsdyn_new | 6.74 ms ± 1.03 ms per loop | 7.41 s ± 396 ms per loop   |
    |--------------|----------------------------|----------------------------|
    | stepsdyn_str | 3.28 ms ± 68.8 µs per loop | 3.67 s ± 121 ms per loop   |
    |--------------|----------------------------|----------------------------|
    

    获得使用:

    %timeit steps(N)
    %timeit stepsdyn(N, memo={})
    %timeit stepsdyn_new(N, {0: [()], 1: [(1,)]})
    %timeit stepsdyn_str(N, {0: [''], 1: ['1']})
    

    内存

    在评估N=33

    的功能时,这些估算特定于我的16GB内存MBP
    • steps:最大内存10.8%
    • stepsdyn:最大内存22.0%
    • stepsdyn_new:最大内存15.7%
    • stepsdyn_str:3.6%最大内存

    非记忆解决方案

    def steps(N):
        if N == 0:
            return [()]
        elif N == 1:
            return [(1,)]
        else:
            l1 = [(1,) + items for items in steps(N - 1)]
            l2 = [(2,) + items for items in steps(N - 2)]
            return l1 + l2
    

答案 1 :(得分:0)

如果你想要一个简洁的方法来计算数字的攀爬N步的方法,假设一次只能攀爬一步或两步,我们可以实现像这样:

def f(n):
  a, b = 0, 1

  for i in xrange(n):
    a, b = b, a + b

  return b

输出:

   f(3)
=> 3
   f(5)
=> 8
   f(40)
=> 165580141
   f(120)
=> 8670007398507948658051921L

请注意,结果只是(n + 1) Fibonacci个数字。