关于0/1背包问题网上有非常多的博文,在此我谨记录一下自己的理解。
问题表述:有N件物品和一个容量为V的背包。第i件物品的体积是C[i](0<=i<=N-1),价值是W[i]。求解将哪些物品装入背包可使价值总和最大。每个物品最多只可以放入背包一次。
这个问题的经典解法思路如下:
我们用f[i][j]表示在考虑前i个物品时体积为j的背包的最大价值,注意,我们并不是把前i个物品全部放入背包,而是考虑i个物品中挑选一些放入背包,使得价值最大的那些情况。
首先,我们考虑只有1个物品(第0个)时,容量分别为0,1,...,V的各背包所包含物品的最大价值。很明显,容量大于等于C[0]的背包的最大价值为W[0],容量小于C[0]的背包的最大价值为0。
然后,我们考虑再来一个(第i个)物品时,容量分别为0,1,...,V的各背包所包含物品的最大价值。对于某个背包,我们有两种选择:将该物品放入该背包或者不放入。
当我们将该物品放入某个体积为j的背包时,该背包的最大价值为f[i][j-C[i]]+W[i]。当我们不把该物品放入某个体积为j的背包时,该背包的最大价值为f[i-1][j]。所以在考虑前i个物品时,体积为j的背包的最大价值为f[i][j]=max{f[i-1][j],f[i][j-C[i]]+W[i]}.
迭代以上步骤,从i=0到N-1,最后得到的f[N-1][V]就是最后的答案。
上述算法的时间复杂度为O(VN),空间复杂度也是O(VN)。时间复杂度是最优的,而空间复杂度可以进一步优化:我们注意到在公式f[i][j]=max{f[i-1][j],f[i][j-C[i]]+W[i]}中,对于j从0到V,f[i][j]只与f[i-1][j]有关,而与i-2,i-3等情况下的f无关,所以我们只需要考虑前一次迭代(亦即i-1)的结果就可以。亦即f[j]=max{f[j],f[j-C[i]]+W[i]}.又因为在计算f[j]时用到了比j小的f:f[j-C[i]],所以在对j进行迭代时应该从后向前迭代:
1 for ( int i=0;i<N;i++ ){ 2 for ( int j=V;j>=0;j-- ){ 3 if (j-item[i][0]>=0){ // 此处判断是为了防止将j物品放入容量小于C[j]的背包中 4 f[j]=max(f[j],f[j-item[i][0]]+item[i][1 ]); 5 } 6 } 7 }
我们可以用一个例子来展示一下上述代码迭代的过程。取V=10,N=3.三个物品的体积分别为3,4,5.价值分别为4,5,6,迭代过程中f数组的值为:
0 0 0 4 4 4 4 4 4 4 4
0 0 0 4 5 5 5 9 9 9 9
0 0 0 4 5 6 6 9 10 11 11
第一行为只考虑第1个物品的情况。所有容量大于等于3的背包的价值都为该物品的价值:4.
第二行为只考虑前2个物品的情况。所有容量大于等于7的背包可以同时容纳前2个物品,价值为4+5=9,容量为4-6的背包可以容纳第2个物品,价值为5,容量为3的背包可以容纳第1个物品,价值为4.
第三行以此类推。
我们取最后1行的最后一个数字为结果,亦即考虑所有3个物品的体积为10的背包的最大价值,为11.
参考文献:
[1] 0/1问题 动态规划法