您的位置:首页 > 其它

0-1背包以及完全背包(如何输出包里的物件)

2017-04-02 23:09 405 查看

0-1 背包问题

问题: 有Num个物品,每个物品的重量为weight[i],每个物品的价值为value[i]。现在有一个背包,它所能容纳的重量为 M ,问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?

解决方法: 采用动态规划的方法解决。

01背包的状态转换方程

f[i,j] = Max{ f[i-1,j-Weight[i]]+Value[i] ( j >= Wi ),  f[i-1,j] }


看不懂这个公式很正常,待会看了做法就知道了。

给一个具体的题目:

有a,b,c,d,e 5个物品。他们的重量和价值是(4, 6) (5,4) (6,5) (2,3) (2,6) ,

然后用一个大小为10的背包装他们,要求价值最大化。

这个时候我们借助一张表来解题

what can in bugweightvalue012345678910
a46
a,b54
a,b,c65
a,b,c,d23
a,b,c,d,e26
what can in bug意思就是说这一行代表什么能在包里,当我们填第一行的时候,只能是a在包,我们不考虑其他的b,c,d,e,就这样一行加一个物品考虑,一直往下。

按照这个思路:

第一行,0,1,2,3 因为都不满足物品重量为 4的要求,所以填0,而4 - 10 填物品a的value 6。

第二行,0,1,2,3不满足物品a ,b重量的需求,同样为0,但4,5上一行已经能满足的,这一行当然能满足,所以照抄下来。到了6 就有两种选择了,一种是采用上一行的结果,另一种是放这次加入物品b,两种取价值最大。

以此类推。

为了便于理解,我打印了过程给大家看:



以下是C++代码,打印过程被我注释,可以打开去看下整个dp的过程。

#include <iostream>
#define max(x,y) (x)>(y) ? (x) : (y)
using namespace std;

int main(int argc, const char * argv[])
{
//数据定义部分
int Num; //物品数量
int M;   //背包容量
cin >> Num;
cin >> M;
int weight[Num],f[Num][M+1],value[Num];
for (int i=0;i<Num; i++) {
cin >> weight[i] >> value[i];
}

//    //赋初值0,便于打印过程
//    for(int i=1;i<Num;i++) {
//        for(int j=0;j<=M;j++) {
//            f[i][j] = 0;
//        }
//    }

//先填写第一行
for(int j=0;j<=M;j++) {
f[0][j] = (weight[0]>j?0:value[0]);
}

//从第二行逐渐填满整个dp表
for(int i=1; i<Num; i++) {
for(int j=0;j<=M; j++) {
if(j >= weight[i]) {
f[i][j] = max(f[i-1][j],f[i-1][j-weight[i]]+value[i]);
}else{
f[i][j] = f[i-1][j];
}
}
//打印过程
//        for(int i=0;i<Num;i++) {
//            for(int j=1;j<=M;j++) {
//                cout << f[i][j] << " ";
//            }
//            cout << endl;
//        }
//        cout << "-----------------" << endl;
}
cout << endl;
cout << f[Num-1][M] << endl;
return 0;
}


复杂度也就是 O(n*m)

完全背包问题

问题: 有n种物品,每种物品有无限个,每个物品的重量为weight[i],每个物品的价值为value[i]。现在有一个背包,它所能容纳的重量为total,问:当你面对这么多有价值的物品时,你的背包所能带走的最大价值是多少?

它和0-1背包的问题就是 个 –》种

实际上完全背包问题和0-1背包问题一样解,只需要在上面公式变换一下

f[j] = Max(f[i,j-weight[i]]+Value[i] (j > Weight[i]),f[i-1,j]);({i,j|0<i<=n,0<=j<=total})


f[i,j] = Max{ f[i-1,j-Weight[i]]+Value[i] ( j >= Weight[i] ),  f[i-1,j] }


对比下两个公式

把 i-1 –> i 即可,怎么理解呢,因为可以多个同种商品,所以我直接在这一行去加就可以,不需要往前一行加。

两种背包问题的改进

再想一想上面两种背包的求法还有个什么问题。对我们仅仅得到了最优解,但是并不知道由哪些物品组成的。所以我们再改进下算法。

0-1 背包问题的改进:

我们加入一个sign表来登记加入的物品。然后通过逆过程来找回这些登记的点。具体实现可以参考下面代码以及代码注释。 另外位了区别第0个物品和sign数组默认的0值,我把第0个物品,改成第1个物品,其他的也往后退一位。

#include <iostream>
#include <string.h>
using namespace std;

