有n个物品和1个购物车,每个物品i对应价值为vi,重量wi,购物车的容量为W(你也可以将重量设定为体积)。每个物品只有一件,要么装入,要么不装入,不可拆分。如何选取物品装入购物车,使购物车所装入的物品的总价值最大?
(1)定义问题的解空间
购物车问题属于典型的0-1背包问题,每个物品有且只有两种状态,要么装入购物车,要么不装入。用变量xi表示第i种物品是否被装入购物车的行为,xi的取值为0或1。该问题解的形式是一个n元组,且每个分量的取值为0或1。
解空间:{x1,x2,…,xi,…,xn}
显约束:xi =0或1
(2)确定解空间的组织结构
问题的解空间描述了2n种可能的解,即n个元素组成的集合所有子集个数。问题的解空间树为子集树,解空间树的深度为问题的规模n。
(3)搜索解空间
· 约束条件
购物车问题的解空间包含2n种可能的解,需要设置约束条件,来判断所有可能的解装入背包的物品的总重量是否超出购物车的容量,如果超出,为不可行解;否则为可行解。搜索过程不再搜索那些导致不可行解的结点及其孩子结点。
约束条件为:
· 搜索过程
从根结点开始,以广度优先的方式进行搜索。根节点首先成为活结点,也是当前的扩展结点。一次性生成所有孩子结点,由于子集树中约定左分支上的值为“1”,因此沿着扩展结点的左分支扩展,则代表装入物品;由于子集树中约定右分支上的值为“0”,因此沿着扩展结点的右分支扩展,则代表不装入物品。此时,判断是否满足约束条件和限界条件,如果满足,则将其加入队列中;反之,舍弃。然后再从队列中取出一个元素,作为当前扩展结点,搜索过程队列为空时为止。
假设现在有4个物品和购物车的容量,每个物品的重量w为(2,5,4,2),价值v为(6,3,5,4),购物车的容量为10(W=10。求在不超过购物车容量的前提下,把哪些物品放入购物车,才能获得最大价值。
当前放入购物车的物品价值cp=0;当前剩余物品价值rp=sumv;当前剩余容量rw=W;当前处理物品序号为1;当前最优值bestp=0。解向量为x[]=(0,0,0,0),创建一个根结点Node(cp,rp,rw,id),标记为A,加入先进先出队列q中。cp为装入购物车的物品价值,rp剩余物品的总价值,rw为剩余容量,id为物品号,x[]为当前解向量,
时间复杂度:
空间复杂度:
空间主要耗费在Node结点里面存储的变量和解向量,因为最多有O(2n+1)个结点,而每个结点的解向量需要O(n)个空间,则空间复杂度为O(n*2n+1)。其实每个结点都记录解向量的办法是很笨的噢,我们可以用指针记录当前结点的左右孩子和父亲,到达叶子时逆向找其父亲结点,直到根结点,就得到了解向量,这样空间复杂度降为O(n),大家不妨动手写写看。
算法优化拓展:
优先队列优化,以当前结点的上界为优先值,把普通队列改成优先队列,即优先队列式分支限界法。优先级定义为活结点代表的部分解所描述的装入的物品价值上界,该价值上界越大,优先级越高。活结点的价值上界up =活结点的cp+剩余物品装满购物车剩余容量的最大价值rp'。
//program 6.2 01背包 普通队列bfs AC 831ms
#include<iostream>
#include<queue>
using namespace std;
const int maxn=105;
int n; //物品数量
double W; //背包容量
double w[maxn],v[maxn];//w[i]表示第i个物品的重量,v[i]表示第i个物品的价值
double bestp,sumv; //当前重量,当前价值,最优值,总价值
bool bestx[maxn]; //最优解
struct node{
double cp,rp; //cp当前放入背包的物品价值,rp剩余物品的价值
double rw; //剩余容量
int id; //物品号
node() {}
node(double _cp,double _rp,double _rw,int _id){
cp=_cp;
rp=_rp;
rw=_rw;
id=_id;
}
};
void knapsack_bfs(){
queue<node> q; //创建一个普通队列(先进先出)
q.push(node(0,sumv,W,1)); //根结点入队
while(!q.empty()){ //如果队列不空
node cur,lc,rc;//定义三个结点型变量
cur=q.front();//取出队头元素
q.pop(); //队头元素出队
// cout<<cur.cp<<" "<<cur.rp<<" "<<cur.rw<<" "<<cur.id<<endl;
int t=cur.id;//当前物品序号
if(t>n) continue;
if(cur.cp+cur.rp<bestp) continue;
int cp=cur.cp;
int rp=cur.rp-v[t];
if(w[t]<=cur.rw){ //满足约束条件,可以放入
lc=node(cp+v[t],rp,cur.rw-w[t],t+1);//生成左孩子
if(lc.cp>bestp)//比最优值大更新
bestp=lc.cp;
q.push(lc);//左孩子入队
}
if(cp+rp>bestp){//满足限界条件
rc=node(cp,rp,cur.rw,t+1);//生成右孩子
q.push(rc);//右孩子入队
}
}
}
int main(){
int t;//t表示测试用例数
cin>>t;
while(t--){
cin>>n>>W;
bestp=0.0,sumv=0.0; //sumv为所有物品的总价值
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
sumv+=v[i];
}
knapsack_bfs();
cout<<bestp<<endl;
}
return 0;
}
/*测试数据
1
4 10
2 6
5 3
4 5
2 4
*/
//program 6.2 01背包 优先队列bfs AC 3ms
#include<iostream>
#include<algorithm>
#include<queue>
using namespace std;
const int maxn=105;
int n; //物品数量
double W; //背包容量
double w[maxn],v[maxn];//w[i]表示第i个物品的重量,v[i]表示第i个物品的价值
double a[maxn],b[maxn];//辅助数组
double bestp,sumv; //当前重量,当前价值,最优值,总价值
bool bestx[maxn]; //最优解
struct goods{//定义物品结构体,包含物品序号和单位重量价值
int idx;
double p;//单位重量价值
}g[maxn];
bool cmp(goods a1,goods a2){//按照物品单位重量价值由大到小排序
return a1.p>a2.p;
}
struct node{
double cp,up; //cp当前放入背包的物品价值,up=cp+brp
double rw; //剩余容量
int id; //物品号
node() {}
node(double _cp,double _up,double _rw,int _id){
cp=_cp;
up=_up;
rw=_rw;
id=_id;
}
};
bool operator <(const node &a, const node &b){//队列的优先级,up越大越优先
return a.up<b.up;
}
double bound(int i,double cleft){//计算brp,剩余物品装满剩余容量获得的最大价值
double brp=0.0;
while(i<=n&&a[i]<cleft){
cleft-=a[i];//排序后的物品重量
brp+=b[i];//排序后的物品价值
i++;
}
if(i<=n)//通过切割装满背包
brp+=b[i]/a[i]*cleft;
return brp;
}
void knapsack_prioritybfs(){
priority_queue<node> q; //创建一个优先队列,优先级为装入购物车的物品价值上界up
q.push(node(0,bound(1,W),W,1)); //根结点入队
while(!q.empty()){ //如果队列不空
node cur,lc,rc;//定义三个结点型变量
cur=q.top();//取出队头元素
q.pop(); //队头元素出队
cout<<cur.cp<<" "<<cur.up<<" "<<cur.rw<<" "<<cur.id<<endl;
int t=cur.id;//当前物品序号
if(t>n) continue;
if(cur.up<bestp) continue;
if(a[t]<=cur.rw){ //满足约束条件,可以放入
lc=node(cur.cp+b[t],cur.up,cur.rw-a[t],t+1);//生成左孩子
if(lc.cp>bestp)//比最优值大更新
bestp=lc.cp;
q.push(lc);//左孩子入队
}
double up=cur.cp+bound(t+1,cur.rw);
if(up>bestp){//满足限界条件
rc=node(cur.cp,up,cur.rw,t+1);//生成右孩子
q.push(rc);//右孩子入队
}
}
}
int main(){
int t;//t表示测试用例数
cin>>t;
while(t--){
cin>>n>>W;
bestp=0.0;
for(int i=1;i<=n;i++){
cin>>w[i]>>v[i];
g[i].idx=i;
g[i].p=v[i]/w[i];
}
sort(g+1,g+n+1,cmp);
for(int i=1;i<=n;i++){//把排序后的数据传递过去
a[i]=w[g[i].idx];//a[]为排序后的物品重量
b[i]=v[g[i].idx];//b[]为排序后的物品价值
}
knapsack_prioritybfs();
cout<<bestp<<endl;
}
return 0;
}
/*测试数据
1
4 10
2 6
5 3
4 5
2 4
*/