三种思路:递归、改进递归(记忆化搜索)、自底向上动态规划
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
/*
问题描述:一个背包,容量为C。现在有n种不同的物品,编号为0-n-1,其中每一种
物品的重量为w(i),价值为v(i)。问如何将物品装入背包使总价值量
最大。
思考:双约束下的选取组合最优解问题,物品数、背包容量约束的情况下求价值量最大。
每个物品有取/不取两种策略,故n个物品的策略总数为(2^n)*n,这也是暴力
解法的时间复杂度。
常规思路:递归、改进递归(记忆化搜索)、自底向上动态规划
函数定义:F(n,C)表示在n个物品中选取放入容量为C的背包中。
选取策略表示:F(i,c)=max(F(i-1,c),v(i)+F(i-1,c-w(i))),即每次都可以
不选取当前物品or选取当前物品。
*/
/*
思路一:递归。考虑递归出口,显然可选取物品的索引低于下界时本次贡献价值量返回0,
背包容量不足时本次贡献价值量返回0。
*/
class Knapsack01_1{
private:
//考虑0-index的物品,填充当前背包容量c,组合的最大值
int bestValue(const vector<int> &w,const vector<int> v,int index,int c){
if(index<0||c<=0){ //递归出口
return 0;
}
//尝试向背包中放入物品,两种策略选其优
//int res=max(bestValue(w,v,index-1,c),v[index]+bestValue(w,v,index-1,c-w[index]));
//另一种更好的写法:容量足够才考虑另一种策略,避免多余的递归
int res=bestValue(w,v,index-1,c);
if(c>=w[index]){
res=max(res,v[index]+bestValue(w,v,index-1,c-w[index]));
}
return res;
}
public:
int knapsack01(const vector<int> &w,const vector<int> &v,int C){
int n=w.size();
return bestValue(w,v,n-1,C);//递归函数bestValue,考虑0-n-1共n个物品
}
};
/*
思路二:优化递归。双变量index和c构成了多个数据对,递归过程中存在对一个数据对的多次重复计算。
即存在重叠子问题和最优子结构,使用记忆化搜索进行优化。
*/
class Knapsack01_2{
private:
//memo[i][j]表示给定一个index和c之后相应的状态,用于进行记忆化搜索
vector<vector<int>> memo;
//考虑0-index的物品,填充当前背包容量c,组合的最大值
int bestValue(const vector<int> &w,const vector<int> v,int index,int c){
if(index<0||c<=0){ //递归出口
return 0;
}
/*
与单纯递归的区别:先检查memo中是否存储了计算结果(避免重复计算)
*/
if(memo[index][c]!=-1){
return memo[index][c];
}
int res=bestValue(w,v,index-1,c);
if(c>=w[index]){
res=max(res,v[index]+bestValue(w,v,index-1,c-w[index]));
}
//完成记忆化的过程,将当前给定index、c的状态结果记录到memo[][]中
memo[index][c]=res;
return res;
}
public:
int knapsack01(const vector<int> &w,const vector<int> &v,int C){
int n=w.size();
/*
为了实现记忆化搜索,基于两个约束条件的情况,构建一个二维数组。共有n行(对应n个物品)
有C+1列,对应0-C个背包容量,且初始化为-1。
*/
memo=vector<vector<int>>(n,vector<int>(C+1,-1));
return bestValue(w,v,n-1,C);//递归函数bestValue,考虑0-n-1共n个物品
}
};
/*
思路三:自底向上分析,实现动态规划。
*/
class Knapsack01_3{
private:
//memo[i][j]表示给定一个index和c之后相应的状态,用于进行记忆化搜索
vector<vector<int>> memo;
//考虑0-index的物品,填充当前背包容量c,组合的最大值
int bestValue(const vector<int> &w,const vector<int> v,int index,int c){
if(index<0||c<=0){ //递归出口
return 0;
}
/*
与单纯递归的区别:先检查memo中是否存储了计算结果(避免重复计算)
*/
if(memo[index][c]!=-1){
return memo[index][c];
}
int res=bestValue(w,v,index-1,c);
if(c>=w[index]){
res=max(res,v[index]+bestValue(w,v,index-1,c-w[index]));
}
//完成记忆化的过程,将当前给定index、c的状态结果记录到memo[][]中
memo[index][c]=res;
return res;
}
public:
int knapsack01(const vector<int> &w,const vector<int> &v,int C){
int n=w.size();
if(n==0){
return 0;
}
/*
为了实现记忆化搜索,基于两个约束条件的情况,构建一个二维数组。共有n行(对应n个物品)
有C+1列,对应0-C个背包容量,且初始化为-1。
*/
memo=vector<vector<int>>(n,vector<int>(C+1,-1));
//return bestValue(w,v,n-1,C);//递归函数bestValue,考虑0-n-1共n个物品
//动态规划不同于自顶向下的递归,它是自底向上思考,故先处理最基础的问题
//考虑memo第0行
for(int j=0;j<C+1;j++){
memo[0][j]=(j>=w[0]?v[0]:0);
}
//解决最基础的问题后,剩下是向上递推的过程,即逐行逐列的更新值
for(int i=1;i<n;i++){
for(int j=0;j<C+1;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][C];
}
};
int main(){
return 0;
}