題目
有N件物品和一個(gè)容量為V的背包。第i件物品的費(fèi)用是c[i]春缕,價(jià)值是w[i]。求解將哪些物品裝入背包可使價(jià)值總和最大艘蹋。
基本思路
這是最基礎(chǔ)的背包問題锄贼,特點(diǎn)是:每種物品僅有一件,可以選擇放或不放女阀。
用子問題定義狀態(tài):即f[i][v]
表示前i
件物品恰放入一個(gè)容量為v
的背包可以獲得的最大價(jià)值宅荤。則其狀態(tài)轉(zhuǎn)移方程便是:
f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]+w[i]}
這個(gè)方程非常重要屑迂,基本上所有跟背包相關(guān)的問題的方程都是由它衍生出來的。所以有必要將它詳細(xì)解釋一下:“將前i
件物品放入容量為v
的背包中”這個(gè)子問題冯键,若只考慮第i
件物品的策略(放或不放)惹盼,那么就可以轉(zhuǎn)化為一個(gè)只牽扯前i-1
件物品的問題。如果不放第i
件物品琼了,那么問題就轉(zhuǎn)化為“前i-1
件物品放入容量為v
的背包中”逻锐,價(jià)值為f[i-1][v]
;如果放第i
件物品雕薪,那么問題就轉(zhuǎn)化為“前i-1
件物品放入剩下的容量為v-c[i]
的背包中”昧诱,此時(shí)能獲得的最大價(jià)值就是f[i-1][v-c[i]]
再加上通過放入第i
件物品獲得的價(jià)值w[i]
。
優(yōu)化空間復(fù)雜度
以上方法的時(shí)間和空間復(fù)雜度均為O(N*V)
所袁,其中時(shí)間復(fù)雜度基本已經(jīng)不能再優(yōu)化了盏档,但空間復(fù)雜度卻可以優(yōu)化到O(V)
。
先考慮上面講的基本思路如何實(shí)現(xiàn)燥爷,肯定是有一個(gè)主循環(huán)i=1..N
蜈亩,每次算出來二維數(shù)組f[i][0..V]
的所有值。那么前翎,如果只用一個(gè)數(shù)組f[0..V]
稚配,能不能保證第i
次循環(huán)結(jié)束后f[v]
中表示的就是我們定義的狀態(tài)f[i][v]
呢?f[i][v]
是由f[i-1][v]
和f[i-1][v-c[i]]
兩個(gè)子問題遞推而來港华,能否保證在推f[i][v]
時(shí)(也即在第i
次主循環(huán)中推f[v]
時(shí))能夠得到f[i-1][v]
和f[i-1][v-c[i]]
的值呢道川?事實(shí)上,這要求在每次主循環(huán)中我們以v=V..0
的順序推f[v]
立宜,這樣才能保證推f[v]
時(shí)f[v-c[i]]
保存的是狀態(tài)f[i-1][v-c[i]]
的值冒萄。偽代碼如下:
for i=1..N
for v=V..0
f[v]=max{f[v],f[v-c[i]]+w[i]};
其中的f[v]=max{f[v],f[v-c[i]]}
一句恰就相當(dāng)于我們的轉(zhuǎn)移方程f[i][v]=max{f[i-1][v],f[i-1][v-c[i]]}
,因?yàn)楝F(xiàn)在的f[v-c[i]]
就相當(dāng)于原來的f[i-1][v-c[i]]
橙数。如果將v
的循環(huán)順序從上面的逆序改成順序的話尊流,那么則成了f[i][v]
由f[i][v-c[i]]
推知,與本題意不符灯帮,但它卻是另一個(gè)重要的背包問題P02最簡捷的解決方案崖技,故學(xué)習(xí)只用一維數(shù)組解01背包問題是十分必要的。
事實(shí)上钟哥,使用一維數(shù)組解01背包的程序在后面會(huì)被多次用到迎献,所以這里抽象出一個(gè)處理一件01背包中的物品過程,以后的代碼中直接調(diào)用不加說明瞪醋。
過程ZeroOnePack
,表示處理一件01背包中的物品装诡,兩個(gè)參數(shù)cost
银受、weight
分別表明這件物品的費(fèi)用和價(jià)值践盼。
procedure ZeroOnePack(cost,weight)
for v=V..cost
f[v]=max{f[v],f[v-cost]+weight}
注意這個(gè)過程里的處理與前面給出的偽代碼有所不同。前面的示例程序?qū)懗?code>v=V..0是為了在程序中體現(xiàn)每個(gè)狀態(tài)都按照方程求解了宾巍,避免不必要的思維復(fù)雜度咕幻。而這里既然已經(jīng)抽象成看作黑箱的過程了,就可以加入優(yōu)化顶霞。費(fèi)用為cost
的物品不會(huì)影響狀態(tài)f[0..cost-1]
肄程,這是顯然的。
有了這個(gè)過程以后选浑,01背包問題的偽代碼就可以這樣寫:
for i=1..N
ZeroOnePack(c[i],w[i]);
初始化的細(xì)節(jié)問題
我們看到的求最優(yōu)解的背包問題題目中蓝厌,事實(shí)上有兩種不太相同的問法。有的題目要求“恰好裝滿背包”時(shí)的最優(yōu)解古徒,有的題目則并沒有要求必須把背包裝滿拓提。一種區(qū)別這兩種問法的實(shí)現(xiàn)方法是在初始化的時(shí)候有所不同。
<p>如果是第一種問法隧膘,要求恰好裝滿背包代态,那么在初始化時(shí)除了f[0]
為0
其它f[1..V]
均設(shè)為-∞
,這樣就可以保證最終得到的f[N]
是一種恰好裝滿背包的最優(yōu)解疹吃。
如果并沒有要求必須把背包裝滿蹦疑,而是只希望價(jià)格盡量大,初始化時(shí)應(yīng)該將f[0..V]
全部設(shè)為0
萨驶。
<p>為什么呢歉摧?可以這樣理解:初始化的f數(shù)組事實(shí)上就是在沒有任何物品可以放入背包時(shí)的合法狀態(tài)。如果要求背包恰好裝滿篡撵,那么此時(shí)只有容量為0的背包可能被價(jià)值為0的nothing“恰好裝滿”判莉,其它容量的背包均沒有合法的解,屬于未定義的狀態(tài)育谬,它們的值就都應(yīng)該是-∞了券盅。如果背包并非必須被裝滿,那么任何容量的背包都有一個(gè)合法解“什么都不裝”膛檀,這個(gè)解的價(jià)值為0锰镀,所以初始時(shí)狀態(tài)的值也就全部為0了。
這個(gè)小技巧完全可以推廣到其它類型的背包問題咖刃,后面也就不再對(duì)進(jìn)行狀態(tài)轉(zhuǎn)移之前的初始化進(jìn)行講解泳炉。
小結(jié)
01背包問題是最基本的背包問題,它包含了背包問題中設(shè)計(jì)狀態(tài)嚎杨、方程的最基本思想花鹅,另外,別的類型的背包問題往往也可以轉(zhuǎn)換成01背包問題求解枫浙。故一定要仔細(xì)體會(huì)上面基本思路的得出方法刨肃,狀態(tài)轉(zhuǎn)移方程的意義古拴,以及最后怎樣優(yōu)化的空間復(fù)雜度。