0-1揹包問題

Reference: https://www.jianshu.com/p/a66d5ce49df5java

問題描述:

0-1揹包問題:給定n種物品和一揹包。物品 i 的重量彷佛 wi,其價值爲 vi,揹包的容量爲 c。問應該如何選擇裝入揹包中的物品,使得裝入揹包中物品的總價值最大?git

說實在的,書上講的東西生澀難懂,我更偏向於看一些有趣的東西。咱們來換一個風格來描述這一個問題。
如下內容大部分來自《算法圖解》一書。看完以後大有收穫。github

另外一種風格的描述:

假設你是一個小偷,揹着一個可裝下4磅東西的揹包,你能夠偷竊的物品以下:web

 
 

爲了讓偷竊的商品價值最高,你該選擇哪些商品?算法

簡單算法

最簡單的算法是:嘗試各類可能的商品組合,並找出價值最高的組合。數組

 
 

這樣顯然是可行的,可是速度很是慢。在只有3件商品的狀況下,你須要計算8個不一樣的集合;當有4件商品的時候,你須要計算16個不一樣的集合。每增長一件商品,須要計算的集合數都將翻倍!這種算法的運行時間是O(2ⁿ),真的是慢如蝸牛。微信

動態規劃

解決這樣問題的答案就是使用動態規劃!下面來看看動態規劃的工做原理。動態規劃先解決子問題,再逐步解決大問題。學習

對於揹包問題,你先解決小揹包(子揹包)問題,再逐步解決原來的問題。this

 
 

比較有趣的一句話是:每一個動態規劃都從一個網格開始。spa

揹包問題的網格以下:

 
 

網格的各行爲商品,各列爲不一樣容量(1~4磅)的揹包。全部這些列你都須要,由於它們將幫助你計算子揹包的價值。

網格最初是空的。你將填充其中的每一個單元格,網格填滿後,就找到了問題的答案!

1.吉他行

後面會列出計算這個網格中單元格值得公式,但如今咱們先來一步一步作。首先來看第一行。

 
 

這是吉他行,意味着你將嘗試將吉他裝入揹包。在每一個單元格,都須要作一個簡單的決定:偷不偷吉他?別忘了,你要找出一個價值最高的商品集合。

第一個單元格表示揹包的的容量爲1磅。吉他的重量也是1磅,這意味着它能裝入揹包!所以這個單元格包含吉他,價值爲1500美圓。

下面來填充網格。

 
 

與這個單元格同樣,每一個單元格都將包含當前可裝入揹包的全部商品。

來看下一個單元格。這個單元格表示揹包容量爲2磅,徹底可以裝下吉他!

 
 

這行的其餘單元格也同樣。別忘了,這是第一行,只有吉他可供你選擇,換而言之,你僞裝如今還沒發偷竊其餘兩件商品。

 
 

此時你極可能心存疑惑:原來的問題說的額是4磅的揹包,咱們爲什麼要考慮容量爲1磅、2磅等得揹包呢?前面說過,動態規劃從小問題着手,逐步解決大問題。這裏解決的子問題將幫助你解決大問題。

 
 

別忘了,你要作的是讓揹包中商品的價值最大。這行表示的是當前的最大價值。它指出,若是你有一個容量4磅的揹包,可在其中裝入的商品的最大價值爲1500美圓。

你知道這不是最終解。隨着算法往下執行,你將逐步修改最大價值。

2.音響行

咱們來填充下一行——音響行。你如今處於第二行,能夠偷竊的商品有吉他和音響。

咱們先來看第一個單元格,它表示容量爲1磅的揹包。在此以前,可裝入1磅揹包的商品最大價值爲1500美圓。

 
 

該不應偷音響呢?

揹包的容量爲1磅,顯然不能裝下音響。因爲容量爲1磅的揹包裝不下音響,所以最大價值依然是1500美圓。

 
 

接下來的兩個單元格的狀況與此相同。在這些單元格中,揹包的容量分別爲2磅和3磅,而之前的最大價值爲1500美圓。因爲這些揹包裝不下音響,所以最大的價值保持不變。

 
 

揹包容量爲4磅呢?終於可以裝下音響了!原來最大價值爲1500美圓,但若是在揹包中裝入音響而不是吉他,價值將爲3000美圓!所以仍是偷音響吧。

 
 

你更新了最大價值。若是揹包的容量爲4磅,就能裝入價值至少3000美圓的商品。在這個網格中,你逐步地更新最大價值。

 
 

3.筆記本電腦行

下面以一樣的方式處理筆記本電腦。筆記本電腦重3磅,無法將其裝入1磅或者2磅的揹包,所以前兩個單元格的最大價值仍然是1500美圓。

 
 

對於容量爲3磅的揹包,原來的最大價值爲1500美圓,但如今你能夠選擇偷竊價值2000美圓的筆記本電腦而不是吉他,這樣新的最大價值將爲2000美圓。

 
 

對於容量爲4磅的揹包,狀況頗有趣。這是很是重要的部分。當前的最大價值爲3000美圓,你可不偷音響,而偷筆記本電腦,但它只值2000美圓。

 
 

