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 bug | weight | value | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
a | 4 | 6 | |||||||||||
a,b | 5 | 4 | |||||||||||
a,b,c | 6 | 5 | |||||||||||
a,b,c,d | 2 | 3 | |||||||||||
a,b,c,d,e | 2 | 6 |
按照这个思路:
第一行,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; }
相关文章推荐
- Android SDK下, 如何在程序中输出日志 以及如何查看日志.
- 完全卸载xcode 以及如何在Finder当前目录下打开Terminal
- vs中如何设置DLL以及LIB输出路径
- HDU 1114 Piggy-Bank (完全背包水题,但注意一下时间输出)
- 01背包,完全背包,多重背包问题详细介绍以及源代码实现
- 如何设定虚拟机的内核调试,以及把用户态调试器的输出重定向到内核调试输出
- POJ1787 【完全背包+物品计数+路径输出】
- (3)分布式下的爬虫Scrapy应该如何做-递归爬取方式,数据输出方式以及数据库链接
- 如何将文章带格式的存入数据库,并带格式的输出以及数据库经常插入空行问题
- 如何从按行读文件&&以及每行中有多少个数据块,并输出每个数据块
- var, dynamic 差別以及如何實作像 ViewBag 一樣的物件【转】
- dp之完全背包poj1787(完全背包以及路径记录 推荐)
- Oracle11完全卸载方法 deinstall.bat如何用以及如何删除oracle注册表
- 【转】JAVA输出内容打印到TXT以及不同系统中如何换行
- POJ 1787 Charlie's Change (完全背包/多重背包,输出方案的物品个数)
- HttpClient4的cookie rejected问题,以及如何消除该warning输出
- 背包问题--完全背包 详解以及实现
- Android SDK下, 如何在程序中输出日志 以及如何查看日志.
- 如何将Action中的Hibernate查询结果LIST输出在控制台以及JSP界面
- 如何从按行读文件&&以及每行中有多少个数据块,并输出每个数据块 (2)