最近重新复习下动态规划相关知识,所以把经典的背包问题拿出来重新看下。最为经典的莫过于背包九讲,详见:
这里只是把自己在做的过程中一些想法记录下来。
本文主要描述01背包问题。背包问题指的是我们有多少件物品要放进背包,求放进背包的价值最大。而01背包指的是每个种类的物品只有1件。
现在有三件物品,笔记本、手机跟手表。每件物品重量跟价值如下:
物品 | 笔记本 | 手机 | 手表 |
---|---|---|---|
重量 | 3kg | 1kg | 1kg |
价值 | 2000 | 5000 | 3000 |
现有一个4kg的背包,请问要怎么分配空间,使得整体价值最大。
动态规划的思路实际就是把大的问题拆分成小的问题,
这里我们先定义状态:dp[i][j],把前i个物品放到容量为j的背包里的最大价值。
那么有:
1)如果第i件物品,我们选择放到背包里(背包有足够容量j-weight[i]>0):
dp[i][j] = max(dp[i-1][j] , dp[i-1][j-weight[i]] + value[i])
2)不放到背包里:
dp[i][j] = dp[i-1][j]
按照这个思路,遍历 i:1-3,j:1-4
i=0,j=0~4,这是不管背包多少重量,都没有价值,所以dp[0][j] = 0。这也是dp[][]的初始状态,相当于这里进行了初始化。
i=1,这时我们看下笔记本时,背包重量从1到4的时候,背包里面的最大价值会是多少。
i=1,j=1或2,背包无法放下笔记本,dp[1][1] = dp[1][2] = 0
i=1,j=3,背包能放下笔记本,dp[1][3] = dp[0][3-1] + value[0] = 2000
同理,i=1,j=4时,dp[1][4] = dp[0][4-1] + value[0] = 2000
那么我们得到:
1 | 2 | 3 | 4 | |
---|---|---|---|---|
笔记本 | 0 | 0 | 2000 | 2000 |
手机 | ||||
手表 |
i=2,这时我们手里有笔记本及手机,背包重量从1到4,背包里面最大价值会是多少。
i=2,j=1时,背包无法放下笔记本,只能放下手机,这时dp[2][1] = dp[1][1-1] +value[2] = 5000
同理,i=2,j=2时, dp[2][2] = dp[1][2-1] +value[2] = 5000
i=2,j=3时, 背包可以放下笔记本或者手机,但无法同时存放,那么我们要对比具体价值大小: dp[2][3] = max(dp[1][3],dp[1][3-1] +value[2]) = max(2000,5000),得到只能存放手机
i=2,j=4时,背包可以放下笔记本和手机。
这时的表格变成:
1 | 2 | 3 | 4 | |
---|---|---|---|---|
笔记本 | 0 | 0 | 2000 | 2000 |
手机 | 5000 | 5000 | 5000 | 7000 |
手表 |
i=3,这时我们手里有笔记本及手机、手表,背包重量从1到4,背包里面最大价值会是多少。
i=3,j=1时,背包无法放下笔记本,只能放下手机或手表,这时dp[3][1] =max(dp[2][1],dp[2][0]+value[3])=max(5000,3000) = 5000
这里成立的原因是,我们已经得到了dp[2][1],这个值已经被更新为前2个物品在1kg背包时的最大价值,再加上第三个对比即可。
因此,我们也可以得到其他情况:
1 | 2 | 3 | 4 | |
---|---|---|---|---|
笔记本 | 0 | 0 | 2000 | 2000 |
手机 | 5000 | 5000 | 5000 | 7000 |
手表 | 5000 | 8000 | 8000 | 8000 |
#include
using namespace std;
/* run this program using the console pauser or add your own getch, system("pause") or input loop */
#define MAX 10
int getMax(int x,int y){
return x>y?x:y;
}
int getValue(int m,int n,int *weight,int *value){
int dp[MAX][MAX];
for(int i=0;i<=m;i++){
for(int j=0;j<=n;j++){
dp[i][j]=0;
}
}
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(j>=weight[i-1]){
dp[i][j]=getMax( dp[i-1][j],dp[i-1][j-weight[i-1]]+value[i-1] ); //这里要注意我们取得都是weight[i-1] value[i-1] 因为这里的数据是按0开始存放。
}else{
dp[i][j] = dp[i-1][j];
}
}
}
int maxValue = dp[m][n];
return maxValue;
}
int main(int argc, char** argv) {
int m = 3;
int n = 4;
int weight[]={3,1,1};
int value[]={2000,5000,3000};
int res = getValue(m,n,weight,value);
cout<<"value:"<
运行得到:
value:8000
按照最大价值的思路,做个逆向运算即可。即:
我们得到了整个表dp[][],现在我们遍历所有的物品i,看下是否有选中具体的某一件。
如何判断有没有选中物品i,根据我们上面的条件,如果选择放某一件物品i,具体的dp会更新如下:
dp[i][j] = dp[i-1][j-weight[i]] + value[i]
那么判断上述这个等式如果成立的话,也就是选中了i,
编写对应代码验证看看:
int restj = n;
for(int i=m;i>0;i--){
for(int j=restj;j>0;j--){
if(dp[i][j]==dp[i-1][j-weight[i-1]]+value[i-1]){
restj -= weight[i-1];
cout<< "pick:"<
运行得到:
pick:3 restj:3
pick:2 restj:2