目录:
- 问题描述
- 问题分析
- 构造最优解
- 空间优化
1. 问题描述:给定N个物品和容量为C的背包,每个物品的重量是Wi,价值为Vi,每个物品只有选择装入背包或者选择不装如背包。求如何选择能使得背包装入的总价值最大。
2. 问题分析:使用一个二维数组来存储每种状态。声明二维数组大小为m[n][c],其中m[i][j]表示在前i个物品中进行适当的选择,使得在不超过背包容量j时能获得最大价值。分析m[i][j]的计算方法
- case(1): j < w[i] ,即背包容量为j时无法放下第i个物品,此时选择不装入该物品。则m[i][j] = m[i-1][j] , 此等式表示面对第i个物品时不选择,并且该值等于前i-1个物品选择时最大价值。
- case(2): j >= w[i],即背包的容量可以放下第i个物品,但因为要满足价值最大化,所以要考虑是否将物品i装入背包中。如果装入包中,则m[i][j] = m[i-1][ j - w[i] ] + v[i],其中m[ i-1 ][ j-w[i] ]指的是考虑了i-1件物品,背包容量为j-w[i]时的最大价值,其本质是为第i件物品腾出了w[i]的空间。如果不装入包中,则m[i][j] = m[i-1][j]。
- 根据上面的分析,可以得出状态转移方程:
if(j >= w[i]): m[i][j] = max(m[i-1][j], m[i-1][j-w[i]] + v[i]) else: m[i][j] = m[i-1][j]
- 实现代码:
for(int i=1;i<=n;i++) // n为物品个数 { for(int j=1;j<=c;j++) // c 为背包容量 { if(j>=w[i]) // 选取 m[i][j]=max(m[i-1][j],m[i-1][j-w[i]]+v[i]); //取较大值 else // 不选 m[i][j]=m[i-1][j]; } }
根据上面可以计算出二维数组最后一个值m[n][c]即为最大价值。
3.构造最优解(前面工作可以得到最大价值,但是不知道是选取了哪些物品而得到的最大值)
- 思路:m[n][c]为最优值,如果m[n][c]=m[n-1][c] ,说明有没有第n件物品都一样,则x[n]=0 ; 否则 x[n]=1。当x[n]=0时,由x[n-1][c]继续构造最优解;当x[n]=1时,则由x[n-1][c-w[i]]继续构造最优解。以此类推,可构造出所有的最优解。
- 代码实现:
void traceback() { for(int i=n;i>1;i--) { if(m[i][c] == m[i-1][c]) x[i] = 0; else { x[i] = 1; c -= w[i]; } } x[1] = (m[1][c]>0) ? 1 : 0; }
4. 空间优化
- 思路:由0-1背包的状态转移方程 m[i][j] = max{m[i-1][j-w[i]]+v[i],m[i-1][j]}的特点,知道当前状态仅依赖前一状态的剩余容量与当前物品重量v[i]的关系。根据这个特点,我们可以将m降到一维即m[j] = max{m[j],m[j-w[i]]+v[i]}。从这个方程中我们可以发现,有两个m[j],但是要区分开。等号左边的m[j]是当前i的状态,右边括号中的m[j]是第i-1状态下的值。
- 优化代码:
-
void FindMaxBetter()//优化空间后的动态规划 { int i,j; for(i=1;i<=number;i++) { for(j=capacity;j>=0;j--) { if(B[j]<=B[j-w[i]]+v[i] && j-w[i]>=0 )//二维变一维 { B[j]=B[j-w[i]]+v[i]; } } } }
- 备注:优化后的代码只可以求出最大价值,并不能构造最优解(也就是不知道选择了哪些物品使价值最大化)
- 当不明白的时候,手动推一遍是最好的方法。