背包问题理解

1、01背包问题

二维数组的状态转移方程如下:

f[i][v] = max(f[i-1][v],f[i-1][v-c[i]]+w[i])

f[i][v]指的是取前i个物品,在体积<=v时的最大价值,从动态规划的思路上理解上面的转移方程,在计算第i和物品时,存在两种可能性,1是不取,2是取,因此f[i-1][v]就对应不取的情况,不取还是前i-1的价值,f[i-1][v-c[i]]+w[i]则对应了取第i件物品,是在i-1的体积基础上减掉i的体积c[i],但需要加上i的价值w[i],因为最终目的是求价值。

伪代码如下:

for i to n
  for 0 to v
    f[i][v] = max(f[i-1][v],f[i-1][v-c[i]]+w[i])

从上面的状态转移方程可以看出,第i件物品只跟[i-1]有关,因此可以尝试进行空间优化,将二维数组优化成一维数组进行状态存储,先直接给出一维下的状态转移方程如下:

f[v] = max(f[v],f[v-c[i]]+w[i])

其实就是等价转换,将i这一维去掉即可,问题是该如何理解呢?

使用一维数组来保存状态的情况下,f[i-1][v]其实就是上一次循环下的v下标的值,那么后面那部分也一样的理解,但这里有个问题是上面的伪代码中第二层循环是从0到v的,在这种情况下,当我们循环第i层时,也从0开始往一维数组中填值,那么就会覆盖掉上一次循环的值,而我们却还要使用上一次循环的值,这样一来就出问题了。比如上一次循环完下标3位置的值是20,而我们进行本次循环的时候会先把这个20改成其他值,比如30,而当我们需要f[v-c[i]]的值时,[v-c[i]]如果是下标3,那么我们需要的是20这个值,而不是现在的30,所以会出错,这里尽量写详细一点,实在担心后面再来看时又忘了怎么回事就尴尬了,这里很关键,刚开始我理解了很久最终才想通。

这种情况下,我们只需要在写代码时将第二层循环改成从v到0即可,如下:

for i to n
  for n to 0
    f[v] = max(f[v],f[v-c[i]]+w[i])

2、完全背包问题

完全背包问题跟01背包的区别在于,01背包每一件物品只有两种情况,要么不选,要么选,而完全背包则是每一种物品可以选无数次,只要总体积不超过背包的体积即可。

同样我们先来看看二维数组的状态转移方程:

f[i][v] = max(f[i-1][v-k*c[i]]+k*w[i]) | 0<= k*c[i] <= v

上述方程中的k则表示当前是否取第i种物品以及取的数量,当k=0时表示不取第i种物品,自然f[i][v] = f[i-1][v],当k=1时,f[i][v]=f[i-1][v-c[i]]+w[i],以此类推非常好理解,跟01背包一个道理,其实上面01背包问题的方程可以写成如下格式:

f[i][v] = max(f[i-1][v-k*c[i]]+k*w[i]) | 0<= k*c[i] <= v and (k == 0 or k==1)

完全背包二维方程同样只跟上一次的循环有关,同理也可以优化成一维数组,如下:

f[v] = max(f[v],f[v-c[i]]+w[i])

是的,你没有看错,跟01背包的一维方程一毛一样,可能会有人问k去哪里了呢?其实这里我们一样使用滚动数组来优化空间,在01背包的时候第二层循环是从大到小遍历的,原因我也将的很清楚了,但是这里就不一样了,这里因为每种物品可以取无数次,因此上面的方程max中的f[v]就不是上一次循环的了,因为数组滚动的,因此我取0,1,2,3...件第i种物品,其实累计的值都一直保存到一维数组中的,比如我取3件,那么算第3件的时候第二件的累计值在数组中,我们直接取就好了。所以一定要记得这里的第二层循环是从小到大的,伪代码如下:

for i to n
  for 0 to n
    f[v] = max(f[v],f[v-c[i]]+w[i])

 

你可能感兴趣的:(算法,动态规划,算法)