o-1背包问题迭代_0-1背包问题还有它的衍生问题的解决思路

134a42a12a513ee93aceb85d735dd1fe.png

0-1 背包问题

0-1背包问题是经典的动态规划问题,它有两种衍生的问题形式,我们称之为完全背包问题和多重背包问题

基本的0-1背包问题:

热动分析.jpg

给一个capacity为n的背包,能装weight总共不超过n的物品,给定m个物品,每一个物品具有价值value和重量weight,我们希望能装的物品value的总和越大越好,那么应该怎么做呢?
#include 

以上这个代码基本就是解决基本0-1背包问题的算法思路了,我们可以看到,问题解决的关键在于找到一条合理的状态转移方程,所以我们考虑用一个合适的“容器”,用来记录一些中间状态,比如dp_cache[i][j]用来表示:

考虑前i个物品(1,...,i)中,在容量为j的情况下能够装下的物品的最大价值。为了减少思维负担,我们信任这个最大价值,并且认为他是最优的,那么下一步的问题就是我们要不要在继续装下一个物品

那么问题就转化成dp_cache[i + 1][j]究竟是容量不够没有装下第 i+1 个物品,还是装下了却得不偿失所以不装,还是装下了确实地能够提高整体容纳物品的价值总和,所以我们会有下面的思考

dp_cache[i + 1][j] = k
  • 当不能装下这个物品(第i + 1个物品),也就是j比第i + 1个物品的的重量要小,那么自然而然不装下第i + 1个物品,k就是dp_cache[i][j]
  • 当能够装下这个物品,那么我们需要考虑要不要装下它,是否值得
    • 如果值得,那么k就是dp_cache[i][j - weight[i + 1]] + value[i + 1],也就是考虑前i个物品,在容量为j - weight[i+1]扣除第i+1个物品的剩余容量的最佳值加上装入的新物品的价值之和
    • 否则,保持不变,也就是k = dp_cache[i][j]比装入这个物品后的情况(dp_cache[i][j - weight[i + 1]] + value[i + 1])要大,那么我们不装入这个新的物品

为了方便下面我们使用dp数组代指dp_cache

这里注意一下

值得注意的是,我们在迭代的过程中,要求dp[i][j]需要考虑的“原材料”,也就是我们所说的迭代前项,是dp[i-1][0,1,2,...,j]与本行的数据都没有关系(指dp[i][0,1,2,3,...,j - 1]),仅仅只用到了上一行的数据,也就是说,将暂存数组从二维优化为一维是可能的

| xx1 | xx2 | xx3 |

| oo1 | oo2 | tag |

如上表所示,要求tag,那么xx是需要用到的,oo是用不到的,而要求第二行第二列的值,也只需要第一行第一列,第一行第二列的值作为推导的前项。

假如我们只用一个一维数组dp_cache来暂存价值信息,并且我们还只有第一行的数据

他现在长这样

| xx1 | xx2 | xx3 |

现在,我们要求下一行第一个数据oo1,没问题,直接使用这个一维数组的第一个数据递推

他现在长这样

| oo1 | xx2 | xx3 |

接下来,我们要求下一个数据oo2,但是问题出现了,求oo2需要xx1还有xx2,但是xx1已经被覆盖了,这样的话我们这个想法就宣告失败了。

那么,如果我们倒着来求dp[i+1][0...j]呢?

我们先求oo3(也就是之前的tag),oo3需要xx1, xx2, xx3来递推得到,也确实有,所以我们顺利得到oo3

这样,这个表现在长这样

| xx1 | xx2 | oo3 |

我们再求oo2,需要xx2和xx1,可以得到

| xx1 | oo2 | oo3 |

最后我们需要求oo1,可以由xx1推导得到

| oo1 | oo2 | oo3 |

这样,我们前i + 1个物品的dp数组已经求出来了

所以相对应的,我们可以把我们的算法代码优化一下,注意由于dp_cache结构改变init()函数也应当改变

