2012-06-11 255 views
4

這個問題與here中提出的問題相同。在python中遞歸實現「最小數量的硬幣」

給定一個硬幣列表,它們的值(c1,c2,c3,... cj,...)和總和i。找到總數爲i的最小數量的硬幣(我們可以使用任意數量的硬幣),或者報告不可能以總和爲S的方式選擇硬幣。

我「米剛推出動態規劃昨天,我試圖做一個代碼爲它。

# Optimal substructure: C[i] = 1 + min_j(C[i-cj]) 
cdict = {} 
def C(i, coins): 

    if i <= 0: 
     return 0 

    if i in cdict: 
     return cdict[i] 
    else: 
     answer = 1 + min([C(i - cj, coins) for cj in coins]) 
     cdict[i] = answer 
     return answer 

這裏,C [i]是爲了錢‘我’量的最佳解決方案,而且可用硬幣{c1,c2,...,cj,...} 對於程序,我增加了遞歸限制以避免最大遞歸深度超出錯誤。但是,此程序僅給出正確的答案,並且解決方案是不可能,但並不表示那。

我的代碼有什麼問題,以及如何糾正它?

+1

如果我<0,你真的想返回0嗎?如果你要求你的程序使20美分出標準的美國硬幣,你的程序將返回1,因爲20 - 25 = -5,所以在遞歸調用程序將返回0,所以你的遞歸調用的最小值將是1,這似乎不正確(除非我誤解了這個問題)。另外,是cdict某種類型的memoizer? – user1413793

+0

yea cdict會記住以前的解決方案。例如cdict [10]將包含10個金額所需的最少數量的硬幣。我已經意識到,如果我 user5198

+0

是的,一旦你返回0,min將它作爲最小配置選取。 ..簡單地檢查0,因爲我沒有爲None。 –

回答

5

這是一個偉大的算法問題,但說實話,我不認爲你的實現是正確的,也可能是我不明白你的函數的輸入/輸出,爲此我表示歉意。

繼承人您的實施的修改版本。

def C(i, coins, cdict = None): 
    if cdict == None: 
     cdict = {} 
    if i <= 0: 
     cdict[i] = 0 
     return cdict[i] 
    elif i in cdict: 
     return cdict[i] 
    elif i in coins: 
     cdict[i] = 1 
     return cdict[i] 
    else: 
     min = 0 
     for cj in coins: 
      result = C(i - cj, coins) 
      if result != 0: 
       if min == 0 or (result + 1) < min: 
        min = 1 + result 
     cdict[i] = min 
     return cdict[i] 

這是我在解決類似的問題的嘗試,但這次返回硬幣的列表。我最初開始使用遞歸算法,該算法接受一個金額和一個硬幣列表,如果沒有找到這樣的配置,它可以返回一個列表中最小數量的硬幣列表或None列表。

def get_min_coin_configuration(sum = None, coins = None): 
if sum in coins: # if sum in coins, nothing to do but return. 
    return [sum] 
elif max(coins) > sum: # if the largest coin is greater then the sum, there's nothing we can do. 
    return None 
else: # check for each coin, keep track of the minimun configuration, then return it. 
    min_length = None 
    min_configuration = None 
    for coin in coins: 
     results = get_min_coin_configuration(sum = sum - coin, coins = coins) 
     if results != None: 
      if min_length == None or (1 + len(results)) < len(min_configuration): 
       min_configuration = [coin] + results 
       min_length = len(min_configuration) 
    return min_configuration 

好吧,現在讓我們看看我們是否可以通過使用動態編程(我稱之爲緩存)來改進它。

def get_min_coin_configuration(sum = None, coins = None, cache = None): 
if cache == None: # this is quite crucial if its in the definition its presistent ... 
    cache = {} 
if sum in cache: 
    return cache[sum] 
elif sum in coins: # if sum in coins, nothing to do but return. 
    cache[sum] = [sum] 
    return cache[sum] 
elif max(coins) > sum: # if the largest coin is greater then the sum, there's nothing we can do. 
    cache[sum] = None 
    return cache[sum] 
else: # check for each coin, keep track of the minimun configuration, then return it. 
    min_length = None 
    min_configuration = None 
    for coin in coins: 
     results = get_min_coin_configuration(sum = sum - coin, coins = coins, cache = cache) 
     if results != None: 
      if min_length == None or (1 + len(results)) < len(min_configuration): 
       min_configuration = [coin] + results 
       min_length = len(min_configuration) 
    cache[sum] = min_configuration 
    return cache[sum] 

現在讓我們運行一些測試。

assert all([ get_min_coin_configuration(**test[0]) == test[1] for test in 
[({'sum':25, 'coins':[1, 5, 10]}, [5, 10, 10]), 
({'sum':153, 'coins':[1, 5, 10, 50]}, [1, 1, 1, 50, 50, 50]), 
({'sum':100, 'coins':[1, 5, 10, 25]}, [25, 25, 25, 25]), 
({'sum':123, 'coins':[5, 10, 25]}, None), 
({'sum':100, 'coins':[1,5,25,100]}, [100])] ]) 

