問題:如果我們有面值為1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元?
動態規划的本質是將原問題分解為同性質的若干相同子結構,在求解最優值的過程中將子結構的最優值記錄到一個表中以避免有時會有大量的重復計算。
例如硬幣組合問題,若求湊夠11元的最少硬幣數,可以先從湊夠0元、1元、2元……的子結構開始分析。
假設d(i)為湊夠i元所需最少硬幣數,則
d(0) = 0 理所當然
d(1) = 1 要湊夠1元,需要從面值小於等於1元的硬幣中選擇,目前只有面值為1元的硬幣
此時d(1) = d(0) + 1
d(2) = d(2 - 1) + 1 = 2, 從面值小於等於2元的硬幣中選擇,符合要求的硬幣面值為:1元。
此時d(2) = d(2-1) + 1
d(3) = d(3 - 3) + 1 = 1, 從面值小於等於3元的硬幣中選擇,符合要求的硬幣面值為:1元,3元。
此時有有兩種選擇:是否選擇含有面值3元的硬幣
含有3元硬幣:d(3) = d(3 - 3) + 1 = 1
不含3元硬幣:d(3) = d(3 - 1) + 1 = d(2) + 1 = 3
自然是選擇二者中較小值
依次類推...
就該問題總結一下,隨着要湊夠錢數的增加:
1.首先要知道所有不大於該錢數的面值,
2.對於每種面值的硬幣,求出當選擇一個該面值的硬幣時所需的硬幣數
當選擇一個硬幣后,所需硬幣數+1,所要湊夠的錢數=原所要湊的錢數-該硬幣面值,所要湊夠的錢數減少,求減少后要湊錢數最少所需硬幣數,屬於原問題的子結構,已求出解
3.在上述求出的結果集中,選擇最小值,即為要湊夠該錢數所需的最少硬幣數
由此可以看出,每個問題的最優值都是借其子結構的最優值得到的。
而該算法的最小的子結構的最優解是已知的,即:當要湊錢數為0元時,最少需要0枚硬幣。
利用這個最小的子結構,通過遞推式便可求出所指定值湊夠錢數的最優值
上面所提到的遞推式,便是狀態轉移方程。利用已知狀態,不斷通過狀態轉移方程求解,便得到了最優值和最優解。
下面看一下硬幣組合問題的數學描述:
d(i)=min{ d(i-vj)+1 },其中i-vj >=0,vj表示第j個硬幣的面值,i表示要湊夠i元,d(i)表示湊夠i元最少需要的硬幣數。即:
0 i == 0 時
min_coin_num(i) = {
min{ min_coin_num( i-coin_value(j) )+1 | i-coin_value(j)>0} coin_value(j)表示第j種硬幣的面值 i > 0 時
當總值total_value為i時, 對於所有的 coin_value(j) < i的硬幣j ,取min{ min_coin_num(i-coin_value(j)) }
最后,該算法的python實現:
1 # 如果我們有面值為1元、3元和5元的硬幣若干枚,如何用最少的硬幣湊夠11元 2 __author__ = 'ice' 3 4 5 def select_coin(coin_value, total_value): 6 min_coin_num = [0] 7 for i in range(1, total_value + 1): 8 min_coin_num.append(float('inf')) 9 for value in coin_value: 10 if value <= i and min_coin_num[i - value] + 1 < min_coin_num[i]: 11 min_coin_num[i] = min_coin_num[i - value] + 1 12 13 return min_coin_num 14 15 16 result = select_coin([1, 3, 5], 11) 17 print("coin number:" + str(result[-1]))