0/1背包问题

简介:   对于0/1背包问题,可以通过作出变量x1,x2,…,xi的一个决策序列来得到它的解。而对变量x的决策就是决定它是取0值还是取1值。假定决策这些x的次序为xn,xn-1,…,x1。在对xn作出决策之后,问题处于下列两种状态之一:   背包的剩余容量是M,则没有产生任何效益;  剩余容量是M-w,则效益值增长了P。

  对于0/1背包问题,可以通过作出变量x1,x2,…,xi的一个决策序列来得到它的解。而对变量x的决策就是决定它是取0值还是取1值。假定决策这些x的次序为xn,xn-1,…,x1。在对xn作出决策之后,问题处于下列两种状态之一:

  背包的剩余容量是M,则没有产生任何效益;
  剩余容量是M-w,则效益值增长了P。

显然,对xn-l,xn-2,…,x1的决策相对于决策x所产生的问题状态应该是最优的,否则xn,xn-1,…,x1就不可能是最优决策序列。如果设fj(x)是Knap(1,j,X)最优解的值,那么fn(M)就可表示为:
  fn(M) = max {fn-1(M), fn-1(M-wn)+pn}
  对于任意的fi(X),这里i>0,则有:
  fi(X) = max {fi-1(X), fi-1(X-wi)+pi}

  为了能由前向后递推而最后求解出fn(M),需从f0(X)开始。对所有的X≥0,有f0(X)=0,当X<0时,有f0(X)=-∞。根据(5.2),可求解出0≤X<w1和X≥w1情况下f1(X)的值。接着又可由(5.2)不断递推求出f2,f3,…,fn在X相应取值范围内的值。

例如:非形式化的背包算法,代码如下:

line void DKP(p,w,n,M) {
1 S0={00};
2 for(i=1;i<=n-1;++i) {
3 S1
i={(Pj,Wj)|(Pj-pi,Wj-wi) ∈Si-1 and Wj≤M};
4 Si=Merge_Purge(Si-1,S1
i) ;
5 };
6 (PX,WX)=Sn-1的最末序偶;
7 (PY,WY)=(Pj+pn,Wj+wn)其中,Wj是Sn-1中使得Wj+wn≤M的所有
序偶中取最大值的W;
//沿Sn-1, …, S1回溯确定xn, xn-l, …, x1的取值
8 if(PX>PY) xn = 0//PX是Sn的最末序偶
9 else xn = 1//PY是Sn的最末序偶
10 回溯确定xn, xn-l, …, x1;
11 }//DKP

DKP的实现:

  可以用两个一维数组P和W来存放所有的序偶(P1,W1)。P1的值放在P中,W1的值放在W中。序偶集S0,S1, …, Sn互相邻接地存放。各个集合可以用指针F(i)来指示,这里0≤i≤n。对于0≤i<n,F(i)指示S中第一个元素所在的位置。F(n)是Sn-1中最末元素的位置加1。

例如:0/1背包问题的算法,代码如下:

line void DKnap(p,w,n,M,m) {
float p[n],w[n],P[m],W[m],pp,ww,M;
int F[0:n],t,h,u,i,j,p,next;
1 F[0]=1;P[1]=W[1]=0 //S0设定
2 t=h=1//S0的首端与末端
3 F[1]=next=2//P和W中第一个空位
4 for(i=1;i<=n-l;++i) { //生成Si
5 k = t;
6 u = 在1≤r≤h中使得W[r]+wi≤M的最大的r;
7 for(j=1;j<=u;++j) { //生成S1
i及归并
8 (pp,ww)=(P[j]+pi,W[j]+wi); //S1
i中的下一个元素
9 while((k<=h) and (W[k]<ww)) {
//从Si-1中取元素来归并
10 P[next]=P[k];W[next]=W[k];
11 next=next+1;k=k+1
12 };
13 if((k<=h) and (W[k]=ww)) { pp=max(pp,P[k]);
14 k=k+l
15 };
16 if(pp>P[next-1] (P[next],W[next])=(pp,ww)
17 next=next+118 };
19 while((k<=h) and P[k]<=P[next-1]) { //清除
20 k=k+121 };
22 }
//将Si-1中剩余的元素并入Si
23 while(k<h) {
24 (P[next],W[next])=(P[k],W[k]);
25 next=next+l;k=k+l;
26 }
//对Si+1置初值
27 t=h+1;h=next-1;F[i+1]=next;
28 };
29 Parts;
30 }//DKnap