價值沒有原來高,可是等一等,筆記本電腦的重量只有3磅,揹包還有1磅的重量沒用!

 
 

在1磅的容量中,可裝入的商品的最大價值是多少呢?你以前計算過。

 
 

根據以前計算的最大價值可知,在1磅的容量中可裝入吉他,價值1500美圓。所以,你須要作以下的比較:

 
 

你可能始終心存疑惑:爲什麼計算小揹包可裝入的商品的最大價值呢?希望你如今明白了其中的緣由!餘下了空間時,你可根據這些子問題的答案來肯定餘下的空間可裝入哪些商品。筆記本電腦和吉他的總價值爲3500美圓,所以偷它們是更好的選擇。

最終的網格相似於下面這樣。

 
 

答案以下:將吉他和筆記本電腦裝入揹包時價值更高,爲3500美圓。

你可能認爲,計算最後一個單元格的價值時,我使用了不一樣的公式。那是由於填充以前的單元格時,我故意避開了一些複雜的因素。其實,計算每一個單元格的價值時,使用的公式都相同。這個公式以下。

 
 

你可使用這個公式來計算每一個單元格的價值,最終的網格將與前一個網格相同。如今你明白了爲什麼要求解子問題了吧?你能夠合併兩個子問題的解來獲得更大問題的解。

 
 

代碼實現:

算法的核心是思想,當清楚了整個過程,那麼寫代碼就簡單了,直接來模擬上述的一個過程:

/** * @author:我沒有三顆心臟 * @create:2017-11-14-10:24 */ public class MaxBag { static int n; // 描述物品個數 static int c; // 描述揹包容量 static int[] value; // 描述物品價值 static int[] weight; // 描述物品重量 public static void main(String[] args) { // 初始賦值操做 value = new int[]{1500, 3000, 2000}; weight = new int[]{1, 4, 3}; c = 4; n = 3; // 構造最優解的網格:3行4列 int[][] maxValue = new int[n][c]; for (int i = 0; i < n; i++) { for (int j = 0; j < c; j++) { maxValue[i][j] = 0; } } // end for // 填充網格 for (int i = 0; i < n; i++) { for (int j = 1; j <= c; j++) { if (i == 0) { maxValue[i][j - 1] = (weight[i] <= j ? value[i] : 0); } else { int topValue = maxValue[i - 1][j - 1]; // 上一個網格的值 int thisValue = (weight[i] <= j ? // 當前商品的價值 + 剩餘空間的價值 (j - weight[i] > 0 ? value[i] + maxValue[i - 1][j - weight[i]] : value[i]) : topValue); // 返回 topValue和thisValue中較大的一個 maxValue[i][j - 1] = (topValue > thisValue ? topValue : thisValue); } // end if } // end inner for } // end outer for // 打印結果二維數組maxValue for (int i = 0; i < n; i++) { for (int j = 0; j < c; j++) { System.out.printf("%6d", maxValue[i][j]); } System.out.println(); } } } 

最後打印出來的結果以下:

 
 

再增長一件商品將如何呢

假設你發現還有第四件商品可偷——一個iPhone!(或許你會堅決果斷的拿走,可是請別忘了問題的自己是要拿走價值最大的商品)

 
 

此時須要從新執行前面所作的計算嗎?不須要。別忘了,動態規劃逐步計算最大價值。到目前爲止,計算出的最大價值以下:

 
 

這意味着揹包容量爲4磅時,你最多可偷價值3500美圓的商品。但這是之前的狀況,下面再添加表示iPhone的行。

 
 

咱們仍是從第一個單元格開始。iPhone可裝入容量爲1磅的揹包。以前的最大價值爲1500美圓,但iPhone價值2000美圓,所以該偷iPhone而不是吉他。

 
 

在下一個單元格中,你可裝入iPhone和吉他。

 
 

對於第三個單元格,也沒有比裝入iPhone和吉他更好的選擇了。

對於最後一個單元格,狀況比較有趣。當前的最大價值爲3500美圓,但你能夠偷iPhone,這將餘下3磅的容量。

 
 

3磅容量的最大價值爲2000美圓!再加上iPhone價值2000美圓,總價值爲4000美圓。新的最大價值誕生了!

最終的網格以下。

 
 

問題:沿着一列往下走,最大價值可能下降嗎?

 
 

答案是:不可能。由於每次迭代時,你都存儲的是當前的最大價值。最大價值不可能比之前低!

總結:

有時候教科書生澀難懂,你須要找一些更好的資料來幫助你學習,更重要的一點是,保持一顆好奇心還有求知慾。

歡迎轉載,轉載請註明出處!
簡書ID:@我沒有三顆心臟
github:wmyskxz 歡迎關注公衆微信號:wmyskxz_javaweb 分享本身的Java Web學習之路以及各類Java學習資料

做者:我沒有三顆心臟 連接:https://www.jianshu.com/p/a66d5ce49df5 來源:簡書 簡書著做權歸做者全部,任何形式的轉載都請聯繫做者得到受權並註明出處。
相關文章
相關標籤/搜索