背包问题:
已知有n种物品,一个可容纳M重量的背包,物品i(1<=i<=n)的重量wi,对应的价值为pi。怎样的装包,才能使包中物品的总价值最大?
已知条件:n,M,(p1,p2,…,pn),(w1,w2,…,wn)。
背包问题分为2种:如果物品可以拆分,就是普通背包问题。如果每个物品不能拆分,就是01背包问题。
普通背包问题-贪心算法
由于普通背包问题,物品可以拆分,假设物品i装进背包的部分的x[i],其中0<=xi<=1。思想为:1.按效益值pi/wi比值的降序排列;2.按排序次序将物品放入背包,如果剩余容量能放下物品w[i],则xi=1,能放下部分,则xi=leftM/wi,否则xi=0。
最终∑n1wixi=M,∑n1pixi即为最大收益。
例如:n=3,M=20,(p1,p2,p3)=(25,24,15),(w1,w2,w3)=(18,15,10)。
解:按pi/wi排序,物品排序为(物品2,物品3,物品1),装包过程x2=1,x3=1/2,x2=0。结果(x1,x2,x3)=(0,1,1/2),最大效益值为31.5。
01背包问题-动态规划
对于0/1背包问题,可以通过作出变量(x1,x2,...,xi)的一个决策序列fi(x)来得到它的解。而对于变量x的决策就是决定它取0还是1。
如果设fj(x)是KNAP(1,j,x)最优值的解,那么,fn(M)就可以表示为
对于任意的fi(x),这里i>0,则有
为了能尤前向后递推最后求解fn(M),需从f0(x)开始。对于x>=0,f0(x)=0,当x<0,f0(x)=-∞。
其实,如果画图,可以看出每个fi都是由一些序偶(pj,wj)组成的集合所说明,其中wj是使fi在某处产生一次阶跃的x值,pj=fi(wj)。第一对序偶是(p0,w0)。
算法思想:
- 设S0={ (0,0) }。
- Si−1是fi−1的所有序偶的集合,Si1是fi−1(x−wi)+pi的所有序偶的集合。求Si1,就是把序偶(pi,wi)加到Si−1中每一对序偶,
Si1=(p,w)|(p−pi,w−wi)∈Si−1 - 上面第二个公式中取max值,其实就是将Si−1和Si−1归并得到Si。归并过程中,如果Si−1和Si−1之一有序偶(pj,wj),另一个序偶(pk,wk),并且在wj>=wk的同时有pj<=pk,那么序偶(pj,wj)被舍弃。
算法时间复杂度为O(n),空间复杂度为O(n)。
例如:n=3,M=6,(p1,p2,p3)=(1,2,5),(w1,w2,w3)=(2,3,4)。
解:
S0={(0,0)}
- S11={(1,2)}
S1={(0,0),(1,2)}
- S21={(2,3),(3,5)}
S2={(0,0),(1,2),(2,3),(3,5)}
- S31={(5,4),(6,6),(7,7),(8,9)}
S3={(0,0),(1,2),(2,3),(3,5),(6,6),(7,7),(8,9)}
根据支配规则,在S3中删去了序偶(3,5)。
可知,f(6)=6。
01背包问题代码
//保存成KNAP.h头文件
//然后在项目主函数,main中KNAP::Test::main();调用即可
#include <iostream>
#include <vector>
using namespace std;
namespace KNAP{
struct PWPoint{
int p;
int w;
PWPoint(int _p,int _w):p(_p),w(_w){ }
};
class Solution{
public:
int knap(int n,int M,vector<int> p,vector<int> w){
vector<PWPoint > fn,fn1;
fn.push_back(PWPoint(0,0));
int j,k;
for(int i=0;i<n;i++){
//get s1(i)
fn1.clear();
for(j=0;j<fn.size();j++){
fn1.push_back(PWPoint(fn[j].p+p[i],fn[j].w+w[i]));
}
//raw merger s(i-1) and s1(i) to s(i)
j=0,k=0;
while(j<fn.size() && k<fn1.size()){
if(fn[j].w>fn1[k].w){
fn.insert(fn.begin()+j,fn1[k]);
j++;
k++;
}else{
j++;
}
}
while(k<fn1.size()){
fn.push_back(fn1[k++]);
}
//delete except in s(i)
j=0;
while(j<fn.size()){
if(j<fn.size()-1){
if(fn[j].w == fn[j+1].w){
if(fn[j].p>=fn[j+1].p){
fn.erase(fn.begin()+j+1);
}else{
fn.erase(fn.begin()+j);
}
}else{
if(fn[j].p>=fn[j+1].p){
fn.erase(fn.begin()+j+1);
}else{
j++;
}
}
}else{
j++;
}
}
//delete fn[i] if fn[i].w>M
}
//get map_p for M
int cur_p=fn[0].p;
for(int i=1;i<fn.size();i++){
if(M<fn[i].w){
break;
}else{
cur_p=fn[i].p;
}
}
return cur_p;
}
};
class Test{
public:
static void main(){
int n=3,M=6;
vector<int> p{1,2,5},w{2,3,4};
Solution s;
int ret = s.knap(n, M, p, w);
cout<< ret <<endl;
}
};
}