0-1背包问题的动态规划求法(开心的小明)

注意:动态规划问题的子问题的解并不包含全局最优解!而是全局最优解包含子问题的解!也即子问题的局部最优解是什么并不能最终决定全局解在该局部的取值。因此动态固化存储了所有的情况,不管这些情况是否会被用到,自底向上的过程中,不知道上层的解应该是怎么样。求解的过程中也没有存储解的结构,而是只计算最优质,最后回溯来获得解的结构。

问题描述:

给定n种物品和一背包。物品i的重量是wi,其价值为vi,背包的容量为C。问:应如何选择装入背包的物品,使得装入背包中物品的总价值最大?

     形式化描述:给定c >0, wi >0, vi >0 , 1≤i≤n.要求找一n元向量(x1,x2,…,xn,), xi∈{0,1}, ∋ ∑ wi xi≤c,且∑ vi xi达最大.即一个特殊的整数规划问题。

1. 最优子结构的性质:


动态规划的灵魂是用空间换时间,和分治一样,可以写为递归方程,不同的是动态规划的递归方程是有许多重复的子问题的。

动态规划问题的关键是分析出问题的最优子结构的性质,也即定义问题的规模。

在该问题中,如果定义问题的规模为{可选物品为【1,。。。。j】},则大规模问题{可选物品为【1,。。。。j+1】}的解与小问题之间没有最优子结构的性质,因为【1...j】如果已经装满了,则第j+1个物品只能替换掉1到j中的某个物品,但是1到j的选择为bool量,可能有些物品都没有被选中。即该问题没有最优子结构的性质,这是不是表示该问题不能用动态规划的算法来做呢?


换一个思路:既然这里背包的容量是限制,那么为什么不把背包的容量也算进来呢。

也即如果问题没有最优子结构的性质,可能是问题的规模为1维,想办法把问题的规模扩展到多维,可能就有了最优子结构的性质,这再次体现了用空间换时间的思想。


设bool数组(y1,y2,....yn)是所给0-1背包问题的一个最优解,则(y2,y3,...yn)是对应的子问题的最优解


注意这里的问题规模不仅仅包含了选取的物品的范围,而且还包含了背包的容量

     证明:使用反证法。若不然,设(z2,z3,…,zn)是上述子问题的一个最优解,而(y2,y3,…,yn)不是它的最优解。显然有
                                    ∑vizi > ∑viyi   (i=2,…,n)
     且                           w1y1+ ∑wizi<= c
     因此                       v1y1+ ∑vizi (i=2,…,n) > ∑ viyi, (i=1,…,n)
     说明(y1,z2, z3,…,zn)是(3.4.1)0-1背包问题的一个更优解,导出(y1,y2,…,yn)不是背包问题的最优解,矛盾。


2. 递归方程

定义m(i,j)表示背包容量为j,可选物品为【i.....n】情况下的最优值(只是可选而已,并不表示这些物品都选上了),则可以证明这个解是有最优子结构的性质的。

最后的最优解为m(1,c)。即可选物品为[1,......n]的情况下,背包总容量为c的情况下的最优值。也即最好要赶出 m(1,c)这个值。

分情况讨论下m(i,j)的计算方法

j表示在当前context下背包的容量:

如果j(当前capacity)大于物品i的重量, 则 m(i,j)可能要靠牺牲前面物品来获取最优值

即m(i,j)=max{m(i+1,j)也就是即使有空,也把第i个物品空出来不放,或m(i+1,j-wi)+vi,即前面腾出wi的空间用来放置物品i。 比较这两种情况的最优值,选择大的那个。

如果选择放入第i个物品,则这个决定是确定的!更高规模的问题不能更改这个决定,因此必须强制把背包的容量降低到j-wi来看小规模问题m(i+1,j-wi)的最优值加上v【i】是否能超过不放物品i,而背包容量也不减少的最优值,这一点是问题规模增加背包容量这一个维度的核心所在!!

如果j当前的capacity比物品i的重量小,则当前context下,无论如何也放不进物品i。此时 m(i,j)=m(i+1,j)

3. 初始值的选取

首先计算边界值, jMax=min(w[n]-1,c)  即选取最后一个物品小一格的背包容量

for(int j=0;j<=jMax;j++) m[n][j]=0;  即只有最后一个物品可选时, 背包容量不及最后一个物品的重量,放不进去,最优值为0.

能进入下面这个for循环就说明jMax=w[n]-1,否则jMax=c

for(int j=w[n];j<=c;j++) m[n][j]=v[n];  当背包容量大于w[n]时,即最后一个物品装入,此时最优值为v[n]

动态规划的底层因为没有更小的子问题,因此答案总是确定的!!即m[n][j]=v[n],因为可选物品是n,因此只要背包容量足够,那就应该放入!

4 . 自底向上赶

for(int i=n-1;i>1;i--)   //可选物品的范围为  [i,.....n]

{

  jMax=min(w[i]-1,c); //判断背包的容量和第i的物品的大小关系,找出最小的限制值

 for(int j=0;j<=jmax; j++) m[i][j]=m[i+1][j];   // 当背包的容量比第i个物品还小时,肯定装不进去,最优值不变。

能进入下面这个for循环就说明jMax=w[i]-1

for(int j=w[i];j<=c;j++) m[n,j]=max{m[i+1][j],  m[i+1][j-w[i]]+v[i]};     // m[i+1][j-w[i]]这个值在上层已经算过了

   

}

//上面的循环没有算到i=1,因此这里单独算一遍。

m【1】[c]=m[2][c];// 如果第1件物品没有背包容量c大。否则,在下面的一行重新计算m【1】【c】

if(c>=w[1]) m[1][c]=max{m[1][c],m[2][c-w[1]+v[1]]};  //这里赶出了  最终的值m[1][c]

5回溯求解最优值的结构

动态规划赶的过程只关注最优值,没有关注最优解,回溯可以求出最优解

traceBack(int **m,int* w,int c,int n,int* x)

  for(int i=1;i<n;i++)

 {

      if(m[i][c]==m[i+1][c])  x[i=0;// 第i件物品不装入

                         else

                        {

                             x[i]=1;c-=w[i];   // 背包的容量相应减小

                         } 

          

  }

  x[n]=(m[n][c])?1:0;   m[n][c]表示背包容量剩下为c,第n个物品是否装入


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值