1.背包問題
(1)問題由來:給定n個重量為w1,w2..........wn,價值為v1,v2........,vn的物品和一個承重為W的背包,求這些物品中最有價值的一個子集,並要求能夠裝到背包中。這里假設所有的重量和包的承重都是正整數,而物品的總重量不必是整數。
(2)地推公式:為了設計一個動態規划算法,需要推導一個遞推關系,用較小實例解的形式來表示背包問題的實例的解。接下來我們來考慮一個由前i個物品定義的實例,物品的重量分別w1,w2,w3,..wi,價值分別為v1,v2,.........vi,背包承受的重量為j(j>=1,j<=w)。設F(i,j)為該實例最優解的物品總價值,也就是說能夠放進承重為j的背包中的前i個物品中最有價值自己的總價值。可以將前i個物品中能夠放進承重為j的背包中的子集分為兩個類別:包括第i個物品的子集和不包括第i個物品的子集。
(3)根據上面的描述有下面的結論:
根據定義,在不包括第i個物品的子集中,最優子集的價值為F(i-1,j)。
在包括第i個物品的子集中,最優子集是由該物品和前i-1個物品中能夠放進承重為j-wi的背包的最優子集組成。這種最優子集的價值為vi+F(i-1,j-w)
(4)因此,在前i個物品中最優解的總價值為兩個價值中的較大值。當然如果第i個物品不能夠放進背包,則從前i個物品中選出的最優子集的總價值等於從前i-1個物品中選出的最優子集的總價值。這個結果又下面的遞推公式。
我們可以比較容易的定義如下的初始條件:當j>=0的時候,F(0,j)=0;當i>=0的時候,F(i,0)=0
我們的目標是F(n,w),即n個給定的物品中能夠放進承受重量為w的背包中的子集的最大總價值以及最優子集本身。當i,j>0的時候,為了計算第i行第j列的單元格F(i,j),我們拿前一行同一列的單元格與vi加上前一行左邊wi列的單元格的和作比較,計算出兩者的較大者。
(5)實例:考慮下列數據給出的實例
下圖給出了由動態規划公示計算的動態規划表
最大的總價值為F(4,5)=37可以通過上表回溯過程來求得最優子集元素,因為F(4,5)=37,F(3,5)=32,物品4填滿背包余下5-2=3個單位承重量的一個最優子集都包括在最優解中。
(6)記憶化
動態規划所涉及問題的解滿足一個交疊子問題來表示遞推關系。直接使用自頂向下的這樣一個地推關系求解導致算法要不止一次的求解公共子問題,因此算法的效率比較低(一般來說是指數級的),另一方面經典動態規划是自底向上工作的,它用所有較小的子問題來填充表格,但是每個子問題只解一次,這種方法無法令人滿意的一面是,在求解給定問題時,有些較小子問題的解常常不是必須的。所以我們使用記憶法,該方法使用自頂向下的方式對給定問題進行求解,但是還需要維護一個類似自底向上動態規划算法使用的表格。算法思想如下:
2.實例:
package com.nowcoder.dp; import org.junit.Test; public class Knapsack { public static void main(String[] args){ } /** * * @param val 實例最優解的最大值 * @param wight 物品的重量 * @param w 背包容量 * @return */ public static int knapsack(int[] val,int[] wight,int w){ int n = val.length; //物品的總數量 int[][] v= new int[n+1][w+1]; //創建背包矩陣 //對於第0行所有的列來說,他們沒有選擇物品的權利不能選擇物品,所以不管背包容量多少,總價值都是0 for(int col = 0 ; col<= w ; col++){ v[0][col] = 0; } //對於第0列所有的行來說,背包容量為0,不能再向背包中放任何物品 for(int row = 0 ; row <= n ;row++){ v[row][0]=0; } /** * 接下來填充記錄表 */ for(int i = 1 ; i <=n;i++){//先一行一行的填充 for(int j =1 ; j <= w ; j++){ //再在每一行中按列來填充 if(wight[i-1]<=w){//如果當前物品的重量小於當前背包的重量 v[i][j] = Math.max(val[i-1]+v[i-1][w-wight[i-1]],v[i-1][w]); }else {//如果當前物品的重量大於當前背包中的重量 v[i][j] = v[i-1][j]; } } } return v[n][w]; } @Test public void test(){ int[] val = {10, 40, 30, 50}; int[] wight={5, 4, 6, 3}; int w = 10 ; System.out.println(knapsack(val,wight,w)); } }