一:0-1背包问题
- 容量V是体积或者重量的定义
- 物品总数:n,每个物品有自己的体积或者重量+自己的价值
- 求:在不超过容量的前提下,使得总价值最大
首先求问题的状态,也就是解决问题的函数
这里应该有2个参数:物品总数和背包容量
F(n,C)----把n个物品有选择地放入容量为C的背包,使得F(放入背包的物品总价值)最大
说白了有两种状态:
- 第一种:F(i,c)=F(i-1,c)-------当前这i个物品,不把这个物品放入背包
- 第二种:F(i,c)=v(i)+F(i-1,c-w(i))------选择了这个物品,然后背包容量减去该体积
上述就是:
自顶向下解决问题,又叫状态转移方程
一:自顶向下解决问题
#include<vector> #include<algorithm> using namespace std; class soluction{ private: vector<int> w; vector<int> v; //记忆化搜索:memo[index][c]---当前的index物品的总容量为c vector<vector<int>> memo; int bestValue(int index,int c){ //从0~index的物品中选,当前背包容量c if(index<0||c<=0) return 0; if(memo[index][c]!=-1) return memo[index][c]; int res=bestValue(index-1,c); if(w[index]<=c){//当前物品放入包中 res=max(res,v[index]+bestValue(index-1,c-w[index])); } memo[index][c]=res; return res; } public: int knapsack(const vector<int>& w,const vector<int>& v,int capcity){ //参数:n个物品的重量和价值,背包的容量 this->w=w; this->v=v; int n=w.size(); memo=vector<vector<int>>(n,vector<int>(capcity+1,-1)); return bestValue(n-1,capcity); } };
memo=vector<vector<int>>(n,vector<int>(capcity+1,-1));
-----注意是可以取到容量capcity,所以vector容量是+1
不难,思考方式就是选不选该物品,只不过采取倒着的方式,限制就是容量
注意在选择该物品时候,要保证加入该物品,容量不超标。
二:自底向上解决问题
class soluction{ public: int knapsack(const vector<int>& w,const vector<int>& v,int cap){ assert(w.size()==v.size()); int n=w.size(); vector<vector <int>> memo(n,vector <int>(cap+1,-1)); //自底向上 for(int j=0;j<=cap;j++){ memo[0][j]=(j>=w[0]?v[0]:0); } for(int i=1;i<n;i++){ for(int j=0;j<=cap;j++){ memo[i][j]=memo[i-1][j]; if(j>=w[i]){ memo[i][j]=max(memo[i][j],v[i]+memo[i-1][j-w[i]]); } } } return memo[n-1][cap]; } };
首先计算第一行---也就是只考虑编号为0的物品(保证背包容量大于该物品)
接下来依据第一行,计算1~n-1行
依据容量0~cap计算------要么取,要么不取。取后把已经计算的加入
进行优化
int knapsack_2(const vector<int>& w,const vector<int>& v,int c){ /*优化一:因为只有memo[n-1][c]是我们的想要的结果 当前行的数值只依赖与前一行,所以考虑只开辟两行 利用奇偶去覆盖行数*/ int n=w.size(); vector<vector<int>> memo(2,vector<int>(c+1,-1)); for(int j=0;j<=c;j++) memo[0][j]=(w[0]<=j?w[0]:0); for(int i=1;i<n;i++) for(int j=0;j<=c;j++){ memo[i%2][j]=memo[(i-1)%2][j];//先默认取前一行 if(w[i]<=j){ memo[i%2][j]=//二选一 max(memo[i%2][j],v[i]+memo[(i-1)%2][j-w[i]]); } } return memo[(n-1)%2][c];//注意是n-1; }
int knapsack_2(const vector<int>& w,const vector<int>& v,int c){ /*优化一:因为只有memo[n-1][c]是我们的想要的结果 当前行的数值只依赖与前一行,所以考虑只开辟两行 利用奇偶去覆盖行数*/ int n=w.size(); vector<vector<int>> memo(2,vector<int>(c+1,-1)); for(int j=0;j<=c;j++) memo[0][j]=(w[0]<=j?w[0]:0); for(int i=1;i<n;i++) for(int j=0;j<=c;j++){ memo[i&1][j]=memo[(i-1)%2][j];//先默认取前一行 if(w[i]<=j){ memo[i&1][j]=//二选一 max(memo[i&1][j],v[i]+memo[(i-1)&1][j-w[i]]); } } return memo[(n-1)&1][c];//注意是n-1; }
奇偶可以利用:%2或&1来计算
优化二思想:
因为更新时候,如果后一行的容量比新考虑的物品编号值小,那么直接复制前一行的结果,所以选择从后往前考虑,这样可以直接在原基础上进行覆盖,不必将物品编号单设一个维度
int knapsack_single(const vector<int>& w,const vector<int>& v,int c){ int n=w.size(); vector<int>memo(c+1,-1);//只记录容量 for(int j=0;j<=c;j++) memo[j]=(w[0]<=j?v[0]:0); for(int i=1;i<n;i++) for(int j=c;j>=w[i];j--)//从后往前编号 if(w[i]<=j){ memo[j]=max(memo[j],v[i]+memo[j-w[i]]); } return memo[c]; }
这里因为只有一行,所以只从改变的开始,也就是j容量在大于新编号容量时候才更新
而且由于有了数据,不去考虑不变情况
最后返回memo[C]