一個背著背包的小偷闖空門偷東西,他必須趁屋主回來之前把有價值的物品塞進包包內帶走。考慮到小偷自身的行動力,背包能裝的物品總重量有限,小偷要如何選擇物品才能獲得最高的總價值?



這篇文章將會直接以動態規劃來解決問題,如果您還不熟悉動態規劃的話可以先查看這篇文章:

https://magiclen.org/dynamic-programming-basic/

假設房屋內有以下幾項物品:

  1. 一支價值為1200元的石英錶,重200公克。
  2. 一支價值為1500元的手機,重500公克。
  3. 一幅價值為1500元的畫,重300公克。
  4. 一臺價值為4000元的平板電腦,重850公克。

而背包最多只能放1公斤,也就是1000公克。

在塞滿背包的條件下,小偷可以怎麼偷?

  1. 平板電腦。(4000元,850公克)
  2. 石英錶、手機、畫。(4200元,1000公克)

顯然,小偷要偷「石英錶、手機、畫」才可以讓背包擁有最高的價值。當然這類問題不是只要選擇最多的物品數量(從重量最輕的物品開始選),總價值就一定最高;也不是只要從價值最高的物品開始選,總價值就一定最高。如果物品的種類多了,就不像上面這樣可以輕易地列出組合方式,找出擁有最高總價值的組合。

以Top-down的方式來思考上面的問題。逐一去看各個物品,來決定要不要(或是能不能)將其放進背包。假設愈貴的物品愈先看,那麼最後小偷得決定要不要把石英錶放進背包,如果要放,那麼背包內的總價值就會是判斷完上一個物品要不要放進背包的價值再加上石英錶的價值;如果不要放,那麼背包內的總價值就會是判斷完上一個物品要不要放進背包的價值。而如果小偷在最後階段放了石英錶,則上一個物品在判斷要不要被放進背包時,最大重量就必須要再減掉石英錶的200公克(如此才能保證最後一次有辦法把石英錶放進包包)……其餘階段也依此類推。最小的問題為看完物品了(沒有物品能放,價值為0)。

至於在每個階段要如何決定物品要不要放進背包,首先就去看物品的重量,如果還放得下的話,就去判斷把物品放進背包,和不要把物品放進背包時的價值哪個大,若前者大,當然就是放進背包;若後者大,當然就是不放進背包。

以程式來解題的話,我們可以寫出以下的遞迴函數:

由於以上程式碼有重疊子問題,所以可以用動態規劃的記憶法來優化或是用製表法將其改寫成迭代函數。

以製表法為例,改寫如下:

觀察以上程式,可以發現dp陣列能簡化成長度為max_weight + 1maxWeight + 1的一維陣列。

程式繼續優化如下:

而以上程式,又可以再優化成:

想要知道背包裡到底放了哪些物品,以我們目前使用的dp一維陣列演算法並不太容易做到。大概就是要建立出一個二維陣列,去記錄在所有的最大重量以及物品下,該物品有沒有被放進背包,最後再反推回去找到所有被放進背包的物品。而如果是用之前的dp二維陣列,則可以直接反推放進背包的物品。

dp[w][ith]表示在最大重量為w時,放或不放ith物品的最大價值(ith0開始放),以v來表示。要判斷這個ith物品到底有沒有被放進去,就去看dp[w][ith + 1]是否與dp[w][ith]相等,相等表示沒有放,不相等表示有放。若是沒放,繼續看下一個ith;若是有放,則減少最大重量w和價值v後,再去看下一個ith。如果價值v被減為0了,表示背包內的東西都已經找出來了。

程式如下: