對其餘動態規劃問題感興趣的,也能夠查看算法
詳解動態規劃最少硬幣找零問題--JavaScript實現編程
一開始在接觸動態規劃的時候,可能會雲裏霧裏,彷佛能理解思路,可是又沒法準確地表述或者把代碼寫出來。本篇將一步一步經過做圖的方式幫助初次接觸動態規劃的同窗來理解問題。這一篇將以經典的 01揹包 問題爲例子來說解,最後經過純 JavaScript 來實現,在 Sublime 上運行演示。固然若是不會 JavaScript 也一點關係都沒有,由於最重要的是理解整個推導過程。在語言實現的時候,也沒有涉及什麼語言特性,基本上懂個C語言就能看懂了。bash
給定一個固定大小的揹包,揹包的容量爲 capacity,有一組物品,存在對應的價值和重量,要求找出一個最佳的解決方案,使得裝入揹包的物品總重量不超過揹包容量 capacity,並且總價值最大。本題中給出了3個物品,其價值和重量分別是 (3,2),(4,3),(5,4)。括號左邊爲價值,右邊爲重量,揹包容量 capacity 爲5。那麼求出其搭配組合,使得揹包內總價最大,且最大價值爲多少?編程語言
在開始計算以前,須要先對動態規劃中的01揹包問題有基本的理解:post
清楚了上面的原則以後,就能夠開始進行分析了。 當一個新物品出現的時候,須要去決策若是選擇了它,是否會讓總價值最大化。咱們根據問題,創建以下表格用於分析: ui
咱們對這個表格作一下說明,左上角 val
和 w
分別是物品的價值和重量。即上面所描述的3個物品的價值與重量對應關係。spa
從第三列到最後一列,使用了變量 j,它表示揹包總容量,最大值爲5,也就是前面問題所說的 capacity 的值。3d
第二行到最後一行,使用 i 表示,下標從0開始,一共有3個物品,因此 i 的最大值爲 2。即咱們使用i表示物品,在下面介紹中將i=0
稱爲物品0
,i=1
稱爲物品1
,以此類推。code
除了 j = 0 的狀況之外,咱們將從左到右,從上到下一步一步去填寫這個表格,來找到最大的價值。
表格中未填寫的空格,表示揹包內物品總價值。咱們後面將使用 T[i][j]
二維數組來表示它。
若是揹包總容量爲0,那麼很顯然地,任何物品都沒法裝進揹包,那麼揹包內總價值必然是0。因此第一步先填滿 j=0 的狀況。
正如上面所說,咱們接下來將從上到下,從左往右地填寫這個表格。因此如今把注意力定位到 i =0, j = 1 的空格上。
在分析過程當中,有一個重要原則:分析第i行時,它的物品組合僅能是小於等於i的狀況。
怎麼理解這個原則:好比分析i=0這一行,那麼揹包裏只能裝入物品0,不能裝入其餘物品。分析i=1這一行,物品組合能夠是物品0和物品1
i=0 j=1
: 揹包總容量爲1,可是物品0 的重量爲 2,沒法裝下去,因此這一格應該填 0。
i=0 j=2
: 揹包總容量爲2,恰好能夠裝下物品0 ,因爲物品0 的價值爲3,所以這一格填 3。
i=0 j=3
: 揹包總容量爲3,因爲根據上面說明的物品組合原則,第0行,僅能放物品0,不須要考慮物品1 和 物品2,因此這一格填 3。
i=0 j=4
: 同理,填 3 。
i=0 j=5
: 同理,填 3 。
這樣咱們能夠完成第0行的填寫,以下圖:
在這一行,能夠由物品0 和物品1 進行自由組合,來裝入揹包。 i=1 j=1
: 揹包總容量爲1,可是物品0 的重量爲 2,物品1重量爲3,揹包沒法裝下任何物品,因此填 0。
i=1 j=2
: 揹包總容量爲2,只能裝下物品0,因此填 3。
i=1 j=3
: 揹包總容量爲3,這時候能夠裝下一個物品1,或者一個物品0,僅僅從人工填表的方式,很容易理解要選擇物品1,可是咱們該如何以一個確切的邏輯來表達,讓計算機明白呢?基於上面說說明的價值和重量在表格中從上到下遞增原則,能夠確認物品1的價值是大於物品0的,因此默認狀況下優先考慮物品1,當選擇了物品1以後,把揹包剩餘的容量和物品1以前的物品重量對比(也就是和物品0的重量對比,若是剩餘重量能裝下前面的物品,那麼就繼續裝)。因此這裏選擇物品1,填 4
i=1 j=4
: 選擇了物品1以後,物品1 的重量爲3,揹包容量爲4, 減去物品1的重量後, 剩餘容量爲1,沒法裝下物品0,因此這裏填 4
i=1 j=5
選擇了物品1以後,剩餘的容量爲2,恰好能夠裝下物品0,因此一格揹包裝了物品1,和物品 0,總價值爲7,把 7 填入表格。
這樣咱們就完成了第二行的填寫,以下圖:
i=2 j=1
: 填 0 。
i=2 j=2
: 填寫這一行時,3種物品都有機會被裝入揹包。總容量爲2時,只能裝物品0,因此填 3。
i=2 j=3
: 物品2的重量爲4,大於容量j,因此這裏能夠參考 T[i-1][j]的值,也就是 i=1 j=3
那一格的值,填 4。
i=2 j=4
: 能夠裝下物品2,價值爲5。也能夠裝下物品1。這一空格須要謹慎一點。咱們將使用更嚴謹的方式來分析。在i=1 j=5
中出現了物品組合一塊兒裝入揹包的狀況,這一空將延續這種分析方式。咱們選擇了物品2,剩餘的容量表達式應爲 j-w[i] 即 4 - 4 = 0,剩餘的容量用於上一行的搜索,因爲上一行咱們是填寫完的,因此能夠很輕易地獲得這個值。表達式能夠寫成 val[i] + T[i][j-w[i]]
,能夠根據這個表達式得出一個值。可是這並非最終結果,還須要和上一行同一列數值對比,即 T[i-1][j]
,對比,取最大值。最後這裏填 5。
i=2 j=5
: 根據上面計算原理,這裏若是選擇了物品2,那麼最大價值只能5,參照上一行,同一列,價值爲7,取最大值。因此放棄物品2,選擇將物品0和物品1裝入揹包,填寫7。
完成後的表格以下:
理解了上面整個填表過程,咱們要把邏輯抽取出來,在具體代碼實現以前,先用僞代碼表達出來。
if(j < w[i]){ //容量小於重量,hold不住
T[i][j] = T[i-1][j]; //因此值等於上一行,同一列。若是i=0,沒有上一行,則T[i][j] 取0
}else{
T[i][j] = max(val[i] + T[i-1][j-w[i]] , T[i-1][j]); //參照上面 i=2 j=4 和 i=2 j=5 時的填表分析
}
複製代碼
以上這簡短的僞代碼就是解決問題的核心思路,能夠應用於任何你熟悉的編程語言上。
說到這裏,這篇文章應該是要基本告一段落了。
若是你已經理解了上面的填表分析和僞代碼表達,那麼就能夠嘗試着本身去用代碼實現了。最後放出在使用 JavaScript的一種實現方式供你們參考,再也不針對針對代碼作太多說明,重要區域會有註釋。若是 Sublime 支持純 JavaScript,能夠直接複製黏貼,command+b 運行看結果。
function knapSack(w,val,capacity,n){
var T = []
for(let i = 0;i < n;i++){
T[i] = [];
for(let j=0;j <= capacity;j++){
if(j === 0){ //容量爲0
T[i][j] = 0;
continue;
}
if(j < w[i]){ //容量小於物品重量,本行hold不住
if(i === 0){
T[i][j] = 0; // i = 0時,不存在i-1,因此T[i][j]取0
}else{
T[i][j] = T[i-1][j]; //容量小於物品重量,參照上一行
}
continue;
}
if(i === 0){
T[i][j] = val[i]; //第0行,不存在 i-1, 最多隻能放這一行的那一個物品
}else{
T[i][j] = Math.max(val[i] + T[i-1][j-w[i]],T[i-1][j]);
}
}
}
findValue(w,val,capacity,n,T);
return T;
}
![](https://user-gold-cdn.xitu.io/2018/5/19/16377bff6e2d5c63?w=448&h=468&f=png&s=180322)
//找到須要的物品
function findValue(w,val,capacity,n,T){
var i = n-1, j = capacity;
while ( i > 0 && j > 0 ){
if(T[i][j] != T[i-1][j]){
console.log('選擇物品'+i+',重量:'+ w[i] +',價值:' + values[i]);
j = j- w[i];
i--;
}else{
i--; //若是相等,那麼就到 i-1 行
}
}
if(i == 0 ){
if(T[i][j] != 0){ //那麼第一行的物品也能夠取
console.log('選擇物品'+i+',重量:'+ w[i] +',價值:' + values[i]);
}
}
}
// w = [2,3,4]. val = [3,4,5] , n = 3 , capacity = 5
//function knapSack([2,3,4],[3,4,5],5,3);
//
var values = [3,4,5],
weights = [2,3,4],
capacity = 5,
n = values.length;
console.log(knapSack(weights,values,capacity,n));
複製代碼
輸出結果