// 新初始化函数

这样,第二个版本的算法执行函数就出来了

// 第二个版本

如果j小于weight[i]那么,显然是没有继续往下遍历的必要了,所以可以继续改为

// 最终版本

以上,就是0-1背包问题的解法了,假设物品有m个,背包容量为n,他的空间复杂度是O(n),时间复杂度是O(n*m);

时间上应该是可以再优化的(比如说最后一次迭代不去求dp[m][0....j-1],直接返回dp[m][n]也就是dp_cache[n]),但是O(n*m)的时间复杂度还是逃不掉的

完全背包问题

魔法分析.jpg

给一个capacity为n的背包,能装weight总共不超过n的物品,给定m种物品,每一种物品具有价值value和重量weight,我们希望能装的物品value的总和越大越好,那么应该怎么做呢?

注意这个问题和基本0-1背包问题的区别!!!

注意这个问题和基本0-1背包问题的区别!!!

注意这个问题和基本0-1背包问题的区别!!!

m种物品的数量是不限的!

m种物品的数量是不限的!

m种物品的数量是不限的!

有的小伙伴这个时候就有点懵逼了,那咋办啊,暴力遍历一下啊?

// 我们假设初始化函数没有问题

这种做法简单粗暴够直接,我喜欢,但是它没有利用到同一行的数据

我们记

k1 = dp[i][j] + value[i]
k2 = dp[i - 1][j + weight[i]]

dp[i][j]代表的是前 i 物品在 j 这个容量下的最优值,我们足够地信任他,那么当我们考虑dp[i][j + weight[i]]的值的时候,我们要作出一个判断,是继续装第 i 种物品更加合适还是只考虑前 i - 1 种但是容量增大更合适

也就是说

记 x = j + weight[i]
dp[i][x] = max(dp[i][x - weight[j]] + value[i], dp[i - 1][x])
dp[i][x] = max(k1, k2)

这样的话,我们就可以得到相对不那么暴力的算法了

// 第二版

发现了没有,dp[i][j]的递推的“原材料”是dp[i][j-w]还有dp[i-1][j]

我们是否可以效仿之前提到的做法,优化这个算法的空间复杂度,将二维暂存数组转化为一维数组以节省空间呢

当然可以!!

由于之前详细讲过转化成滚动数组的验证方法,这里只是给出较为简略的说明

这是二维数组版本

| xx1 | xx2 | xx3 |

| oo1 | oo2 | oo1 |

好的,现在假设我们已经得到了前 i - 1 种的结果 xx1 xx2 xx3

| xx1 | xx2 | xx3 |

现在我们要求 oo1, oo1需要xx1还有边界的0(希望这个无需说明)作为递推的前项

现在这个表长这样

| oo1 | xx2 | xx3 |

现在我们要求 oo2,oo2要求同一行的前置位已经求出和同一列的前置位已经求出,也就是ooX(X < 2)还有xx2的数据作为递推的前项,这里刚好满足,所以我们填下oo2

现在这个表长这样

| oo1 | oo2 | xx3 |

现在我们要求 oo3,oo3要求同一行的前置位已经求出和同一列的前置位已经求出,也就是ooX(X < 3)还有xx3的数据作为递推的前项,这里刚好满足,所以我们填下oo3

现在这个表长这样

| oo1 | oo2 | oo3 |

大功告成

我们反过来思考一下,为什么基本的0-1背包问题的递推要避开同一行的数据而不得不倒着遍历?

恰恰是因为要避免重复装入第i个物品

而在完全背包问题中,我们要反其道行之,就是要考虑可能的重复装入情况,这样最终的代码反而是非常简洁的

// 最终版,初始化函数什么的自己去改,这里只展示proc函数

代码仅供参考

多重背包问题

不想分析.jpg

你直接把它转换成基本0-1背包问题不就完事了么,这题摸了

结束

最后祝您,身体健康,再见

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值