揹包問題之零一揹包

2022-08-31 03:06:10 字數 4255 閱讀 5192

注:參考文獻《揹包九講》.

零一揹包問題

一:題目描述

有 n 件物品和乙個容量為 v 的揹包.放入第 i 件物品耗用的費用為ci(即所占用揹包的體積),得到的價值是 wi.求將哪些物品裝入揹包所得到的總價值最大.

二:基本思路

01揹包是最基礎的揹包問題,這道題的特點是每種物品僅有一件,可以選擇放或不放,且不要求揹包必須被放滿,只要求最後的總價值最大.

用子問題定義狀態:f[i][v] 表示對於前 i 件物品,當揹包容量為 v 時所能得到的價值最大值.設想,將 "前 i 件物品放入容量為 v 的揹包中" 這個子問題,若只考慮第 i 件物品的策略(要麼放要麼不放),那麼就可以轉化為乙個之和前 i - 1 件物品相關的問題.如果不放第 i 件物品, 那麼問題就轉化為 」前 i - 1 件物品放入容量為  v 的揹包中「,價值就是 f[i - 1][v]; 如果放第 i 件物品,那麼問題就轉化為 」前 i  - 1 件物品放入剩下的容量為 v - ci 的揹包中」, 此時獲得的價值為 f[i - 1][v - ci] + wi。 特殊的,當 v < ci 時,可以認為當前的容量是放不下第 i 件物品的,即此時相當於不放第 i 件物品的價值f[i - 1][v].分析到這裡則可得狀態轉移方程為:

f[i][v] = v i  ?f[i - 1][v] :max( f[i - 1][v], f[i - 1][v - ci] + wi ).

在這裡要特別的說明一下,這個方程非常重要,一定要知道這是怎麼推出來的,幾乎後面的所有的揹包問題都和這個方程有著密不可分的聯絡.

偽**如下:

f[0...n][0...v]  <---  0

for i  <--- 1  to  n

for v <--- ci to v

f[i][v] =v < ci? f[i - 1][v] : max( f[i - 1][v], f[i - 1][v - ci] + wi );

具體**:

1

void _01pack(int f[maxv], int n, int v, int c, intw)7

}8 }

三:優化空間複雜度

可以清楚的看到上面演算法的時間複雜度和空間複雜度均為 o(n * v), 這裡時間複雜度已經不能得到優化,但是空間複雜度確可以優化到 o(v).

先看上面**是如何實現的.最外面一層迴圈,每次計算出二維陣列 f[i][0...v] 的值,計算的時候 f[i][0...v] 是由它的上一層 f[i  - 1][0...v] 而得到的.那麼如果把這個陣列換成一維的 f[v] 那麼還能保留上一次的狀態嗎.答案是可以的.由於動態規劃演算法的無後效性,第 i + 1 件物品的選擇與否不會影響到第 i 件物品(即它的前一件物品)的選擇狀態.那麼可以在上面第二次迴圈中按照 v <--- v...0 遞減的順序來計算 f[v], 這樣計算 f[v] 時所需要的狀態 f[v] 和 f[v - ci] + wi 仍然還是上一次的狀態.而計算 f[v] 之後, v 的

順序是遞減的

, f[v] 不會影響到 f[v'] (v' < v), 因為f[v'] 只與 f[v'](上一次的值) 和 f[v - ci] 有關, 而 f[v] > f[v'] > f[v' - ci

]. 所以又可得狀態轉移方程.

f[v] = max( f[v], f[v - ci] + wi ).

偽**如下:

f[0...v]  <---  0

for i  <--- 1  to  n

for v <--- v  to  ci

f[v] = max( f[v], f[v - ci] + wi );

具體**:

1

void _01pack(int f, int n, int v, int c, intw)7

}8 }

可以看到從第乙個狀態轉移方程到第二個狀態轉移方程的空間優化效率還是挺大的:

f[i][v] = max( f[i - 1][v], f[i - 1][v - ci] + wi ).      ---->     f[v] = max( f[v], f[v - ci] + wi ).

在第二個方程中 f[v]1 = max(f[v]2, f[v - ci] + wi), 其實 f[v]2 就相當與方程一中的 f[i - 1][v], 對應的 f[v - ci] + wi

就相當於 f[i  -1][v - ci] + wi.這一正確性是在內層迴圈遞減的前提下才成立的.否則, 將內層迴圈改為遞增, 那麼 f[i][v] 其實是由 f[i][v] 和 f[i][v - ci] 推出來的,這不符合基本思路中的**.

