轉(zhuǎn)自背包九問(wèn):http://love-oriented.com/pack/P01.html
P01: 01背包問(wèn)題
題目
有N件物品和一個(gè)容量為V的背包菱阵。第i件物品的費(fèi)用是c[i]微驶,價(jià)值是w[i]。求解將哪些物品裝入背包可使價(jià)值總和最大呼巷。
基本思路
這是最基礎(chǔ)的背包問(wèn)題,特點(diǎn)是:每種物品僅有一件,可以選擇放或不放蹦哼。
用子問(wè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)的問(wèn)題的方程都是由它衍生出來(lái)的纲熏。所以有必要將它詳細(xì)解釋一下:“將前i件物品放入容量為v的背包中”這個(gè)子問(wèn)題,若只考慮第i件物品的策略(放或不放)锄俄,那么就可以轉(zhuǎn)化為一個(gè)只牽扯前i-1件物品的問(wèn)題局劲。如果不放第i件物品,那么問(wèn)題就轉(zhuǎn)化為“前i-1件物品放入容量為v的背包中”奶赠,價(jià)值為f[i-1][v]鱼填;如果放第i件物品,那么問(wèn)題就轉(zhuǎn)化為“前i-1件物品放入剩下的容量為v-c[i]的背包中”车柠,此時(shí)能獲得的最大價(jià)值就是f[i-1][v-c[i]]再加上通過(guò)放入第i件物品獲得的價(jià)值w[i]剔氏。
優(yōu)化空間復(fù)雜度
以上方法的時(shí)間和空間復(fù)雜度均為O(VN),其中時(shí)間復(fù)雜度應(yīng)該已經(jīng)不能再優(yōu)化了竹祷,但空間復(fù)雜度卻可以優(yōu)化到O(N)谈跛。
先考慮上面講的基本思路如何實(shí)現(xiàn),肯定是有一個(gè)主循環(huán)i=1..N塑陵,每次算出來(lái)二維數(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è)子問(wèn)題遞推而來(lái),能否保證在推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)于原來(lái)的f[i-1][v-c[i]]杏糙。
如果將v的循環(huán)順序從上面的逆序改成順序的話,那么則成了f[i][v]由f[i][v-c[i]]推知蚓土,與本題意不符宏侍,但它卻是另一個(gè)重要的背包問(wèn)題P02最簡(jiǎn)捷的解決方案,故學(xué)習(xí)只用一維數(shù)組解01背包問(wèn)題是十分必要的蜀漆。
事實(shí)上谅河,使用一維數(shù)組解01背包的程序在后面會(huì)被多次用到,所以這里抽象出一個(gè)處理一件01背包中的物品過(guò)程确丢,以后的代碼中直接調(diào)用不加說(shuō)明绷耍。
過(guò)程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è)過(guò)程里的處理與前面給出的偽代碼有所不同剃毒。前面的示例程序?qū)懗蓈=V..0是為了在程序中體現(xiàn)每個(gè)狀態(tài)都按照方程求解了,避免不必要的思維復(fù)雜度搂赋。而這里既然已經(jīng)抽象成看作黑箱的過(guò)程了赘阀,就可以加入優(yōu)化。費(fèi)用為cost的物品不會(huì)影響狀態(tài)f[0..cost-1]脑奠,這是顯然的基公。
有了這個(gè)過(guò)程以后,01背包問(wèn)題的偽代碼就可以這樣寫(xiě):
for i=1..N
ZeroOnePack(c[i],w[i]);
初始化的細(xì)節(jié)問(wèn)題
我們看到的求最優(yōu)解的背包問(wèn)題題目中宋欺,事實(shí)上有兩種不太相同的問(wèn)法轰豆。有的題目要求“恰好裝滿背包”時(shí)的最優(yōu)解,有的題目則并沒(méi)有要求必須把背包裝滿齿诞。一種區(qū)別這兩種問(wèn)法的實(shí)現(xiàn)方法是在初始化的時(shí)候有所不同酸休。
如果是第一種問(wèn)法,要求恰好裝滿背包祷杈,那么在初始化時(shí)除了f[0]為0其它f[1..V]均設(shè)為-∞斑司,這樣就可以保證最終得到的f[N]是一種恰好裝滿背包的最優(yōu)解。
如果并沒(méi)有要求必須把背包裝滿但汞,而是只希望價(jià)格盡量大宿刮,初始化時(shí)應(yīng)該將f[0..V]全部設(shè)為0。
為什么呢私蕾?可以這樣理解:初始化的f數(shù)組事實(shí)上就是在沒(méi)有任何物品可以放入背包時(shí)的合法狀態(tài)僵缺。如果要求背包恰好裝滿,那么此時(shí)只有容量為0的背包可能被價(jià)值為0的nothing“恰好裝滿”踩叭,其它容量的背包均沒(méi)有合法的解磕潮,屬于未定義的狀態(tài),它們的值就都應(yīng)該是-∞了。如果背包并非必須被裝滿揉抵,那么任何容量的背包都有一個(gè)合法解“什么都不裝”亡容,這個(gè)解的價(jià)值為0,所以初始時(shí)狀態(tài)的值也就全部為0了冤今。
這個(gè)小技巧完全可以推廣到其它類(lèi)型的背包問(wèn)題闺兢,后面也就不再對(duì)進(jìn)行狀態(tài)轉(zhuǎn)移之前的初始化進(jìn)行講解。
一個(gè)常數(shù)優(yōu)化
前面的偽代碼中有 for v=V..1戏罢,可以將這個(gè)循環(huán)的下限進(jìn)行改進(jìn)屋谭。
由于只需要最后f[v]的值,倒推前一個(gè)物品龟糕,其實(shí)只要知道f[v-w[n]]即可桐磁。以此類(lèi)推,對(duì)以第j個(gè)背包讲岁,其實(shí)只需要知道到f[v-sum{w[j..n]}]即可我擂,即代碼中的
for i=1..N for v=V..0
可以改成
for i=1..n
bound=max{V-sum{w[i..n]},c[i]}
for v=V..bound
這對(duì)于V比較大時(shí)是有用的。
小結(jié)
01背包問(wèn)題是最基本的背包問(wèn)題缓艳,它包含了背包問(wèn)題中設(shè)計(jì)狀態(tài)校摩、方程的最基本思想,另外阶淘,別的類(lèi)型的背包問(wèn)題往往也可以轉(zhuǎn)換成01背包問(wèn)題求解衙吩。故一定要仔細(xì)體會(huì)上面基本思路的得出方法,狀態(tài)轉(zhuǎn)移方程的意義溪窒,以及最后怎樣優(yōu)化的空間復(fù)雜度坤塞。