背包问题和01背包问题是很经典的关于动态规划和贪心算法的题目。
这两个问题很相似,01背包是有一个容量为c的背包,装入一些质量为w[ ]的且价值为v[ ]的物品,每次只能选择放入或者不放,不能只放一部分某个物品。求出可以让背包装最大价值的一个x[ ],其中的每一项表示第 i 个物品是否要装入。
背包问题跟01背包相似,但是可以装入部分商品。
背包问题可以用贪心算法求解,01背包则要用动态规划。
先来说01背包
举个例子:
c=5,即背包的容量是5.放入以下质量和价值的物品
| 编号 | 质量w | 价值v |
| 0 | 1 | 6 |
| 1 | 3 | 12 |
| 2 | 2 | 10 |
根据前面的动态规划的解法,动态规划一般需要一个二维的数组存放每一步计算得到的动态结果,本例用矩阵 m 表示,行标表示商品编号 用 i 表示,列标表示变化的 j ,即容量
| 0 | 1 | 2 | 3 | 4 | 5 | |
| 0 | 0 | 6 | 10 | 16 | 18 | 22 |
| 1 | 0 | 0 | 10 | 12 | 12 | 22 |
| 2 | 0 | 0 | 10 | 10 | 10 | 10 |
本例的最佳装法解x应该是x={0,1,1},也就是装入第二个和第三个,总最大价值为12+10=22
我写了一个简单的代码
1 #include<iostream>
2 using namespace std;
3 int m[3][6];//动态规划中的矩阵
4 int v[3]={12,10,6};//价值
5 int w[3]={3,2,1};//质量
6 int c=5;
7 void knapsack()//v和w的长度都是3
8 {
9 int n=2;
10 int jmax=min(w[n]-1,c);//防止某一个物件比总容量c更大
11
12 //背包里只有第n个物件
13 //后面的表达式里有计算i+1项的,所以要提前把最后一行计算出来
14 for(int j=0;j<=jmax;j++)
15 m[n][j]=0;
16 for(int j=jmax+1;j<=c;j++)
17 m[n][j]=v[n];
18 //背包里的物件从n-1个开始增多
19 for(int i=n-1;i>=0;i--)
20 {
21 int jmax=min(w[i]-1,c);
22 for(int j=0;j<=jmax;j++)
23 m[i][j]=m[i+1][j];//不装这个
24 for(int j=jmax+1;j<=c;j++)
25 {
26 m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);//假如装入计算那个质量更大
27 }
28 }
29 cout<<m[0][5]<<endl;
30 }
31 void traceback()
32 {
33 int x[3];
34 for(int i=0;i<=1;i++)
35 {
36 if(m[i][5]==m[i+1][5])
37 x[i]=0;
38 else
39 {
40 x[i]=1;
41 }
42 }
43 //计算最后一个物品是否放入
44 int sum=0;
45 for(int i=0;i<=1;i++)
46 {
47
48 if(x[i]==1)
49 sum+=v[i];
50
51 }
52 if(sum==m[0][5])
53 {
54 x[2]=0;
55 }
56 else
57 {
58 x[2]=1;
59 }
60 cout<<x[0]<<x[1]<<x[2];
61 }
62 int main()
63 {
64 knapsack();
65 traceback();
66 return 0;
67 }
要理解01背包问题,一定要理解这个m矩阵,我换一个顺序再写一次,假如现在的物品顺序变成下面这样
| 编号 | 质量w | 价值v |
| 0 | 3 | 12 |
| 1 | 2 | 10 |
| 2 | 1 | 6 |
那么矩阵m的变化如下:
| 0 | 1 | 2 | 3 | 4 | 5 | |
| 0 | 0 | 6 | 10 | 16 | 18 | 22 |
| 1 | 0 | 6 | 10 | 16 | 16 | 16 |
| 2 | 0 | 6 | 6 | 6 | 6 | 6 |
要把01背包问题弄清楚,一定要自己写一遍这个矩阵。这个矩阵的计算要靠下一行,所以最前面先计算好最后一行。前面的行的结果只能比后面的大,因为m[ i , j ] 表示背包容量为 j ,可以选择的物品从i,i+1……n。
所有动态规划的问题都要很清楚的理解动态规划的动态矩阵的写法。
牛客上也有这道题,我写了一个版本提交了,牛客网要求输出背包可以承受的最大价值。我把输入输出改成动态输入输出就可以了。
1 #include<iostream>
2 using namespace std;
3 int knapsack(int c,int *w,int *v,int n)
4 {
5 n=n-1;//有n个物品,但是下标是从n-1开始计算的
6 int m[n+1][c+1];
7 int jmax=min(w[n]-1,c);//防止某一个物件比总容量c更大
8
9 //背包里只有第n个物件
10 //后面的表达式里有计算i+1项的,所以要提前把最后一行计算出来
11 for(int j=0;j<=jmax;j++)
12 m[n][j]=0;
13 for(int j=jmax+1;j<=c;j++)
14 m[n][j]=v[n];
15 //背包里的物件从n-1个开始增多
16 for(int i=n-1;i>=0;i--)
17 {
18 int jmax=min(w[i]-1,c);
19 for(int j=0;j<=jmax;j++)
20 m[i][j]=m[i+1][j];//不装这个
21 for(int j=jmax+1;j<=c;j++)
22 {
23 m[i][j]=max(m[i+1][j],m[i+1][j-w[i]]+v[i]);//假如装入计算那个质量更大
24 }
25 }
26 return m[0][c];
27 }
28 void package()
29 {
30 int c,n,i=0;
31 cin>>c>>n;//输入容量和物品的个数
32 int w[n],v[n];
33 for(int i=0;i<n;i++)
34 {
35 cin>>w[i]>>v[i];
36 }
37
38 cout<<knapsack(c,w,v,n);
39 }
40 int main()
41 {
42 package();
43 return 0;
44 }
写了一个c++版本
1 #include<iostream>
2 #include<vector>
3 using namespace std;
4 int knapsack(int c,vector<int> w,vector<int> v,int n)
5 {
6 n=n-1;//有n个物品,但是下标是从n-1开始计算的
7
8 vector<vector<int>> m(n+1);//m矩阵要从最后一行开始算,所以最好把m的大小先固定了
9 int jmax=min(w[n]-1,c);//防止某一个物件比总容量c更大
10
11 //背包里只有第n个物件
12 //后面的表达式里有计算i+1项的,所以要提前把最后一行计算出来
13 vector<int> temp;
14 for(int j=0;j<=jmax;j++)
15 temp.push_back(0);
16 for(int j=jmax+1;j<=c;j++)
17 temp.push_back(v[n]);
18 m[n]=temp;
19
20 //背包里的物件从n-1个开始增多
21 for(int i=n-1;i>=0;i--)
22 {
23 vector<int> tem;
24 int jmax=min(w[i]-1,c);
25 for(int j=0;j<=jmax;j++)
26 tem.push_back(m[i+1][j]);//不装这个
27 for(int j=jmax+1;j<=c;j++)
28 {
29 tem.push_back(max(m[i+1][j],m[i+1][j-w[i]]+v[i]));//假如装入计算哪个质量更大
30 }
31 m[i]=tem;
32 }
33 return m[0][c];
34 }
35 void package()
36 {
37 int c,n;
38 cin>>c>>n;//输入容量和物品的个数
39 vector<int> weight;
40 vector<int> value;
41 int temp1,temp2;
42 for(int i=0;i<n;i++)
43 {
44 cin>>temp1>>temp2;
45 weight.push_back(temp1);
46 value.push_back(temp2);
47 }
48
49 cout<<knapsack(c,weight,value,n);
50 }
51 int main()
52 {
53 package();
54 return 0;
55 }