动态规划之硬币找零问题
動(dòng)態(tài)規(guī)劃 Dynamic Programming
理查德·貝爾曼(Richard Bellman)在1957年提出了動(dòng)態(tài)規(guī)劃(Dynamic Programming, 即 DP)一詞
通過(guò)將解決方案與包含一般子問(wèn)題的子問(wèn)題組合來(lái)解決問(wèn)題
DP 與 分治之間的不同點(diǎn):
使用分而治之解決這些問(wèn)題的效率很低, 因?yàn)橄嗤淖訂?wèn)題必須多次解決(暫時(shí)缺乏具體的例子, 需要多做題目)
DP 將解決每個(gè)問(wèn)題一次, 并將其答案存儲(chǔ)在表格中以備將來(lái)參考
硬幣找零問(wèn)題
目標(biāo): 給定硬幣的面值, 例如, 1, 5, 10, 25, 100, 設(shè)計(jì)一種使用最少數(shù)量的硬幣向客戶付款的方法
收銀員算法(Cashier's algorithm): 在每次迭代中, 添加不會(huì)使我們超過(guò)要支付的金額的最大值的硬幣
例子: 33 美分(33 cents) --> 一個(gè) 25 美分 + 一個(gè) 5 美分 + 三個(gè) 1 美分
收銀員算法
將 n 個(gè)硬幣按照面值進(jìn)行排序, 使得:
[c_1 < c_2 < ... < c_n
]
(S leftarrow empty)
WHILE x > 0
k (leftarrow) largest coin denomination (c_k) such that (c_k leq x)(最大硬幣面值 (c_k) 使得 (c_k leq x))
IF no such k, RETURN "no solution"
ELSE
(x leftarrow x - c_k)
(S leftarrow S cup { k })
RETURN S
找零(Coin Changing)
這個(gè)貪婪算法 -- 收銀員算法是最優(yōu)的嗎?
定理
收銀員算法對(duì)于美國(guó)硬幣來(lái)說(shuō)是最佳的算法, 即這個(gè)貪婪算法可以求出最優(yōu)解(U.S. coins -- 1, 5, 10, 25, 100).
可以根據(jù) x 進(jìn)行歸納證明(存疑, 待補(bǔ)充, 算法本身肯定是正確的).
其他的情況
收銀員算法在其他的情況下可能并不奏效
考慮美國(guó)的郵費(fèi): 1, 10, 21, 34, 70, 100, 350, 1225, 1500(這些都是美國(guó)郵費(fèi)的面值, 并且可能每年都會(huì)變)
使用收銀員算法: 140 美分 = 100 + 34 + 1 + 1 + 1 + 1 + 1 + 1
而最佳的做法: 140c(即 140 美分) = 70 + 70
在某些情況下, 甚至可能無(wú)法找到可行的解決方案
動(dòng)態(tài)規(guī)劃
為了對(duì) n 美分進(jìn)行找零, 我們將首先弄清楚如何對(duì)每個(gè)值 (x < n) 進(jìn)行找零
我們可以從解決方案中構(gòu)建出更小的值
令 (C[n]) 為給 n 美分找零所需的最少的硬幣數(shù)量
令 (x) 為最優(yōu)解中使用的第一個(gè)硬幣的值
然后 (C[n] = 1 + C[n - x])
問(wèn)題的關(guān)鍵是: 我們不知道 x 的值
動(dòng)態(tài)規(guī)劃算法
我們將嘗試所有可能的 (x) 值, 并取最小值
[C[n] = left{egin{matrix}
min_{i:d_i leq n}left{ C[n - d_i] + 1 ight} qquad if n > 0 & \
0 qquad qquad qquad qquad qquad qquad if n = 0& \
end{matrix}ight.
]
Change 代碼
偽碼描述
int Change(int n)
{
if (n < 0>)
return INFTY;
else if (n == 0)
return 0;
return 1 + min(Change(n - d1), Change(n - d2), Change(n - d3));
}
圖解
(n = 12, d_1 = 1, d_2 = 3, d_3 = 7)
這棵樹(shù)的根節(jié)點(diǎn)是我們要求解的 n, 然后 n 分別減去 (d_1, d_2, d_3) 得到其三個(gè)子節(jié)點(diǎn), 然后從左到右依次遞歸分解這 3 個(gè)子節(jié)點(diǎn), 然后以此類推.
最后求解出來(lái)的找零所需的最少的硬幣的數(shù)目是 4, 根據(jù)上面的樹(shù)可以發(fā)現(xiàn), 具體的分法不是唯一的.
動(dòng)態(tài)規(guī)劃的要素
DP 用于解決具有以下特征的問(wèn)題:
最優(yōu)子結(jié)構(gòu)(最優(yōu)原理): 問(wèn)題的最優(yōu)解決方案中包括子問(wèn)題的最優(yōu)解決方案
子問(wèn)題重疊: 在某些地方, 我們多次解決同一子問(wèn)題
動(dòng)態(tài)規(guī)劃的步驟
表征最佳子結(jié)構(gòu)
遞歸定義最佳解決方案的值
自下而上計(jì)算值
(如果需要)構(gòu)建最佳解決方案
記憶化(Memoization)
記憶化是處理重疊子問(wèn)題的一種方法
計(jì)算出子問(wèn)題的解決方案后, 將其存儲(chǔ)在表中
后續(xù)調(diào)用
可以修改遞歸算法以使用記憶化
Change() 有許多重復(fù)的工作, 使用一個(gè)表來(lái)使得算法的時(shí)間復(fù)雜度變?yōu)?(O(nk))
DP_Change(n)
偽碼描述
int DP_Change(int n)
{
int tmp, i, j;
for(i = 1, C[0] = 0; i<= n; i++)
{
tmp = INFTY;
for(j = 0; j < ; j++)
if (d[j] <= i && C[i - d[j]] + 1 < tmp)
tmp = C[i - d[j]] + 1; // 更新最小值
C[i] = tmp;
}
return tmp; // 最后返回的即 C[n]
}
這個(gè)算法和上面的遞歸相反, 是從底向上進(jìn)行構(gòu)建的.
從底向上構(gòu)建
| n | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| (C[n]) | 0 | 1 | 2 | 1 | 2 | 3 | 2 | 1 | 2 | 3 | 2 | 3 | 4 |
| (d_1) | 0 | 1 | 2 | 0 | 1 | 2 | 0 | 0 | 1 | 2 | 0 | 1 | 2 |
| (d_2) | 0 | 0 | 0 | 1 | 1 | 1 | 2 | 0 | 0 | 0 | 1 | 1 | 1 |
| (d_3) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 | 1 | 1 |
動(dòng)態(tài)規(guī)劃 VS 貪婪算法
DP 適合具有以下特征的問(wèn)題:
最優(yōu)子結(jié)構(gòu): 問(wèn)題的最優(yōu)解決方案包括子問(wèn)題的最優(yōu)解決方案
子問(wèn)題重疊: 子問(wèn)題總數(shù)很少, 每個(gè)子問(wèn)題都有許多重復(fù)出現(xiàn)的實(shí)例
自下而上, 建立一張已解決的子問(wèn)題表, 用于解決較大的子問(wèn)題
貪婪算法是自上而下的, 動(dòng)態(tài)規(guī)劃可能是過(guò)大的(overkill); 貪婪算法往往更容易編碼.
總結(jié)
以上是生活随笔為你收集整理的动态规划之硬币找零问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 我用ABAP做过的那些无聊的事情
- 下一篇: bch算法生成nand flash中51