之前說過由於 01揹包 的特殊性,這裡將 01揹包 抽象化,方便之後的呼叫.

解決單個物品 01揹包 的偽**:

def zeroonepack (f, c, w)

for v  <---  v  to  c

f[v] = max( f[v], f[v - c] + w );

這麼寫之後, 01揹包總問題解決的偽**就可以改寫成:

f[0...v]  <--- 0

for  i  <--- 1  to n

zeroonepack(f, c[i], w[i]);

具體**:

1

const

int maxn = 10000;2

intn, v, c[maxn], w[maxn];34

void zeroonepack(int f, int c, int w) 8}

910void solv(int

f) 15 }

四: 01揹包問題的拓展 ------ 初始化的細節問題

在上述 01揹包的問題中,僅問得是 「如何選取,才能使的最後的總價值最大」, 這裡並沒有規定是否必須裝滿揹包, 但是有的題將會給予這個附加條件, 即「在要求恰好裝滿揹包的前提下, 如何選取物品, 才能使的最後的總價值最大 」.

這兩種問法, 在**實現上相差無幾.如果是上述問法,要求 「恰好裝滿揹包」, 那麼在初始化時除了將 f[0] 賦值為 0 之外, 其他的 f[1...v] 都應該賦值為

-∞

,這樣就可以保證最後的得到的 f[v] 是一種恰好裝滿揹包的最優解.如果沒有要求必須把揹包裝滿,而是只希望價值盡量最大,初始化時應該將 f[0...v] 全部設定為 0.

之所以可以這麼做,是因為初始化的 f 事實就是沒有任何物品放入揹包時的合法狀態.如果要求揹包恰好裝滿,那麼只有容量為 0 的揹包在什麼也不裝且價值為 0 的情況下被裝 "恰好裝滿",其他容量的揹包如果不裝物品, 那麼預設的情況下都是不合法狀態,應該被賦值為-∞, 即對於第乙個物品而言, 其合法狀態只能由 f[0] 轉移得到.如果揹包並非必須被裝滿,那麼任何容量的揹包在沒有物品可裝時都存在乙個合法解,即什麼都不裝,且這個解的價值為 0.所以將其全部初始化為 0 是可以的.

注:這個技巧完全可以拓展到其他揹包問題中.

偽**:

def zeroonepack (f, c, w)

for v  <---  v  to  c

f[v] = max( f[v], f[v - c] + w )

end def

def slov()

f[0] = 0, f[1...v]  <----∞

for  i  <--- 1  to n

zeroonepack(f, c[i], w[i])

end def

具體**:

1

const

int maxn = 10000;2

intn, v, c[maxn], w[maxn];34

void zeroonepack(int f, int c, int

w) 8}9

10void solv(int

f) 16 }

五:乙個常數級別的優化

上述偽**的:

for i  <--- 1  to  n

for v <--- v  to  ci

可以優化為:

for i  <--- 1  to  n

for v <--- v  to  max( v - sum(i...n)ci, ci)

揹包問題之01揹包

01揹包就是說針對每一件物品,有選擇裝入或者放棄,是屬於動態規劃類的問題。現在假設我們有m件物品,各有價值,揹包承重為10,假設當前可用的承重量為v,當前在抉擇第m件物品是否放入,重量為w1,價值為v1,那麼,如果不放入,我們的價值應該保持不變,並與之前算出的最大價值相同,如果放入,放入後的價值就是...

揹包問題之0 1揹包

0 1揹包是最基本的揹包問題,其核心思路就在於每個物品的放與不放 每個物品最多只能放一次 題目有 n 個物品和乙個大小為 m 的揹包.給定陣列 a 表示每個物品的大小和陣列 v 表示每個物品的價值.問最多能裝入揹包的總價值是多大?樣例輸入 m 10,a 2,3,5,7 v 1,5,2,4 輸出 9 ...

揹包問題之完全揹包

完全揹包 有n種物品,每種物品有無限個,每個物品的重量為w i 價值為v i 現在有乙個揹包,它所能容納的重量為c,問 你的揹包所能帶走的最大價值是多少?之前01揹包分析過了,如果是順序的話,就表示同一物品可以多次放入!這就是完全揹包!就是這麼神奇!1 include 2 3using namesp...