【问题标题】:Baktracking function which calculates change exceeds maximum recursion depth计算变化的 Baktracking 函数超过最大递归深度
【发布时间】:2013-07-05 18:49:55
【问题描述】:

我正在尝试编写一个函数来查找产生指定金额的所有可能的硬币组合,例如,它计算所有可能的方式来从面额列表 1p、2p、5p 中为 2 英镑提供零钱,10p,20p,50p,1 磅,2 磅。我对此感到困惑,找不到合适的解决方案。

我希望 main 函数是递归的,因为我想更好地理解递归。算法必须回溯,如果在某个时刻找到的组合超过要匹配的数量,程序应该返回之前的步骤并从不同的点开始。

到目前为止,我已经编写了一个普通(非递归)函数,如果每枚硬币只使用一次,它会计算给定国家/地区所有可能的硬币组合(这相当简单)。我还没有尝试为给定的金额找到正确的组合,只是所有可能的硬币组合。

def calcCoins(coins):
    """ 
    returns all possible combinations of coins, when called with 
    [1,2,5,10,20,50,100,200] returns a list of 126 Counters containing 
    for instance Counter{1:1}, Counter{1:1,2:1,5:1}, Counter {50:1,100:1} etc
    """
    i,combs = 1, []
    while i < len(coins):
        for x in combinations(coins,i):
            combs.append(Counter(x))
        i += 1
    return combs

现在我有一个笨拙的递归函数,它接受一个组合和所需数量作为参数,并返回所有可能的方式,其中可以给出等于这个数量的变化。

def findSum(comb,goal,rightOnes):
    if rightOnes == None:
        rightOnes = []
    if sum(comb.elements()) == goal:
        comb_ = Counter(comb)
        if comb_ in rightOnes:
             # probably a cycle, return combinations gathered and exit
             return rightOnes
        rightOnes.append(comb_)
    elif sum(comb.elements()) > goal:
        #this is meant to be backtracking
        return False
    for k in comb:
        comb[k] += 1
        if findSum(comb,goal,rightOnes) != False:
            return findSum(comb,goal,rightOnes)
        else:
            comb[k] = 1
    return rightOnes

对于非常小的组合,该函数会正确运行并返回: 例如对于

test2 = Counter({10: 1, 20: 1})
findSum(test2,200,[])

返回:

 [Counter({10: 18, 20: 1}), Counter({10: 16, 20: 2}), 
  Counter({10: 14, 20: 3}), Counter({10: 12, 20: 4}), 
  Counter({10: 10, 20: 5}), Counter({10: 8, 20: 6}), 
  Counter({20: 7, 10: 6}), Counter({20: 8, 10: 4}), 
  Counter({20: 9, 10: 2})]

但对于较大的,例如

test3 = Counter({1: 1, 2: 1, 10: 1})
test4 = Counter({1: 1, 2: 1, 100: 1, 10: 1}) 

它超出了递归的限制。它运行良好,直到某个时刻,打印出部分结果,但在某些时候它超过了最大递归深度。

我犯了哪些错误导致此功能异常运行?这与我实施回溯有关吗?我省略了一些案例吗?如何优化这个函数,使其不超过最大递归深度?

提前致谢!

编辑: 这是回溯:

   if findSum(comb,goal,rightOnes) != False:
   File "C:\playground\python\problem31.py", line 105, in findSum
   if sum(comb.elements()) == goal:
   File "C:\Python27\lib\collections.py", line 459, in elements
   return _chain.from_iterable(_starmap(_repeat, self.iteritems()))
   RuntimeError: maximum recursion depth exceeded while calling a Python object

最后的部分结果,就在函数中断之前(用 test3 调用)

[Counter({1: 163, 2: 1, 20: 1, 10: 1, 5: 1}), Counter({1: 161, 2: 2, 20: 1, 10: 1, 5: 1}), 
 Counter({1: 159, 2: 3, 20: 1, 10: 1, 5: 1}), Counter({1: 157, 2: 4, 20: 1, 10: 1, 5: 1}), 
 Counter({1: 155, 2: 5, 20: 1, 10: 1, 5: 1}), Counter({1: 153, 2: 6, 20: 1, 10: 1, 5: 1})]

【问题讨论】:

  • 您的函数假定将使用初始组合中的所有硬币并且至少使用一次硬币。在test3test4 你定义了什么目标?返回部分结果的函数意味着它达到了最终返回。我们是否也能得到回溯和部分结果,因为我有预感是对Counter__repr__ 的调用导致了大递归。
  • 目标是 200,我以相同的目标称呼他们。添加了回溯,它清楚地指向 Counter,因为它发生在函数试图对 counter 的元素求和时。
  • 我不想这么说,但 Python 并不是学习这种递归编程的最佳语言。例如,请参阅this answer
  • len(calcCoins([1,2,5,10,20,50,100,200])) 是 254,而不是 126,顺便说一句。

标签: python recursion backtracking


【解决方案1】:

首先,正如this question 的第一个答案所示,由于 Python 作为一种语言的语义,递归并不是一种特别有效的范式。但是,正如那里指出的那样,可以使用sys.setrecursionlimit(2000)。 (或者你需要多少)我想强调这是“懒惰”的解决方案,我强烈建议使用你的第一个(非递归)版本。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-11-05
    • 2013-11-30
    • 2020-12-13
    • 2013-03-03
    • 2016-07-09
    • 2015-08-14
    • 2011-04-26
    相关资源
    最近更新 更多