授予此測試不夠健壯,您也可以這樣做。

import random 
random_sum = random.randint(10**3, 10**4) 
result = get_min_coin_configuration(sum = random_sum, coins = random.sample(range(10**3), 200)) 
assert sum(result) == random_sum 

它的可能是沒有這樣的硬幣組合等於我們的random_sum,但我相信它不太可能...

我確定有更好的實現,我試圖強調可讀性而不是性能。 祝你好運。

更新 以前的代碼有一個小錯誤的假設,以檢查分硬幣並非最大,重新寫了算法PEP8合規性和返回[]當沒有組合可以發現,而不是None

def get_min_coin_configuration(total_sum, coins, cache=None): # shadowing python built-ins is frowned upon. 
    # assert(all(c > 0 for c in coins)) Assuming all coins are > 0 
    if cache is None: # initialize cache. 
     cache = {} 
    if total_sum in cache: # check cache, for previously discovered solution. 
     return cache[total_sum] 
    elif total_sum in coins: # check if total_sum is one of the coins. 
     cache[total_sum] = [total_sum] 
     return [total_sum] 
    elif min(coins) > total_sum: # check feasibility, if min(coins) > total_sum 
     cache[total_sum] = []  # no combination of coins will yield solution as per our assumption (all +). 
     return [] 
    else: 
     min_configuration = [] # default solution if none found. 
     for coin in coins: # iterate over all coins, check which one will yield the smallest combination. 
      results = get_min_coin_configuration(total_sum - coin, coins, cache=cache) # recursively search. 
      if results and (not min_configuration or (1 + len(results)) < len(min_configuration)): # check if better. 
       min_configuration = [coin] + results 
     cache[total_sum] = min_configuration # save this solution, for future calculations. 
    return cache[total_sum] 



assert all([ get_min_coin_configuration(**test[0]) == test[1] for test in 
      [({'total_sum':25, 'coins':[1, 5, 10]}, [5, 10, 10]), 
       ({'total_sum':153, 'coins':[1, 5, 10, 50]}, [1, 1, 1, 50, 50, 50]), 
       ({'total_sum':100, 'coins':[1, 5, 10, 25]}, [25, 25, 25, 25]), 
       ({'total_sum':123, 'coins':[5, 10, 25]}, []), 
       ({'total_sum':100, 'coins':[1,5,25,100]}, [100])] ]) 
+0

非常明確的答案ty! – user5198

+0

沒問題,我認爲這是一個偉大的算法分配,以及很好的方法來教遞歸+動態編程:) –

+0

@ samy.vilar它的輸出是什麼都沒有!! – 2013-04-08 07:59:15

1

像評論說,你需要返回時,一個足夠大的值i < 0,所以它不會被你min選擇這樣的:

cdict = {} 
def C(i, coins): 

    if i == 0: 
     return 0 

    if i < 0: 
     return 1e100 # Return infinity in ideally 

    if i in cdict: 
     return cdict[i] 
    else: 
     answer = 1 + min([C(i - cj, coins) for cj in coins]) 
     cdict[i] = answer 
    return answer 

現在每當函數返回1e100,它意味着解決方案不可能。

例如:

$ python2 coins.py 13555 1 5 9 
1507 coins 
$ python2 coins.py 139 1 5 9 
19 coins 
$ python2 coins.py 139 5 9 
19 coins 
$ python2 coins.py 13977 5 9 
1553 coins 
$ python2 coins.py 13977 9 
1553 coins 
$ python2 coins.py 139772 9 
1e+100 coins 

與用法:

python2 coins.py <amount> <coin1> <coin2> ... 
0

這裏是一個遞歸非常低效執行算法進行改變,其中V是硬幣的列表,並C的的目標金額:

def min_change(V, C): 
    def min_coins(i, aC): 
     if aC == 0: 
      return 0 
     elif i == -1 or aC < 0: 
      return float('inf') 
     else: 
      return min(min_coins(i-1, aC), 1 + min_coins(i, aC-V[i])) 
    return min_coins(len(V)-1, C) 

這是動態編程版本的算法相同:

def min_change(V, C): 
    m, n = len(V)+1, C+1 
    table = [[0] * n for x in xrange(m)] 
    for j in xrange(1, n): 
     table[0][j] = float('inf') 
    for i in xrange(1, m): 
     for j in xrange(1, n): 
      aC = table[i][j - V[i-1]] if j - V[i-1] >= 0 else float('inf') 
      table[i][j] = min(table[i-1][j], 1 + aC) 
    return table[m-1][n-1] 
+0

當我給它'count_change([1,7,10],17)''爲什麼它顯示我的答案是'5'而不是'2',它應該只返回'10'和'7'的硬幣。 – 2013-04-08 08:16:59

