一、
P01: 01背包问题
N件物品和一个容量为V 的背包。放入第i件物品耗费的费用是Ci1,得到的价值是Wi。求解将哪些物品装入背包可使价值总和最大。
价值数组v = {8, 10, 6, 3, 7, 2},
重量数组w = {4, 6, 2, 2, 5, 1},
背包容量C = 12。不超过容量的情况下,使得价值最大。
1、
#include<iostream> #include<string.h> #include<cmath> using namespace std; int main() { int v[7]={0,8,10,6,3,7,2};//价值 ,一定要注意对应关系,多一行,多一列 int w[7]={0,4,6,2,2,5,1};//重量 int c=12,n=6;//容量,个数 int num[6+1][12+1]={0};//预留空间多了一行和一列 for(int i=1;i<=n;i++) //num[i][j],i个物品,j为容量时,的最大价值 { for(int j=1;j<=c;j++) { if(w[i]<=j)//可以选择 ,比较新加的和旧的哪个大 { num[i][j]=max(num[i-1][j],num[i-1][j-w[i]]+v[i]); } else//重量比容量大,不被选择,维持原来 { num[i][j]=num[i-1][j]; } } } for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { cout<<num[i][j]<<" "; } cout<<endl; } }
一定要注意空间的声明,可以提前声明一个较大的空间这样避免空间溢出。
以上是矩阵所有数据都保存下来,可以只保存两行c列,优化空间
2、
#include<iostream> #include<string.h> #include<cmath> using namespace std; int main() { int v[7]={0,8,10,6,3,7,2};//价值 ,一定要注意对应关系,多一行,多一列 int w[7]={0,4,6,2,2,5,1};//重量 int c=12,n=6;//容量,个数 int num[12+1]={0};//预留空间多了一行和一列 int tem[12+1]={0};//中间变量 for(int i=1;i<=n;i++) //num[i][j],i个物品,j为容量时,的最大价值 { for(int j=1;j<=c;j++) { if(w[i]<=j)//可以选择 ,比较新加的和旧的哪个大 { num[j]=max(tem[j],tem[j-w[i]]+v[i]); } else//重量比容量大,不被选择,维持原来 { num[j]=tem[j]; } } for(int k=0;k<13;k++) { tem[k]=num[k]; } } for(int i=1;i<13;i++) { cout<<num[i]<<" "; } }
大大节省空间。
3、只保留一行的逆序求解程序;
#include<iostream> #include<string.h> #include<cmath> using namespace std; int main() { int v[7]={0,8,10,6,3,7,2};//价值 ,一定要注意对应关系,多一行,多一列 int w[7]={0,4,6,2,2,5,1};//重量 int c=12,n=6;//容量,个数 int num[12+1]={0};//预留空间多了一列 存储0位置 for(int i=1;i<=n;i++) //num[i][j],i个物品,j为容量时,的最大价值 { for(int j=c;j>0;j--)//因为需要用到前面的数据,所以逆向求解 { if(w[i]<=j)//可以选择 ,比较新加的和旧的哪个大 { num[j]=max(num[j],num[j-w[i]]+v[i]); } } } for(int i=1;i<13;i++) { cout<<num[i]<<" "; } }
4、利用递归的方法求解
#include<iostream> #include<string.h> #include<cmath> using namespace std; int Getnum(int n,int c) { int v[7]={0,8,10,6,3,7,2};//价值 ,一定要注意对应关系,多一行,多一列 int w[7]={0,4,6,2,2,5,1};//重量 int num=0; if(n==0)//边界条件 num=0; else { if(c<w[n]) { num=Getnum(n-1,c); } else { num=max(Getnum(n-1,c),Getnum(n-1,c-w[n])+v[n]); } } return num; } int main() { int c=12,n=6;//容量,个数 cout<<Getnum(6,12); }
利用这样的方法,程序简单,但是会增加时间复杂度。
5、将该问题改为循环输入
#include<iostream> #include<string.h> #include<cmath> using namespace std; const int N=50; int v[N]={0}; int w[N]={0}; int Getnum(int n,int c) { //int v[6]={8,10,6,3,7,2};//价值 ,一定要注意对应关系,多一行,多一列 //int w[6]={4,6,2,2,5,1};//重量 int num=0; if(n==0)//边界条件 num=0; else { if(c<w[n]) { num=Getnum(n-1,c); } else { num=max(Getnum(n-1,c),Getnum(n-1,c-w[n])+v[n]); } } return num; } int main() { int c,n;//容量,个数 while(cin>>c>>n) { for(int i=1;i<=n;i++) { cin>>v[i]; } for(int i=1;i<=n;i++) { cin>>w[i]; } cout<<Getnum(n,c); v[N]={0}; w[N]={0}; } }
6、
通过指针来传递数组
#include<iostream> #include<cmath> #include<string.h> using namespace std; int Sum(int n,int *P)//指针来传递数组 { int s; for(int i=0;i<n;i++) { s+=P[i]; } return s; } int main() { int v[5]={0,1,2,3,4}; cout<<Sum(5,v); }
二、
P02: 完全背包问题
题目
有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
/*"""
完全背包问题(每个物品可以取无限次)
:param N: 物品个数, 如 N=5
:param V: 背包总容量, 如V=15
:param weight: 每个物品的容量数组表示, 如weight=[5,4,7,2,6]
:param value: 每个物品的价值数组表示, 如value=[12,3,10,3,6]
:return: 返回最大的总价值
"""*/
代码实现
1、
#include <iostream> #include <cstring> using namespace std; const int N=50; int main() { int v[N]={0,12,3,10,3,6};//价值 int w[N]={0,5,4,7,2,6}; //重量 int m[N][N]; int n=5,c=15; //容量15 memset(m,0,sizeof(m)); cout<<m[2][0]<<endl; for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { m[i][j]=m[i-1][j]; for(int k=1;k<=(j/w[i]);k++) { if(m[i-1][j]<m[i-1][j-w[i]*k]+v[i]*k) m[i][j]=m[i-1][j-w[i]*k]+v[i]*k; } } } for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { cout<<m[i][j]<<' '; } cout<<endl; } return 0; }
多次循环比较要比的不仅仅是 m[i-1][j]和m[i-1][j-w[i]*k]+v[i]*k单一的一个数比较。随着k的变化,其实是m[i-1][j]、m[i-1][j-w[i]*k]+v[i]*k和新生成的m[i][j]三个数的比较。写成如下形式的话就会出错
#include <iostream> #include <cstring> #include<cmath> using namespace std; const int N=50; int main() { int v[N]={0,12,3,10,3,6};//价值 int w[N]={0,5,4,7,2,6}; //重量 int m[N][N]; int n=5,c=15; //容量15 memset(m,0,sizeof(m)); for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { for(int k=1;k<=(j/w[i]);k++) { m[i][j]=max(m[i-1][j],m[i-1][j-w[i]*k]+v[i]*k);//出错,只比较了两个数 } } } for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { cout<<m[i][j]<<' '; } cout<<endl; } return 0; }
以m[2][9]为例,本应该是15,这里出现12是由于,当k=2时,m[i-1][j-w[i]*k]+v[i]*k=m[1][1]+3*2=6,小于m[i-1][j]=12,覆盖掉了之前的15。这就是由于比较没有考虑新生成的数引起的错误。
但其实上面的代码也没有与新生成的m[i][j]作比较。这是少了覆盖这一层,结果便正确~
改为如下也对
#include <iostream> #include <cstring> #include<cmath> using namespace std; const int N=50; int main() { int v[N]={0,12,3,10,3,6};//价值 int w[N]={0,5,4,7,2,6}; //重量 int m[N][N]; int n=5,c=15; //容量15 memset(m,0,sizeof(m)); for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { m[i][j]=m[i-1][j];//相当于m[i][j]提前赋了个最大值,然后和自身比较找出最大的即可
for(int k=1;k<=(j/w[i]);k++) { m[i][j]=max(m[i][j],m[i-1][j-w[i]*k]+v[i]*k);// } } } for(int i=1;i<=n;i++) { for(int j=1;j<=c;j++) { cout<<m[i][j]<<' '; } cout<<endl; } return 0; }
以上方法是将完全背包转换为了01背包,比较状态是n个物品和n-1个物品状态比较,
2、空间的优化
#include<cstdio> #include<algorithm> #include<iostream> using namespace std; int w[300],c[300],f[300010]={0}; int V,n; int main() { V=15; n=5; int w[6]={0,12,3,10,3,6};//价值 int c[6]={0,5,4,7,2,6}; //重量 for(int i=1;i<=n;i++) { for(int j=c[i];j<=V;j++)//保证j>c[i] { f[j]=max(f[j-c[i]]+w[i],f[j]);//设 f[v]表示重量不超过v公斤的最大价值,最简单的方式 } } for(int i=1;i<=15;i++) cout<<f[i]<<" "; return 0; }
最简单的形式,判断条件和01背包相比,采用顺序,而非逆序,因为比较的是同一件物品从1加到n件的不同,而不跨越到上一个物品,而和上一个物品比较则根据下标来比的,简化了好多。
3、递归实现
#include<iostream> #include<string.h> using namespace std; const int N=6; int v[N]={0,12,3,10,3,6};//价值 int w[N]={0,5,4,7,2,6}; //重量 int dp(int n,int m)//n物品个数,m背包容量 { int Max; if(n==0) return 0; Max=dp(n-1,m); for(int i=1;i<=m/w[n];i++) { Max=max(Max,dp(n-1,m-w[n]*i)+v[n]*i);//三个比较求出最大值,注意下标比较多,区分开来 } return Max; } int main() { int n=dp(5,15); cout<<n; }
三、
P03: 多重背包问题
题目
有N种物品和一个容量为V的背包。第i种物品最多有n[i]件可用,每件费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
1、将多重背包转化为完全背包,只是将不确定的个数取n和c/w[i]的最小值,其他不变;
#include<iostream> #include<cmath> using namespace std; int Num[100][100]={0}; int main() { int n=3,c=8; int w[4]={0,1,2,2}; int v[4]={0,6,10,20}; int N[4]={0,10,5,2}; for(int i=1;i<=n;i++) //确定矩阵 { for(int j=1;j<=c;j++) { int d=min(j/w[i],N[i]); for(int k=1;k<=d;k++) { Num[i][j]=max(Num[i-1][j],Num[i-1][j-k*w[i]]+k*v[i]); } } } for(int j=1;j<=n;j++) { for(int i=1;i<=c;i++) { cout<<Num[j][i]<<" "; } cout<<endl; } int t[4]={0};//确定每种物品个数 int s=c;//价值总数 for(int i=n;i>0;i--) { int d=min(c/w[i],N[i]); for(int k=d;k>0;k--) { if(Num[i][s]==(Num[i-1][s-k*w[i]]+k*v[i]))//找出最大的加的数是什么,进而求出个数 { t[i]=k; s=s-k*w[i]; break; } } } for(int i=1;i<=n;i++) cout<<t[i]<<" "; cout<<endl; } //3 8 1 6 10 2 10 5 2 20 2 /*3 8 //3件物品,背包承重最大为8 1 6 10 //第一件物品, 重量为1,价值为6, 数目为10 2 10 5 2 20 2*/
求矩阵是转化为了完全背包,只是改了个数。
求每种物品具体用的个数本想着计算的过程中就得出,但是由于只有到运算最后才能得出最大的值,所以过程中并不好判断,只能先求出矩阵后再计算。
计算过程如下:64=24+2*20;所以三好物品用了两个;24=4*6;所以一号物品用了4个,求出。
2、改进空间复杂度
没找到和想出简单易于理解的程序。
3、
#include <iostream> using namespace std; int knapsack_limitnum(int *W, int *V, int *N, int *res, int n, int C) { int value = 0; int **f = new int*[n]; for(int i = 0; i < n; i++) { f[i] = new int[C+1]; } for(int i = 0; i < n; i++) for(int j = 0; j < C+1; j++) f[i][j] = 0; for(int y = 1; y < C+1; y++) { int count = min(N[0], y/W[0]); f[0][y] = (y < W[0])?0:(count * V[0]); } for(int i = 1; i < n; i++) { for(int y = 1; y < C+1; y++) { if(y < W[i]) { f[i][y] = f[i-1][y]; } else { int count = min(N[i], y/W[i]); f[i][y] = f[i-1][y]; for(int k = 1; k <= count; k++) { int temp = f[i-1][y-W[i]*k] + k*V[i]; if(temp >= f[i][y]) f[i][y] = temp; } } } } for(int i = 0; i < n; i++) { for(int y = 0; y < C+1; y++) cout << f[i][y] << " "; cout << endl; } value = f[n-1][C]; int j = n-1; int y = C; while(j) { int count = min(N[j], y/W[j]); for(int k = count; k > 0; k--) { if(f[j][y] == (f[j-1][y-W[j]*k]+k*V[j])) { res[j] = k; y = y - k*W[j]; break; } } j--; } res[0] = f[0][y]/V[0]; for(int i = 0;i < n; i++) { delete f[i]; f[i] = 0; } delete [] f; f = 0; return value; } void test1() { int n, C; while(cin >> n >> C) //n:物品个数,C:承重量 { int *W = new int[n]; int *V = new int[n]; int *N = new int[n]; int *res = new int[n]; for(int i =0; i < n; i++) res[i] = 0; //每种物品存放的个数 int w, v, n1, i = 0; while(i < n) //循环输入 { cin >> w >> v >> n1; W[i] = w; //重量 V[i] = v; //价值 N[i] = n1; //数目 i++; } int value = knapsack_limitnum(W, V, N, res, n, C); cout << value << endl;//最大值 for(int i = 0; i < n; i++) cout << res[i] << " ";//个数 cout << endl; delete res; //res = 0; //释放空间 delete N; //N = 0; delete V; //V = 0; delete W; //W = 0; } } int main() { test1(); return 0; } //3 8 1 6 10 2 10 5 2 20 2 /*3 8 //3件物品,背包承重最大为8 1 6 10 //第一件物品, 重量为1,价值为6, 数目为10 2 10 5 2 20 2*/
4、
自己编写循环输入和函数调用输出的整个程序。
(1)
#include<iostream> #include<cmath> using namespace std; int Num[100][100]={0}; int w[100]={0},v[100]={0},N[100]={0}; int main() { //int n=3,c=8; //int w[4]={0,1,2,2}; //int v[4]={0,6,10,20}; //int N[4]={0,10,5,2}; int n,c; while(cin>>n>>c) { for(int i=1;i<=n;i++) { cin>>w[i]>>v[i]>>N[i]; } for(int i=1;i<=n;i++) //确定矩阵 { for(int j=1;j<=c;j++) { int d=min(j/w[i],N[i]); for(int k=1;k<=d;k++) { Num[i][j]=max(Num[i-1][j],Num[i-1][j-k*w[i]]+k*v[i]); } } } for(int j=1;j<=n;j++) { for(int i=1;i<=c;i++) { cout<<Num[j][i]<<" "; } cout<<endl; } int t[4]={0};//确定每种物品个数 int s=c;//价值总数 for(int i=n;i>0;i--) { int d=min(c/w[i],N[i]); for(int k=d;k>0;k--) { if(Num[i][s]==(Num[i-1][s-k*w[i]]+k*v[i]))//找出最大的加的数是什么,进而求出个数 { t[i]=k; s=s-k*w[i]; break; } } } for(int i=1;i<=n;i++) cout<<t[i]<<" "; cout<<endl; Num[100][100]={0}; w[100]={0};v[100]={0};N[100]={0}; } return 0; } //3 8 1 6 10 2 10 5 2 20 2 /*3 8 //3件物品,背包承重最大为8 1 6 10 //第一件物品, 重量为1,价值为6, 数目为10 2 10 5 2 20 2*/
这是采用声明全局变量方法做的,也没有调用函数
(2)
#include<iostream> #include<cmath> using namespace std; int Num[100][100]={0}; int w[100]={0},v[100]={0},N[100]={0}; void dp(int *w,int *v,int *N,int n,int c)//注意矩阵地址的引用 { for(int i=1;i<=n;i++) //确定矩阵 { for(int j=1;j<=c;j++) { int d=min(j/w[i],N[i]); for(int k=1;k<=d;k++) { Num[i][j]=max(Num[i-1][j],Num[i-1][j-k*w[i]]+k*v[i]); } } } for(int j=1;j<=n;j++) { for(int i=1;i<=c;i++) { cout<<Num[j][i]<<" "; } cout<<endl; } int t[4]={0};//确定每种物品个数 int s=c;//价值总数 for(int i=n;i>0;i--) { int d=min(c/w[i],N[i]); for(int k=d;k>0;k--) { if(Num[i][s]==(Num[i-1][s-k*w[i]]+k*v[i]))//找出最大的加的数是什么,进而求出个数 { t[i]=k; s=s-k*w[i]; break; } } } for(int i=1;i<=n;i++) cout<<t[i]<<" "; cout<<endl; } int main() { //int n=3,c=8; //int w[4]={0,1,2,2}; //int v[4]={0,6,10,20}; //int N[4]={0,10,5,2}; int n,c; while(cin>>n>>c) { for(int i=1;i<=n;i++) { cin>>w[i]>>v[i]>>N[i]; } dp(w,v,N,n,c); Num[100][100]={0}; w[100]={0};v[100]={0};N[100]={0}; } return 0; } //3 8 1 6 10 2 10 5 2 20 2 /*3 8 //3件物品,背包承重最大为8 1 6 10 //第一件物品, 重量为1,价值为6, 数目为10 2 10 5 2 20 2*/
这是采用函数形式,注意定义函数调用矩阵时,用地址*。
四、
P04: 混合三种背包问题
问题
如果将P01、P02、P03混合起来。也就是说,有的物品只可以取一次(01背包),有的物品可以取无限次(完全背包),有的物品可以取的次数有一个上限(多重背包)。应该怎么求解呢?
五、
P05: 二维费用的背包问题
问题
二维费用的背包问题是指:对于每件物品,具有两种不同的费用;选择这件物品必须同时付出这两种代价;对于每种代价都有一个可付出的最大值(背包容量)。问怎样选择物品可以得到最大的价值。设这两种代价分别为代价1和代价2,第i件物品所需的两种代价分别为a[i]和b[i]。两种代价可付出的最大值(两种背包容量)分别为V和U。物品的价值为w[i]。
六、
P07: 有依赖的背包问题
简化的问题
这种背包问题的物品间存在某种“依赖”的关系。也就是说,i依赖于j,表示若选物品i,则必须选物品j。为了简化起见,我们先设没有某个物品既依赖于别的物品,又被别的物品所依赖;另外,没有某件物品同时依赖多件物品。
七、
P08: 泛化物品
定义
考虑这样一种物品,它并没有固定的费用和价值,而是它的价值随着你分配给它的费用而变化。这就是泛化物品的概念。
更严格的定义之。在背包容量为V的背包问题中,泛化物品是一个定义域为0..V中的整数的函数h,当分配给它的费用为v时,能得到的价值就是h(v)。
八、
P09: 背包问题问法的变化
以上涉及的各种背包问题都是要求在背包容量(费用)的限制下求可以取到的最大价值,但背包问题还有很多种灵活的问法,在这里值得提一下。但是我认为,只要深入理解了求背包问题最大价值的方法,即使问法变化了,也是不难想出算法的。
例如,求解最多可以放多少件物品或者最多可以装满多少背包的空间。这都可以根据具体问题利用前面的方程求出所有状态的值(f数组)之后得到。
还有,如果要求的是“总价值最小”“总件数最小”,只需简单的将上面的状态转移方程中的max改成min即可。
下面说一些变化更大的问法。