1.0-1背包问题
有N件物品和一个能装质量为W的背包。第i件物品的重量是w[i],价值是v[i]。求解将哪些物品装入背包可使这些物品的总重不超过背包总重,且价值总和最大。这个问题的特点是:每种物品只有一件,可以选择放或者不放。
定义f[i][j]:在前i个物品中用容量为j的包选择所能得到的最大价值。则转移方程:f[i][j]=max{f[i-1][j],f[i-1][j-w[i]]+v[i]},v[i]和w[i]对应为第i件物品的价值和重量。
代码,对于int[] w={2,2,6,5,4};int[] v={6,3,5,4,6};注意,求得的最优解为,小于等于背包容量所能获得的最高价值
import java.util.Iterator; import java.util.Vector; public class bag {//背包 int maxWeight; Vector<thing> things;//存储所选择的物品 int maxValue; bag(){this(0);} bag(int m){ maxWeight=m; things=new Vector<thing>(); maxValue=0; } public void choose(thing[] t){//选择物品 if(t.length==0||maxWeight==0) System.out.println("Invalid things array"); int[][] func=new int[t.length+1][maxWeight+1]; int i,j; for(i=0;i<=maxWeight;i++) func[0][i]=0; //最大价值计算 for(i=1;i<=t.length;i++) for(j=maxWeight;j>0;j--){ if(t[i-1].weight<=j&&(func[i-1][j-t[i-1].weight]+t[i-1].value)>func[i-1][j]) func[i][j]=func[i-1][j-t[i-1].weight]+t[i-1].value; else func[i][j]=func[i-1][j]; } //找出所选择的物品 j=0; for(i=1;i<=maxWeight;i++) if(func[t.length][i]>maxValue) j=i; i=t.length; while(i>0&j>0){ if(func[i][j]!=func[i-1][j]){ things.addElement(t[i-1]); j-=t[i-1].weight; } i--; } } public void show(){//输出物品 Iterator<thing> iter=things.iterator(); while(iter.hasNext()) System.out.println(iter.next()); } } class thing{//物品 int weight; int value; thing(){this(0,0);} thing(int w,int v){weight=w;value=v;} public String toString(){ return "物品"+Integer.toString(weight)+":"+Integer.toString(value); } }注意:1.由于状态数作为矩阵标号,因此要求总重量或者容积必须为整数。
2.不需要找出所选择的物品时。可以将空间复杂度由O(NW)将至O(W),即满足条件更新即可。
3.算法的时间复杂度为O(NW),对应物品总数和最大重量
4.由于涉及状态转移,因此从后往前更新最好
2.完全背包
完全背包(CompletePack): 有N种物品和一个容量为V的背包,每种物品都有无限件可用。第i种物品的费用是c[i],价值是w[i]。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。
注意到完全背包使用了正序更新。因为每一件物品是无限的。所以在考虑“加选一件第i种物品”这种策略时,却正需要一个可能已选入第i种物品的子结果。
伪代码如下。其中空间爱你复杂度为O(v)时间复杂度为
for (int i = 1;i <= N;i++) { for (int v = 1;v <= V;v++) { f[i][v] = 0; int nCount = v / weight[i]; for (int k = 0;k <= nCount;k++) { f[i][v] = max(f[i][v],f[i - 1][v - k * weight[i]] + k * Value[i]); } } }
完全背包问题有一个很简单有效的优化,是这样的:若两件物品i、j满足c[i]<=c[j]且w[i]>=w[j],则将物品j去掉,不用考虑。即,如果一个物品A是占的地少且价值高,而物品B是占地多,但是价值不怎么高,那么肯定是优先考虑A物品的。
3.0-1背包问题的变种
(1)设报考第i个学校需要花费v[i]美元,能够被录用的概率为w[i]。已知有一定的钱M,则应该如何花钱使得没有被录取的概率最小。
设f[i][j]:在前i个学校中花费j元没有被录取的概率。状态初始化为1。
则转移方程f[i][j]=min{f[i-1][j],f[i-1][j-v[i]]*(1-w[i])}
(2)设银行i有现今v[i],小偷全部偷走时,被抓住的概率为p[i]。已知小偷最多想偷一定的钱,但希望以最大概率逃走。则应该去偷哪些银行。
f[i][j]表示在前i个银行中偷得的money为j时能够逃脱的最大概率。状态初始化为0。
状态转移方程:f[i][j]=max{f[i-1][j],f[i-1][j-v[i]]*(1-p[i])}
(3)假设完全装满问题下的最优解,以0-1背包为例,要求最后的解的重量和为给定的重量。注意此时,除了0位置之外,初始化均为最小值。原因1)对于承重为j的背包,f[0][j]无任何意义,即无法装满、
2)对于第一次f[i][j]不为负值的情况,要求当前所添加的物体直接把当前背包填满。此时用到了最开头的f[i][0]。而后不为负值的f[i][j],j=weight[i]+weight[pre].
#include <iostream> #include <vector> using namespace std; const int MIN=0x80000000; const int N=3; //物品数量 const int V=5; //背包容量 int f[V+1]; int Package(int *W,int *C,int N,int V); void main(int argc,char *argv[]) { int W[4]={0,7,5,8}; //物品权重 int C[4]={0,2,3,4}; //物品大小 int result=Package(W,C,N,V); if(result>0) { cout<<endl; cout<<"the opt value:"<<result<<endl; } else cout<<"can not find the opt value"<<endl; return; } int Package(int *W,int *C,int N,int V) { int i,j; memset(f,0,sizeof(f)); //初始化为0 for(i=1;i<=V;i++) //此步骤是解决是否恰好满足背包容量, f[i]=MIN; //若“恰好”满足背包容量,即正好装满背包,则加上此步骤,若不需要“恰好”,则初始化为0 for(i=1;i<=N;i++) for(j=V;j>=C[i];j--) //注意此处与解法一是顺序不同的,弄清原因 { f[j]=(f[j]>f[j-C[i]]+W[i])?f[j]:(f[j-C[i]]+W[i]); cout<<"f["<<i<<"]["<<j<<"]="<<f[j]<<endl; } return f[V]; }