0-1背包–回溯法
本文附加两种方法,数组法和构建类法,两种方法解题思路原理相同。
问题描述:
给定n个物品和一个背包。物品i的重量为wi,价值为vi,背包容量为c。
如何选择装入背包中的物品,使得装入背包的物品的价值最大?
函数、数组声明:
n物品的个数
c背包的容量
bestp最大价值
i第i个物品
cp当前包内物品价值
cw当前包内物品重量
w[i] 物品的重量
v[i]物品的价值
x[i]暂存物品的选中情况
beatx[i]物品的选中情况
算法描述:
当需要找出问题的解集,或者要求回答什么解是满足某些约束条件的最佳解时,往往要使用回溯法。
回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题
在问题的解空间树中,回溯法按深度优先策略,从根结点出发搜索解空间树。
在搜索解空间树时,只要左儿子结点是一个可行的结点,搜索就进入其左子树。当右子树中有可能有包含最优解时才进入右子树搜索。否则将右子树减去。
为了便于计算 上界,可先将物品依其单位重量价值从大到小排序 ,此后只要按顺序考察各物品即可。在实现时,由Bound计算当前结点处的上界。类Knap的数据成员记录解空间树中的结点信息,以减少参数传递及递归调用所需的栈空间。在解空间树的当前扩展结点处,仅当进人右子树时才计算上界Bound,以判断是否可将右子树剪去。进人左子树时不需计算上票,因为其上界与其父结点的上界相同。
数组法代码实现:
#include<iostream>
using namespace std;
int n,c,x[1010],v[1010],w[1010],bestx[1010];
int sum=0;
void Backtrack(int i,int cp,int cw)
{
if(i>n)
{
if(cp>sum)
{
sum=cp;
for(int i=1;i<=n;i++)
bestx[i]=x[i];
}
}
else
{
for(int j=0;j<=1;j++)//对物品装与不装进行讨论
{
x[i]=j;
if(cw+x[i]*w[i]<=c)
{
cw+=w[i]*x[i];
cp+=v[i]*x[i];
Backtrack(i+1,cp,cw);
cw-=w[i]*x[i];
cp-=v[i]*x[i];
}
}
}
}
int main()
{
cin>>n;
for(int i=1;i<=n;i++)
cin>>w[i];
for(int i=1;i<=n;i++)
cin>>v[i];
cin>>c;
Backtrack(1,0,0);
cout<<"物品的最大价值: "<<sum<<endl;
cout<<"被选中的物品是:";
for(int i=1;i<=n;i++)
{
if(bestx[i]==1)
cout<<" "<<i;
}
return 0;
}
构建类法代码实现:
#include<iostream>
using namespace std;
class Knap
{
friend int Knapsack(int v[],int w[],int c,int n);
private:
int Bound(int i);//计算上界
void Backtrack(int i);//回溯
int c;//背包容量
int n; //物品数
int *w;//物品重量数组
int *v;//物品价值数组
int cw;//当前重量
int cp;//当前价值
int bestp;//当前最优值
int *bestx;//当前最优解
int *x;//当前解
public:
void print()
{
cout<<"被选中的物品是: ";
for(int i=1; i<=n; i++)
{
if(bestx[i]==1)
cout<<i<<" ";
}
cout<<endl;
}
};
int Knap::Bound(int i)
{
int cleft=c-cw;//剩余容量
int b=cp;
//以物品单位重量价值递减序装入物品
while(i<=n&&w[i]<=cleft)
{
cleft-=w[i];
b+=v[i];
i++;
}
if(i<=n)
b+=v[i]*cleft/w[i];
return b;
}
void Knap::Backtrack(int i)
{
if(i>n)
{
if(cp>bestp)
{
bestp=cp;
for(int i=1;i<=n;i++)
bestx[i]=x[i];
}
return ;
}
if(cw+w[i]<=c)//搜索左子树
{
x[i]=1;
cw+=w[i];
cp+=v[i];
Backtrack(i+1);
cw-=w[i];
cp-=v[i];
}
if(Bound(i+1)>bestp)//搜索右子树
{
x[i]=0;
Backtrack(i+1);
}
}
class Object
{
friend int Knapsack(int v[],int w[],int c,int n );
public:
int operator <=(Object a)const
{
return d >=a.d;
}
private:
int ID;
float d;
};
int Knapsack(int v[],int w[],int c,int n)
{
//初始化Knap::Backtrack
int W=0;
int V=0;
Object *Q=new Object[n];
for(int i=1;i<=n;i++)
{
Q[i-1].ID=i;
Q[i-1].d=1.0*v[i]/w[i];
V+=v[i];
W+=w[i];
}
if(W<=c) //装入所有物品
return V;
//依物品单位重量价值排序,冒泡排序
float f;
for(int i=0;i<n;i++)
{
for(int j=i;j<n;j++)
{
if(Q[i].d<Q[j].d)
{
f=Q[j].d;
Q[j].d=Q[i].d;
Q[i].d=f;
}
}
}
Knap K;
K.v=new int[n+1];
K.w=new int[n+1];
K.x=new int[n+1];
K.bestx=new int[n+1];
K.x[0]=0;
K.bestx[0]=0;
for(int i=1;i<=n;i++)
{
K.v[i]=v[Q[i-1].ID];
K.w[i]=w[Q[i-1].ID];
}
K.cp=0;
K.cw=0;
K.c=c;
K.n=n;
K.bestp=0;
//回溯搜索
K.Backtrack(1);
K.print();
delete []Q;
delete []K.w;
delete []K.v;
return K.bestp;
}
int main()
{
int v[100];
int w[100];
int c,n;
cin>>n;
for(int i=1;i<=n;i++)
cin>>w[i];
for(int i=1;i<=n;i++)
cin>>v[i];
cin>>c;
cout<<"物品的最大价值是: "<<Knapsack(v,w,c,n);
return 0;
}