大家好,我是一只学弱狗,记录学习的点点滴滴!
优质文章
优质专栏
题目描述
有n件物品要装入背包,第i件物品的重量wi,价值vi,i=1,2,…,n,背包最多允许装入的重量为B,问如何选择装入背包的物品,使得总价值达到最大?
穷举法
解是一个取值只能是0或1的向量,最先考虑考虑的是穷举,即遍历出所有的情况,然后根据约束条件对所有情况进行筛选,从符合条件的情况中选择最优解。
#include <iostream>
using namespace std;
//最大价值
int maxv = -1;
void dfs(int i,int *recode,int n,int *w,int *v,int B,int *result){
if(i==n){//得到一个解
int cv=0;//当前解的价值
int cw=0;//当前解的重量
for(int i=0;i<n;i++){
cw += recode[i]*w[i];
cv += recode[i]*v[i];
}
if(cw<=B && cv>maxv){//如果当前解的重量小于等于背包重量且价值大于之前的,就替换
maxv = cv;
for(int i=0;i<n;i++) result[i]=recode[i];//更新解
}
return ;
}
recode[i]=0;//当前商品不放
dfs(i+1,recode,n,w,v,B,result);
recode[i]=1;//当前商品放
dfs(i+1,recode,n,w,v,B,result);
}
int main(){
freopen("背包问题.txt","r",stdin);
int n,B;//初始化
cin>>n>>B;
int *w = new int[n];
int *v = new int[n];
for(int i=0;i<n;i++) cin>>w[i];
for(int i=0;i<n;i++) cin>>v[i];
int * recode = new int[n];//存放临时的解状态
int * result = new int[n];//存放最优方案
dfs(0,recode,n,w,v,B,result);
cout<<maxv<<endl;
for(int i=0;i<n;i++) cout<<result[i]<<" "; cout<<endl;
return 0;
}
剪枝
穷举法虽然可以得到正确的解,但是它其实是对整个解空间树都进行了遍历,我们可以通过一个约束函数来对整个解空间树进行剪枝以提高算法的执行速度。
#include <iostream>
using namespace std;
int bound(int i,int n,int *v){
int sum = 0;
while(i<n){
sum+=v[i++];
}
return sum;
}
//i表示当前处理的是第i个商品
//n表示商品总的数量
//B背包的总重量
//cw背包当前所装物品的总重量
void dfs(int i,int n,int B,int cw,int cv,int *w,int *v,int *recode,int &max,int *result){
if(i>=n){
max = cv;
for(int i=0;i<n;i++) result[i] = recode[i];
return ;
}
if(cw+w[i]<=B){//当前商品可以放得下
recode[i]=1;
dfs(i+1,n,B,cw+w[i],cv+v[i],w,v,recode,max,result);
}
if(cv+bound(i+1,n,v)>max){//如果不放当前商品,剩下的商品总价值+目前已有的总价值>最大价值,则继续深搜
recode[i]=0;
dfs(i+1,n,B,cw,cv,w,v,recode,max,result);
}
}
int main(){
freopen("背包问题.txt","r",stdin);
int n,B;//初始化
cin>>n>>B;
int *w = new int[n];
int *v = new int[n];
for(int i=0;i<n;i++) cin>>w[i];
for(int i=0;i<n;i++) cin>>v[i];
int * recode = new int[n];
int * result = new int[n];//存放最优方案
int max_v = -1;
dfs(0,n,B,0,0,w,v,recode,max_v,result);
cout<<max_v<<endl;
for(int i=0;i<n;i++) cout<<result[i]<<" "; cout<<endl;
return 0;
}
递归
剪枝对穷举算法进行了改进,不必遍历整个解空间树,除此之外,是否还有方法呢?答案是有的,可以构造一个递归公式knapsackSR(h,i,c)=max{ knapsackSR(h,i-1,c),knapsackSR(h,i-1,c-wi)+pi }
#include <iostream>
using namespace std;
//从start到n,当背包容量为B时,所能产生的最大效益值
int dfs(int start,int n,int B,int *w,int *v){
if(start>=n)
return 0;
if(w[start]<=B)//如果能装下,则比较装与不装哪种情况下产生的效益值大
return max(dfs(start+1,n,B,w,v),dfs(start+1,n,B-w[start],w,v)+v[start]);
else//如果装不下
return dfs(start+1,n,B,w,v);
}
int main(){
freopen("背包问题.txt","r",stdin);
int n,B;//初始化
cin>>n>>B;
int *w = new int[n];
int *v = new int[n];
for(int i=0;i<n;i++) cin>>w[i];
for(int i=0;i<n;i++) cin>>v[i];
int max_v = -1;
cout<<dfs(0,n,B,w,v)<<endl;
return 0;
}
填表法
#include <iostream>
using namespace std;
int main(){
freopen("背包问题.txt","r",stdin);
int n,B;
cin>>n>>B;
int *w = new int[n];
int *v = new int[n];
for(int i=0;i<n;i++) cin>>w[i];
for(int i=0;i<n;i++) cin>>v[i];
int **map = new int*[n+1];
for(int i=0;i<=n;i++){
map[i] = new int[B+1];
}
for(int i=0;i<=n;i++)
for(int j=0;j<=B;j++)
map[i][j] = 0;
for(int i=1;i<=n;i++){
for(int j=1;j<=B;j++){
if(j>=w[i-1]){
map[i][j] = max(map[i-1][j],map[i-1][j-w[i-1]]+v[i-1]);
}else{
map[i][j] = map[i-1][j];
}
}
}
cout<<map[n][B]<<endl;
return 0;
}
滚动数组
#include <iostream>
using namespace std;
int main(){
freopen("背包问题.txt","r",stdin);
int n,B;
cin>>n>>B;
int *w = new int[n];
int *v = new int[n];
for(int i=0;i<n;i++) cin>>w[i];
for(int i=0;i<n;i++) cin>>v[i];
int **map = new int*[2];
for(int i=0;i<2;i++){
map[i] = new int[B+1];
}
for(int i=0;i<2;i++)
for(int j=0;j<=B;j++)
map[i][j]=0;
for(int i=1;i<=n;i++){
for(int j=0;j<=B;j++){
if(j>=w[i-1]){
map[i%2][j] = max(map[(i+1)%2][j],map[(i+1)%2][j-w[i-1]]+v[i-1]);
}else{
map[i%2][j] = map[(i+1)%2][j];
}
}
}
cout<<map[n%2][B]<<endl;
return 0;
}