二重背包问题
最近刷面经,看见一个二重背包问题,看着百度寥寥无几,而且生涩的公式,对于我这样看数学公式就头疼的家伙,真是不幸。
还好想起了前段时间在看的《算法图解》,喜中往外,怎么会有这样一本神作,不偏不倚,是入门算法的福音。
首先来看一个简单的案例
问题1描述
假设你是个小偷,背着一个可装4磅东西的背包。
你可盗窃的商品有如下3件。
为了让盗窃的商品价值最高,你该选择哪些商品?
思路
如果确实还没想到最好的解决办法,反正商品也不多,可以排列组合,找到价值最大的方法,但是毕竟只能应付少量商品的组合,否则就会很麻烦,到底有没有一个通用的方法可以套用呢?有的,使用动态规划!
-
首先将背包容量切分,并试着将第一个商品以最大价值的方式填充
以第一行为例,因为当前能填充的商品只有吉他,所以在满足背包大小情况下,最大价值为1500
-
在填充第二个商品时,情况就相对复杂
音响有4
磅,在1,2,3
磅背包中放不下,所有最大价值商品还是吉他
当背包容量为4
磅时,终于可以放得下音响,因为音响比吉他之前,所以目前最大价值为3000
-
第三件商品的填充
背包为3
磅时,最大价值为2000
最后一个4
磅背包到底怎么最大化价值呢?这就用到了二重背包最重要的思想,到底是音响价值大,还是笔记本和剩余1
磅空间的物品。我们可以取上一行中
1
磅背包的物品与之,2000+1500>3000
-
综上,网格中第
i
行第j
列可以装的最大价值商品为上一个单元格(第i-1
行第j
列)和当前商品价值和剩余空间最大价值(第i-1
行第j-当前商品体积
列)
代码(PHP)
// 简单背包问题,每个商品数目为1
function doubleKnapsack1()
{
echo "请输入物品数目:";
$N = fgets(STDIN);
echo "请输入背包体积:";
$T = fgets(STDIN);
$W = array(); //物品体积
$V = array(); //物品价值
$value = array(); //用于放置最大价值
for ($i = 0; $i < $N; $i++) {
$temp = fgets(STDIN);
list($W[], $V[]) = explode(' ', $temp);
}
unset($temp, $i);
// 依次遍历商品
for ($i = 0; $i < $N; $i++) {
// 逐渐增加背包体积
for ($j = 0; $j <= $T; $j++) {
if (0 == $i) {
if ($W[$i] <= $j) {
$value[$i][$j] = $V[$i]; // 将此时能够容纳商品最大价值存入
} else {
$value[$i][$j] = 0; // 当不满足时候,给格子赋值0
}
} else {
// 判断是否有剩余背包空间
if (0 <= $j - $W[$i]) {
$value[$i][$j] = max($value[$i - 1][$j], $V[$i] + $value[$i - 1][$j - $W[$i]]);
continue;
}
$value[$i][$j] = $value[$i - 1][$j];
}
}
}
print_r($value);
echo "背包最大能够装价值为 " . $value[(int)$N - 1][(int)$T] . " 的商品\n";
}
读者肯定在想,我去,老子直接看书不就行了,讲得啰里啰嗦的。别,这只是前半部分!
问题2描述
N个物品,每个物品都有恰好两个。第I种物品的体积和价值分别是WI 和VI。
背包的体积为T,问在不超过背包体积的情况下,最多能放进多少价值的物品。
思路
因为出现了两个物品的概念,例如在问题1
中吉他行
最大价值应该是
商品\背包容量 | 1 | 2 | 3 | 4 |
---|---|---|---|---|
吉他 | 1500 | 3000 | 3000 | 3000 |
这就引发了可能存着两个相同商品价值更多问题
不着急,我们接着分析
- 大体思想还是
问题1
的二重背包,只不过多了一个商品数量 - 在计算该行最大价值时候,多一次循环,在处理商品数量为
$k=1
时,按着原思路,第二次循环时,带入权重$k=2
,分别在首行计算和剩余空间背包价值计算中,其中肯定存着第二次循环覆盖第一次结果的情况,此时应该做取最大值运算
代码
// 现在每个商品数目为2
function doubleKnapsack2()
{
echo "请输入物品数目:";
$N = fgets(STDIN);
echo "请输入背包体积:";
$T = fgets(STDIN);
$num = 2; //每个商品数目
$W = array(); //物品体积
$V = array(); //物品价值
$value = array(); //用于放置最大价值
for ($i = 0; $i < $N; $i++) {
$temp = fgets(STDIN);
list($W[], $V[]) = explode(' ', $temp);
}
unset($temp, $i);
// 依次遍历商品
for ($i = 0; $i < $N; $i++) {
// 逐渐增加背包体积
for ($j = 0; $j <= $T; $j++) {
// 增加商品数目循环,查找最大价值
for ($k = 1; $k <= $num; $k++) {
if (0 == $i) {
if ($W[$i] * $k <= $j) {
$value[$i][$j] = $V[$i] * $k; // 将此时能够容纳商品最大价值存入
} else {
$value[$i][$j] = isset($value[$i][$j]) ? max($value[$i][$j], 0) : 0; // 当不满足时候,给格子赋值0
}
} else {
// 判断是否有剩余背包空间
if (0 <= $j - $W[$i] * $k) {
$value[$i][$j] = max($value[$i - 1][$j], $V[$i] * $k + $value[$i - 1][$j - $W[$i] * $k]);
continue;
}
$value[$i][$j] = $value[$i - 1][$j];
}
}
}
}
print_r($value);
echo "背包最大能够装价值为 " . $value[(int)$N - 1][(int)$T] . " 的商品\n";
}
到现在为止,在商品数量再有变化的情况下,相信很多小伙伴都可以举一反三。
本人现在还是一个没有找到工作的小白,希望能够在剩下时间里每天积累一些东西。--20191027