过程DKnap的分析:

  假设Si的序偶数是|Si|。在i>0的情况下,每个Si由Si-1和S1i归并而成,并且以|S1i|≤|Si-1|,因此有|Si|≤2|Si-1|。在最坏情况下没有序偶会被清除,所以

Σ|Si|      =      Σ2i       =      2n-1。

0≤i≤n-1      0≤i≤n-1    即,DKnap的空间复杂度为О(2n)。

由Si-1生成Si需要Θ(|Si-1|)的计算时间,因此,计算S0,S1, …, Sn-1总的时间是Θ(Σ|Si-1|)。由于|Si|≤2i,所以计算S0,S1, …, Sn-1的总时间是О(2n)。

  以上分析表明当n取大值时,DKnap的有效性似乎令人失望。但这类问题在很多情况下都能在“适当的”时间内解出。这是因为在很多情况下p和w都是整数。而且M比2n小得多。另外,支配规则在清除不应并入Si的序偶上是很有效的。

  使用探试方法(heuristic)可以加速DKnap的计算。设L是最优解的估计值,它使得fn(M)≥L,又设PLEFT(i)=Σpj|1<j≤n。如果序偶(P,W)∈Si,且使得
P+PLEFT(i)<L,那么(P,W)就可从Si中清除。这是因为(P,W)充其量只能对S1n产生(P+Σpj,W+Σwj)这样的序偶,而P+Σpj=P+PLEFT(i)<L,故序偶(P,W)不可

                                      i<j≤n i<j≤n        i<j≤n
 能导致最优解fn(M),故可从Si删去。找小于等于fn(M)的估计值L的一种简单方法是取Si的最末序偶(P,W)的P作为L。此时P≤fn(M)。更好的一种估计值是将某些剩余物品的p值与P值加在一起作为L。

小结

01背包问题是最基本的背包问题,它包含了背包问题中设计状态、方程的最基本思想,另外,别的类型的背包问题往往也可以转换成01背包问题求解。故仔细体会上面基本思路的得出方法,状态转移方程的意。

 

相关文章
|
6月前
|
存储 算法
动态规划(背包问题)
动态规划(背包问题)
|
6月前
|
C++
每日练习之——背包问题
每日练习之——背包问题
32 1
|
7月前
|
算法 测试技术 C#
|
7月前
|
算法
动态规划—(背包问题)
动态规划—(背包问题)
|
7月前
|
消息中间件 Kubernetes NoSQL
动态规划- 背包问题总结(一)
动态规划- 背包问题总结(一)
|
存储 算法
动态规划之背包问题
动态规划之背包问题
98 0
|
算法 C语言 C++
【动态规划】背包问题(01背包,完全背包)
有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。
126 0
|
机器学习/深度学习
动态规划-背包问题
动态规划-背包问题
【33. 0 1 背包问题】
背包问题 - 一共有N件物品,背包容量为V,物品有俩个属性,一个是体积一个是价值(类似在一个宝岛上,你有一个背包,它只能装满这个背包,小岛上有很多金子,各种体积的,而且每个金子纯度不一样,看最后怎么装才会使得背包中的金子价值最高) **四种背包问题** - 0 1 背包 每件物品最多只用一次, - 完全背包 每件物品有无线个 - 多重背包 每个物品不一样,每个物品最多有有S<sub>i</sub>个——朴素版和优化版 - 分组背包 有水果,汽车,人等,每一种里面最多只能选择一个
161 0
【33. 0 1 背包问题】