从递归算法到自底向上的动态编程方法

时间:2019-04-06 12:27:46

标签: python algorithm recursion dynamic-programming bottom-up

我有一个递归算法,可以计算一些概率值。输入是一个整数列表和一个表示常数值的单个整数值。

例如,p([12,19,13], 2)进行三个递归调用,分别是

p([12,19],0)p([13], 2)

p([12,19],1)p([13], 1)

p([12,19],2)p([13], 0)

因为2可以分解为0 + 2、1 + 1或2 + 0。然后,每个调用都采用类似的方法,并进行其他几个递归调用。

我拥有的递归算法

limit = 20
def p(listvals, cval):
    # base case
    if len(listvals) == 0:
        return 0

    if len(listvals) == 1:
        if cval == 0:
            return listvals[0]/limit
        elif listvals[0] + cval > limit:
            return 0
        else:
            return 1/limit

    result = 0
    for c in range(0,cval+1):
        c1 = c
        c2 = cval-c
        listvals1 = listvals[:-1]
        listvals2 = [listvals[-1]]
        if listvals[-1] + c2 <= limit:
            r = p(listvals1, c1) * p(listvals2, c2)
            result = result+r

    return result

我一直试图将其转换为自下而上的DP代码,但无法弄清楚进行迭代的方式。

我写下了最终结果所需计算的所有中间步骤,很明显在递归调用的底部有很多重复。

我尝试创建如下所示的预先计算值的字典

m[single_value]=[list of calculated values] 

并使用这些值代替进行第二次递归调用p(listvals2, c2),但是就运行时间而言,它并没有太大帮助。

如何通过使用适当的自下而上的方法来缩短运行时间?

1 个答案:

答案 0 :(得分:1)

不确定我是否了解您的程序要计算的内容,所以对此无能为力,也许还可以解释一下?

关于提高性能,您仅缓存在递归调用中重复的计算的叶节点。更好的方法是将函数p的第一个参数作为元组而不是列表,然后将p的两个参数的元组用作字典中的缓存键。 / p>

Python的标准库functools提供了一种简单的方法来完成这一相当普通的工作。

from functools import wraps

def cached(func):
  cache = {}
  @wraps(func)
  def wrapped(listvals, cval):
    key = (listvals, cval)
    if key not in cache:
        cache[key] = func(key)
    return cache[key]
  return wrapped

使用此装饰器缓存所有调用功能:

@cached
def p(listvals, cval):

现在让您的p接受元组而不是列表:

p((12,19,13), 2)