双重背包问题

二重背包问题

最近刷面经,看见一个二重背包问题,看着百度寥寥无几,而且生涩的公式,对于我这样看数学公式就头疼的家伙,真是不幸。

还好想起了前段时间在看的《算法图解》,喜中往外,怎么会有这样一本神作,不偏不倚,是入门算法的福音。

https://book.douban.com/subject/26979890/

首先来看一个简单的案例

问题1描述

假设你是个小偷,背着一个可装4磅东西的背包。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ob8zICbw-1572164550711)(https://github.com/klzdy123/klzdy123.github.io/blob/master/images/1572155114751.png)]
你可盗窃的商品有如下3件。

为了让盗窃的商品价值最高,你该选择哪些商品?
在这里插入图片描述

思路

如果确实还没想到最好的解决办法,反正商品也不多,可以排列组合,找到价值最大的方法,但是毕竟只能应付少量商品的组合,否则就会很麻烦,到底有没有一个通用的方法可以套用呢?有的,使用动态规划!

  1. 首先将背包容量切分,并试着将第一个商品以最大价值的方式填充
    在这里插入图片描述
    以第一行为例,因为当前能填充的商品只有吉他,所以在满足背包大小情况下,最大价值为1500

  2. 在填充第二个商品时,情况就相对复杂
    在这里插入图片描述
    ​ 音响有4磅,在1,2,3磅背包中放不下,所有最大价值商品还是吉他
    在这里插入图片描述
    ​ 当背包容量为4磅时,终于可以放得下音响,因为音响比吉他之前,所以目前最大价值为3000

  3. 第三件商品的填充
    在这里插入图片描述
    背包为3磅时,最大价值为2000
    在这里插入图片描述
    最后一个4磅背包到底怎么最大化价值呢?这就用到了二重背包最重要的思想,到底是音响价值大,还是笔记本和剩余1磅空间的物品。

    我们可以取上一行中1磅背包的物品与之,2000+1500>3000
    在这里插入图片描述

  4. 综上,网格中第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吉他行最大价值应该是

商品\背包容量1234
吉他1500300030003000

这就引发了可能存着两个相同商品价值更多问题

不着急,我们接着分析

  1. 大体思想还是问题1的二重背包,只不过多了一个商品数量
  2. 在计算该行最大价值时候,多一次循环,在处理商品数量为$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
  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值