背包问题和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 }