int main()
{
int N;
int M;
cin >> N;
cin >> M;

int weight[N+1];
int value[N+1];
for(int i=1;i<=N;i++) {
cin >> weight[i] >> value[i];
}

int sign[N+1][M+1];
int dp[N+1][M+1];

memset(dp,0,sizeof(dp));
memset(sign,0,sizeof(sign));

//给第一行赋值
for(int j = 0;j<=M;j++) {
if( weight[1] > j) {
dp[1][j] = 0;
}else{
dp[1][j] = value[1];
sign[1][j] = 1;
}
}

for(int i = 2;i<=N;i++) {
for(int j = 0;j<=M;j++) {
if(j >= weight[i]) {
if( (dp[i-1][j-weight[i]]+value[i]) > dp[i-1][j]) {
dp[i][j] = dp[i-1][j-weight[i]]+value[i];
sign[i][j] = i;
}else{
dp[i][j] = dp[i-1][j];
sign[i][j] = sign[i-1][j];
}
}else{
dp[i][j] = dp[i-1][j];
sign[i][j] = sign[i-1][j];
}
}
for(int i=1;i<=N;i++) {
for(int j=1;j<=M;j++) {
cout << dp[i][j] << " ";
}
cout << endl;
}
cout << "********" <<endl;
for(int i=1;i<=N;i++) {
for(int j=1;j<=M;j++) {
cout << sign[i][j] << " ";
}
cout << endl;
}
cout << "-----------------" << endl;

}
cout << dp
[M] << endl;

//逆回去看哪些物品放进去了
int all_val = dp
[M];
int isIn[N+1];
memset(isIn,0,sizeof(isIn));
int x = N,y= M;

while( all_val != 0) { //所有的价值都减掉为止
if(isIn[sign[x][y]] == 1) { //如果纪录过的点是已经算过的,那么继续上一行的检查
x--;
y = y - weight[sign[x][y]];
}else{    //减掉这个物品的价值,然后把这个物品标志一下,说明这个物品被选上,然后继续检查上一行
all_val -= value[sign[x][y]];
isIn[sign[x][y]] = 1;
y = y - weight[sign[x][y]];
x--;
}
}
for(int i=1;i<=N;i++) {
cout << isIn[i] << " ";
}

return 0;
}


完全背包问题的改进版

跟0-1背包的区别在于它可以重复,所以isIn的值不只是为0和1那么简单了。但实际上它的做法更简单。只需要逆回 同一行 即可。这里真的很难解释,看代码吧。

#include <iostream>
#include <string.h>
using namespace std;

int main()
{
int N;
int M;
cin >> N;
cin >> M;

int weight[N+1];
int value[N+1];
for(int i=1;i<=N;i++) {
cin >> weight[i] >> value[i];
}

int sign[N+1][M+1];
int dp[N+1][M+1];

memset(dp,0,sizeof(dp));
memset(sign,0,sizeof(sign));

//给第一行赋值
for(int j = 0;j<=M;j++) {
if( weight[1] > j) {
dp[1][j] = 0;
}else{
dp[1][j] = value[1];
sign[1][j] = 1;
}
}

for(int i = 2;i<=N;i++) {
for(int j = 0;j<=M;j++) {
if(j >= weight[i]) {
if( (dp[i][j-weight[i]]+value[i]) > dp[i-1][j]) {
dp[i][j] = dp[i][j-weight[i]]+value[i];
sign[i][j] = i;
}else{
dp[i][j] = dp[i-1][j];
sign[i][j] = sign[i-1][j];
}
}else{
dp[i][j] = dp[i-1][j];
sign[i][j] = sign[i-1][j];
}
}
for(int i=1;i<=N;i++) {
for(int j=1;j<=M;j++) {
cout << dp[i][j] << " ";
}
cout << endl;
}
cout << "********" <<endl;
for(int i=1;i<=N;i++) {
for(int j=1;j<=M;j++) {
cout << sign[i][j] << " ";
}
cout << endl;
}
cout << "-----------------" << endl;

}
cout << dp
[M] << endl;

//逆回去看哪些物品放进去了
int all_val = dp
[M];
int isIn[N+1];
memset(isIn,0,sizeof(isIn));
int x = N,y= M;

while( all_val != 0) { //所有的价值都减掉为止
all_val -= value[sign[x][y]];
isIn[sign[x][y]] ++;
y = y - weight[sign[x][y]];
}
for(int i=1;i<=N;i++) {
cout << isIn[i] << " ";
}

return 0;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