问题描述:
有n 个物品,它们有各自的重量和价值,现有给定容量c的背包,如何让背包里装入的物品具有最大的价值?
思路分析:
一、动态规划:
1、把背包问题抽象化(X1,X2,…,Xn,其中 Xi 取0或1,表示第 i 个物品选或不选),Vi表示第 i 个物品的价值,Wi表示第 i 个物品的体积(重量);
2、建立模型,即求max(V1X1+V2X2+…+VnXn);
3、约束条件,W1X1+W2X2+…+WnXn<c;
4、定义m(i,j):当前背包容量 j,前 i 个物品最佳组合对应的价值;
5、寻找递推关系式,面对当前商品有两种可能性:
第一,背包的容量比该商品体积小,装不下,此时的价值与前i-1个的价值是一样的,即V(i,j)=V(i-1,j);
第二,还有足够的容量可以装该商品,但装了也不一定达到当前最优价值,所以在装与不装之间选择最优的一个,即V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i) },其中V(i-1,j)表示不装,V(i-1,j-w(i))+v(i) 表示装了第i个商品,背包容量减少w(i)但价值增加了v(i);
由此可以得出递推关系式:
1) j<w(i) V(i,j)=V(i-1,j)
2) j>=w(i) V(i,j)=max{V(i-1,j),V(i-1,j-w(i))+v(i)}
二、回溯法:
回溯法需要构造解的子集树。对于每一个物品i,对于该物品只有选与不选2个决策,总共有n个物品,可以顺序依次考虑每个物品,这样就形成了一棵解空间树,基本思想就是遍历这棵树,以枚举所有情况,最后进行判断,如果重量不超过背包容量,且价值最大的话,该方案就是最后的答案。在搜索状态空间树时,只要左子节点是可一个可行结点,搜索就进入其左子树。对于右子树,先计算上界函数,以判断是否将其减去(剪枝)。上界函数bound():当前价值cw+剩余容量可容纳的最大价值<=当前最优价值bestp。
为了更好地计算和运用上界函数剪枝,选择先将物品按照其单位重量价值从大到小排序,此后就按照顺序考虑各个物品。
三、分支限界法:
采用优先队列求解该问题,活结点队列中的结点结点元素N的 优先级由结点的上界函数Bound计算出的uprofit确定。算法中,E 是当前扩展结点,cw是该节点的重量,cp是其价值,up是价值上界。通过循环不断扩展结点,直到子集树的叶节点成为扩展结点,此时优先队列中所有活结点的价值上界都不超过该叶节点的价值。因此该结点相应的解为问题的最优解。
先检查扩展结点的左孩子结点的可行性,可行就加入活结点队列和子集树中。当右孩子结点满足上界约束将其加入子集树和活结点队列。
代码:
1、动态规划
void knapsack(int *v, int *w, int c, int n, int **m){
int jMax = min(w[n]-1,c);
for(int j = 0; j<=jMax; j++){
m[n][j] = 0;
}
for(int j = w[n]; j<=c; j++){
m[n][j] = v[n];
}
for(int i = n-1; i>1; --i){
jMax = min(w[i]-1,c);
for(int j = 0; j<=jMax; j++){
m[i][j] = m[i+1][j];
}
for(int j = w[n]; j<=c; j++){
m[i][j] = max(m[i+1][j],m[i+1][j-w[i]]+v[i]);
}
}
m[1][c] = m[2][c];
if(c>=w[1]){
m[1][c] = max(m[2][c],m[2][c-w[1]]+v[1]);
}
}
void TraceBack(int *x, int *w, int c, int n, int **m){
for(int i = 1; i<n; i++){
if(m[i][c] == m[i+1][c]){
x[i] = 0;
}else{
x[i] = 1;
c-=w[i];
}
}
x[n] = (m[n][c]) ? 1:0;
}
2、回溯
int cw = 0; //当前背包中物品的重量
int cp = 0; //当前背包中物品的价值
int bestp = 0; //当前的最优解
int *w,
*p,
*bestx,
n,
c;
//限界函数
int Bound(int i){
int left = c-cw;
int b = cp;
while(i <= n && w[i] <= left){
left -= w[i];
b += p[i];
i++;
}
if(i<=n) b += p[i]*left/w[i];
return b;
}
void BackTrack(int i){
if(i>n){
bestp = cp;
return;
}
if(cw+w[i] <= c){
cw += w[i];
cp += p[i];
bestx[i] = 1;
BackTrack(i+1);
cw -= w[i];
cp -= p[i];
}
if(Bound(i+1) > bestp) {
bestx[i] = 0;
BackTrack(i+1);
}
}
3、分支限界
struct bbnode{
bbnode *parent;
int LChild;
};
//加入队列的节点
struct Node{
operator int () const {return uprofit;}
float uprofit;
int profit;
int weight;
int level;
bbnode *ptr;
};
//优先队列
priority_queue<Node> Q;
bbnode *E;
int c;
int n;
int *w;
int *p;
int cw;
int cp;
int *bestx;
//限界函数
float Bound(int i){
int cleft = c-cw;
float b = cp;
while(i<=n && w[i]<=cleft){
cleft -= w[i];
b += p[i];
i++;
}
if(i<=n) b += 1.0* p[i]/w[i]*cleft;
return b;
}
void AddLiveNode(int up, int cp, int cw, int ch, int lev){
bbnode *b = new bbnode;
b->parent = E;
b->LChild = ch;
Node N;
N.uprofit = up;
N.profit = cp;
N.weight = cw;
N.level = lev;
N.ptr = b;
Q.push(N);
}
int MaxKnapsack(){
bestx = new int [n+1];
int i = 1;
E = NULL;
cw = cp = 0;
int bestp = 0;
float up = Bound(1);
while(i != n+1){
int wt = cw + w[i];
if(wt<=c){
if(cp+p[i]>bestp) bestp = cp+p[i];
AddLiveNode(up, cp+p[i], cw+w[i], 1, i+1);
}
up = Bound(i+1);
if(up>=bestp) AddLiveNode(up, cp, cw, 0, i+1);
Node N = Q.top(); Q.pop();
E = N.ptr;
cw = N.weight;
cp = N.profit;
up = N.uprofit;
i = N.level;
}
for(int j=n; j>0; --j){
bestx[j] = E->LChild;
E = E->parent;
}
return cp;
}
运行结果: