二重揹包問題
最近刷面經,看見一個二重揹包問題,看着百度寥寥無幾,而且生澀的公式,對於我這樣看數學公式就頭疼的傢伙,真是不幸。
還好想起了前段時間在看的《算法圖解》,喜中往外,怎麼會有這樣一本神作,不偏不倚,是入門算法的福音。
首先來看一個簡單的案例
問題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