一、题目描述:
有n件物品,每件物品占用的空间为w[i], 价值为p[i]。
有容量为 V 的背包。求在容量允许的范围下,背包装入物品的最大价值。
用dp[i][v] 表示 用剩余容量为v的背包,来装前i件物品,可以达到的最大价值。
那么 dp[i][0] = 0;
在当前为i,v的情况下,考察第i件物品。有两种情况。
1、如果i物品的体积大于v,根本装不下了。没得选,只能放弃。则最大价值不变。相当于,用前i-1件物品来填满v。dp[i][v] = dp[i-1][v];
2、如果i物品的体积不大于v,可以选择装入i或者放弃i。
如果装入i,则剩余的容量变为v-w[i]。整个问题变为,先把i装入,再用v-w[i]的空间去装前i-1件物品。 dp[i][v] = dp[i-1][v-w[i]] + p[i]
如果放弃i,则dp[i][v] 依然是dp[i-1][v]。和情况1相同。
状态转移方程就出来了:
if( v >= w[i] ) dp[i][v] = max { dp[i-1][v] , dp[i-1][ v-w[i] ] + p[i] } else dp[i][v] = dp[i-1][v]
代码如下:
int main() { int n = 5; int sum = 12; int w[5] = {5, 4, 7 ,2 ,6}; int p[5] = {12,3,10, 3, 6}; /* dp[i][V] 表示前i个,放入体积为V的背包内,获得的最大收益。 dp[0][0] = {0}; dp[i][V] = max{ dp[i-1][V], dp[i][V-w[i]] + p[i] } */ int dp[6][13]; int i,v; memset(dp,0,sizeof(dp)); for(i = 1; i <= n ; i++) { //for(v = w[i-1];v<=sum;v++) 这是错的!因为如果v < w[i]的话,dp[i][v]等于dp[i-1][V]。 for(v = 1;v <= sum; v++) { if(v >= w[i-1]) dp[i][v] = max_2( dp[i-1][v-w[i-1]] + p[i-1],dp[i-1][v] ); else dp[i][v] = dp[i-1][v]; } } for(i = 1; i <= n ; i++) { for(v = 0;v<=sum;v++) { printf("%2d ",dp[i][v]); } printf("\n"); } return 0; }
当V=0 和 1时,任何物品都不能放下,所以 前两列的数都是0.
当v=2时,只能放下第四件物品,所以前三件的时候,依然是0,第四件以后,开始变化。
二、空间优化
注意到每次求解 dp[i][v]只用到了上一行(i-1)的值:dp[i-1][v] 和 dp[i-1][ v-w[i] ]。考虑用dp[v]来表示之前dp[i][v]对应的状态。
第i次循环开始之前,dp [ j ] = dp [ i-1 ][ j ] ( 0<= j <= V )
第i次循环结束之后,dp [ j ] = dp [ i ][ j ] ( 0<= j <= V )
那么在计算第i次循环的dp值时,两种选择变为下面的情况。
如果放弃物品i,即dp[i][v] = dp[i-1][v],dp[v]保持不变即可。
如果放入物品i,即dp[i][v] = dp[i-1][ v-w[i] ], 很幸运,当前的dp[v-w[i]]就是dp[i-1][v-w[i]]。
OK,代码出来了。
for (i=0;i<n;i++) { for(v = 0; v <= sum; v++) { if( v < w[i]) dp[v] = dp[v] ;// 第i-1轮的值直接更新到了第i轮 else dp[v] = max{ dp[v],dp[ v-w[i] ] }; } }
实际运行结果是不对的。
仔细观察,有个问题出现了。我们必须保证,在计算dp[v]的时候,dp[ v -w[i] ]依然等于dp[i-1][ v-w[i] ]。
但是看代码中,明显不是这样。因为v是从小到大计算,dp[ v-w[i]] 已经在 dp[v]之前被计算过了。我们用到的dp[ v-w[i] ]是第i次循环计算出来的值。
我们的代码相当于在执行:dp[ i ][ v ] = max{ dp[ i-1 ][ v ],dp[ i ][ v-w[i] ] }; (注意是dp[ i ][ v-w[i] ]!)
为了保证先计算dp[v],需要颠倒一下计算顺序。
最终版本如下:
int main() { int w[5] = {5, 4, 7 ,2 ,6}; int p[5] = {12,3,10, 3, 6}; int i,v,n = 5; int sum = 12; int dp[13]; memset(dp,0,sizeof(dp)); for(i=0;i<n;i++) { for(v=sum;v>=w[i];v--) { dp[v] = max_2( dp[v], dp[v-w[i]]+p[i] ); } } for(i = 0;i<=sum;i++) { printf("%d ",dp[i]); } return 0; }
第一次循环之前,i=0,dp[v] 全体为0 .
每一次循环得到的值,和dp[i][v]的第i行是一致的。
记录自己的一些理解和思考。不能保证代码完全正确,因为没有经过大量数据测试。
欢迎指正!
根据背包九讲的第一讲,整理扩充下。自己思考完才能看懂了。。。