二重揹包問題

二重揹包問題

最近刷面經,看見一個二重揹包問題,看着百度寥寥無幾,而且生澀的公式,對於我這樣看數學公式就頭疼的傢伙,真是不幸。

還好想起了前段時間在看的《算法圖解》,喜中往外,怎麼會有這樣一本神作,不偏不倚,是入門算法的福音。

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吉他行最大價值應該是

商品\揹包容量 1 2 3 4
吉他 1500 3000 3000 3000

這就引發了可能存着兩個相同商品價值更多問題

不着急,我們接着分析

  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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章