0-1背包问题
有n个重量和价值分别为 wi,vi的物品。从这些物品中挑选出总重量不超过 WW的物品, 求所有挑选方案中价值总和的最大值。
样例:
n=4 (w,v)=(2,3),(1,2),(3,4),(2,2) W=5
n个物品,每种物品只有两种选择,放进背包,或者不放进背包。n个物品对应的,最大的所有可能的总数为 2n2n 种不同的放法。
最朴素的,我们可以枚举所有可能的放法,找到最大值。
Rec(n,W) 表示剩余容量为W的背包,还有1~n 这nn个物品可以选择, 所能取得的最大的背包价值。
还剩i个物品可以选, 背包容量剩余j的时候,可以分为两种情况:
1. 第i个物品不选,只考虑前i−1个物品。则 Rec(i,j)=Rec(i−1,j),此时背包容量j不变
2. 第i个物品被选择, 接下来要考虑的就是, 如何在前i−1个物品中选择物品放入容量为j−w[i]的背包, 则 Rec(i,j)=Rec(i−1,j−w[i])+v[i]
总和两种可能,取较大值。可以给出递推式:
Rec(i,j)=max(Rec(i−1,j),Rec(i−1,j−w[i])+v[i])
还需要考虑一些细节:
1. 终止条件, i=0 ,无物品可选 Rec(0,j)=0。
0个物品可以选择,放入容量为j的背包,得到的最大价值只能为0
2. j<w[i],背包剩余容量不足以放下第i个物品,Rec(i,j)=Rec(i−1,j)R
综上,得到递推式:
递归方法:
#include <iostream>
using namespace std;
int a[5]={3,4,2,7,8};
int b[5]={9,2,4,5,3};
int w=5,n=5;
int res(int i,int j){
int rec;
if(i==0){//终止条件,无物品可选Rec(0,j)=0
//0个物品可以选择,放入容量为j的背包,得到的最大价值只能为0
rec=0;
}
else if(j<a[i]){
//背包剩余量不足以放下第i个物品
rec=res(i-1,j);
}
else{
//抉择,第i个物品选或者不选,都试一下,去最大值
rec=__max(res(i-1,j),res(i-1,j-a[i])+b[i]);
}
return rec;
}
int main(){
cout<<res(n,w)<<endl;
return 0;
}
从上图可以看出,我们枚举了所有可能的情况,即对于每个物品i,物品i选还是不选,我们都考虑了一遍。递归的层数是n+1层,最后一层是判定终止。
再来重申一下 Rec(i,j) 的含义,表示有编号为1~ i 的这前 i 个物品可以选择,放入容量为j 的背包,所能达到的最大价值。终止条件是i=0,时,无物品可选,Rec(0,j)=0,题目要求的是 Rec(n,W)。
在求解Rec(n,W)的过程中,有些情况可能会被重复计算。比如上图Rec(1,2) 在第四行计算了2次。这种重复计算是随着n的增大,指数级增长的,所以n,Wn,W 较大时,问题就是不可解的。时间和空间复杂度太高 ,为O(2n) 。
动态规划求解:
代码示例
#include <iostream>
#include <cstring>
#define MAXN 1000
using namespace std;
int dp[MAXN][MAXN];
int w[MAXN] = {0, 2, 1, 3, 2};
int v[MAXN] = {0, 3, 2, 4, 2};
int W = 5, n = 4;
int solve(int n, int W) {
memset(dp, 0, sizeof(dp));
for (int i = 1; i <= n; i++) { //i从1开始,因为i=0的值已经确定为0
for (int j = 0; j <= W; j++) {
if (j < w[i]) {
dp[i][j] = dp[i-1][j];
}
else {
dp[i][j] = max(dp[i-1][j], dp[i-1][j-w[i]] + v[i]);
}
}
}
return dp[n][W];
}
int main() {
cout << solve(n, W) << endl;
return 0;
}