問題描述
假設你為一家自動售貨機廠家編程序,自動售貨機要每次找給顧客最少數量硬幣;假設某次顧客投進$1紙幣,買了ȼ37的東西,要找ȼ63,那么最少數量就是:2個quarter(ȼ25)、1個dime(ȼ10)和3個penny(ȼ1),一共6個.
分別使用貪心算法,遞歸,以及遞歸的優化版本:遞歸 + 備忘錄技術,與動態規划四種解法
問題抽象: coin_list = [1,10,25,100]. coin = 63. 在所給定coin_list等於coin最少個數.(63的最優組合是25 25 10 1 1 1.一共6個)
解法一:貪心算法
def get_min_coins(money_value): # 貪心版本
'''
貪心算法(又稱貪婪算法)是指,在對問題求解時,總是做出在當前看來是最好的選擇。
也就是說,不從整體最優上加以考慮,他所做出的是在某種意義上的局部最優解。
'''
return_list = []
money_coins = [1,5,10,25,100] # 不兼容愛爾波尼亞的21分硬幣.其實63的最優解應該是三個21的硬幣.貪心算法的局限性
money_coins_sorted = sorted(money_coins,reverse=True)
for current_value in money_coins_sorted:
len_current_value = money_value // current_value
return_list += [current_value] * len_current_value
money_value = money_value - current_value*len_current_value
'''
第一次: len_current_value == 2 , return_list = [25,25] , money_value == 13
第二次: ...
第三次: ...
第四次: ...
'''
if money_value <=0: # 截至條件
break
return return_list
#print(get_min_coins(63))
解法二:遞歸求解
def recMC(coin_value_list,money_value): # 使用遞歸求得最優解
'''
遞歸三條件:
不斷縮小自身規模
不斷調用自身
有結束條件
其中,遞歸是自頂向下進行計算的.
'''
min_len = money_value
if money_value in coin_value_list: # 遞歸結束條件
return 1
else:
for current_value in [c for c in coin_value_list if c < money_value]:
now_len = 1 + recMC(coin_value_list,money_value-current_value) # 調用自身,且縮小規模
if now_len < min_len:
min_len = now_len
return min_len
# print(recMC([1,5,10,25], 63)) # 這個程序執行的太慢了.一共需要執行67,716,925次.
解法三:遞歸優化:備忘錄技術
def recDc(coin_value_list,money_value,know_results): # 遞歸改進版本
'''
遞歸改進的關鍵是消除重復計算.其中有一種技術是備忘錄技術.
可以用一個表將計算過的中間結果保存起來,在計算之前查表看看是否已經計算過
在遞歸調用之前,先查找表中是否已有部分找零的最優解如果有,
直接返回最優解而不進行遞歸調用如果沒有,才進行遞歸調用
'''
min_len = money_value
print(know_results)
if money_value in coin_value_list:
return 1
elif know_results[money_value] != 0:
return know_results[money_value]
else:
for i in [c for c in coin_value_list if c < money_value]:
now_len = 1 + recDc(coin_value_list,money_value-i,know_results)
if now_len < min_len:
min_len = now_len
know_results[money_value] = now_len
print(know_results)
return min_len
# print(recDc([1,5,10,25],63,[0]*64))
解法四:動態規划
def dpMakeChange (coinValueList,change,minCoins): # 動態規划解法
'''
動態規划:其所求的最優解是由子問題的最優解構成的.
動態規划是自底向上的計算.
在找零遞加的過程中,設法保持每一分錢的遞加都是最優解,一直加到求解找零錢
數,自然得到最優解
'''
for coin in range(1,change+1):
min_len = coin
for i in [ c for c in coinValueList if c <=coin]:
if minCoins[coin-i] + 1 < min_len:
min_len = minCoins[coin-i]+1
minCoins[coin] = min_len
return minCoins[change]
# print(dpMakeChange([1,5,10,25],63,[0]*64))
解法四擴展:動態規划擴展
def dp_make_change(coin_value_list,money_value,minCOins,coins_record):# coins_record記錄每一個最優解所添加的硬幣.用於返回最優硬幣
'''
'''
for coin in range(1,money_value+1):
min_len = coin
new_add_coin =1
for i in [c for c in coin_value_list if c <= coin]:
if minCOins[coin-i] + 1 < min_len:
min_len = minCOins[coin-i] + 1
new_add_coin = i
minCOins[coin] = min_len
coins_record[coin] = new_add_coin
# print(coins_record)
return minCOins[money_value]
def print_coins(cions_record,how_much_money):
'''
在得到最后的解后,減去選擇的硬幣幣值,回溯到表格之前的部分找零,
就能逐步得到每一步所選擇的硬幣幣值
'''
rev_cions = []
while how_much_money>0:
how_much_money = how_much_money - cions_record[how_much_money]
rev_cions.append(cions_record[how_much_money])
return rev_cions
# some_money = 63
# cions_record = [0]*64
# print(dp_make_change([1,5,10,25],some_money,[0]*64,cions_record))
# print(print_coins(cions_record,some_money))