+0

@ActingAngel你'再右吧。我意外地發佈了一些代碼,用於找出一系列硬幣的數量可以改變的數量的方法C.這不是問題。我糾正了上面的解決方案,謝謝指出我的錯誤! –

1

這是一個有趣的方法它。有點破解,但這就是爲什麼它很有趣。下面

import math 

    def find_change(coins, value): 
     coins = sorted(coins, reverse=True) 
     coin_dict = {} 
     for c in coins: 
      if value % c == 0: 
       coin_dict[c] = value/c 
       return coin_dict 
      else: 
       coin_dict[c] = math.trunc(value/ float(c)) 
       value -= (c * coin_dict[c]) 

    coins = [1, 5, 10, 25] 
    answer = find_change(coins, 69) 
    print answer 
    [OUT]: {25: 2, 10: 1, 5: 1, 1: 4} 

是相同的溶液用註解與邊緣殼體保護

import math 
    def find_change(coins, value): 
     ''' 
     :param coins: List of the value of each coin [25, 10, 5, 1] 
     :param value: the value you want to find the change for ie; 69 cents 
     :return: a change dictionary where the key is the coin, and the value is how many times it is used in finding the minimum change 
     ''' 
     change_dict = {} # CREATE OUR CHANGE DICT, THIS IS A DICT OF THE COINS WE ARE RETURNING, A COIN PURSE 
     coins = sorted(coins, reverse=True) # SORT COINS SO WE START LOOP WITH BIGGEST COIN VALUE 
     for c in coins: 
      for d in coins:  # THIS LOOP WAS ADDED BY A SMART STUDENT: IE IN THE CASE OF IF THERE IS A 7cent COIN AND YOU ARE LOOKING FOR CHANGE FOR 14 CENTS, WITHOUT THIS D FOR LOOP IT WILL RETURN 10: 1, 1: 4 
       if (d != 1) & (value % d == 0): 
        change_dict[d] = value/d 
        return change_dict 
      if value % c == 0:  # IF THE VALUE DIVIDED BY THE COIN HAS NO REMAINDER, # ie, if there is no remainder, all the neccessary change has been given # PLACE THE NUMBER OF TIMES THAT COIN IS USED IN THE change_dict # YOU ARE FINISHED NOW RETURN THE change_dict 
       change_dict[c] = value/c 
       return change_dict 
      else: 
       change_dict[c] = math.trunc(value/ float(c))  # PLACE THAT NUMBER INTO OUR coin_dict # DIVIDE THE VALUE BY THE COIN, THEN GET JUST THE WHOLE NUMBER # IE 69/25.0 = 2.76 # math.trunc(2.76) == 2 # AND THAT IS HOW MANY TIMES IT WILL EVENLY GO INTO THE VALUE, 
       amount = (c * change_dict[c]) # NOW TAKE THE NUMBER OF COINS YOU HAVE IN YOUR UPDATE THE VALUE BY SUBTRACTING THE c * TIME NUMBER OF TIMES IT WAS USED # AMOUNT IS HOW MUCH CHANGE HAS BEEN PUT INTO THE CHANGE DICT ON THIS LOOP # FOR THE CASE OF 69, YOU GIVE 2 25CENT COINS, SO 2 * 25 = 50, 19 = 69 - 50 
       value = value - amount    # NOW, UPDATE YOUR VALUE, SO THE NEXT TIME IT GOES INTO THIS LOOP, IT WILL BE LOOKING FOR THE MIN CHANGE FOR 19 CENTS... 

    coins = [1, 5, 10, 25] 
    answer = find_change(coins, 69) 
    print answer 
    [OUT]: {25: 2, 10: 1, 5: 1, 1: 4} 
    edge_case_coins = [1, 7, 10, 25] 
    edge_case_answer = find_change(coins, 14) 
    print edge_case_answer 
    [OUT]: {7: 2} 
+0

你能多解釋一下嗎? –

+1

我爲ya @ rm-vanda添加了一些評論 –

0

下面是一個使用一個while循環。該算法非常簡單。你首先用最大的硬幣來支付這筆錢。如果你知道你會過去,你切換到下一個較小的硬幣,並重復,直到錢是0.該代碼的優點是,雖然最壞的情況下運行時間更高(我認爲它是m * n(m是列表的大小,而n代表while中的迭代),代碼要簡單得多

我認爲沒有0值的硬幣並且總是會有值爲1的硬幣。當值不是1時,函數會給硬幣的最佳數量的答案的價格之下。

def find_change(coins, money): 
    coins = sorted(coins, reverse=True) 
    coincount = 0 

    for coin in coins: 
     while money >= coin: 
      money = money - coin 
      coincount += 1 

    return coincount 

我試着想一個角落情況下,這些都不會工作(它會與具有0值的硬幣任何名單溢出),但couldn想不起來。

相關問題