日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

动态规划—01背包问题

發(fā)布時(shí)間:2024/4/15 编程问答 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 动态规划—01背包问题 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

原文作者:弗蘭克的貓?

原文地址:【動(dòng)態(tài)規(guī)劃】01背包問(wèn)題

摘要:

01背包問(wèn)題:n個(gè)物品放入容量為c的背包中。

常見(jiàn)解法:

  • 分治法:遞歸計(jì)算,且存在重復(fù)計(jì)算的bug
  • 自上而下填表法:從大到小使用遞歸計(jì)算,利用二維數(shù)組保存已經(jīng)計(jì)算過(guò)的結(jié)果,減少遞歸次數(shù)
  • 自下而上填表法:從小到大使用轉(zhuǎn)移方程計(jì)算,利用二維數(shù)組保存所有結(jié)果。無(wú)遞歸,效率高

目錄

說(shuō)明

最優(yōu)化原理

無(wú)后效性

01背包問(wèn)題

問(wèn)題分析

分治法

動(dòng)態(tài)規(guī)劃解法

驗(yàn)證可行性

自上而下記憶法

自下而上填表法

總結(jié)


0|1說(shuō)明

前面用動(dòng)態(tài)規(guī)劃解決了正則表達(dá)式的問(wèn)題,感覺(jué)還是不過(guò)癮,總覺(jué)得對(duì)于動(dòng)態(tài)規(guī)劃的理解還沒(méi)有到位,所以趁熱打鐵,繼續(xù)研究幾個(gè)動(dòng)態(tài)規(guī)劃的經(jīng)典問(wèn)題,希望能夠借此加深對(duì)動(dòng)態(tài)規(guī)劃的理解。在此之前,還需要說(shuō)兩個(gè)跟動(dòng)態(tài)規(guī)劃有關(guān)的理論知識(shí)。

0|1最優(yōu)化原理

最優(yōu)化原理指的最優(yōu)策略具有這樣的性質(zhì):不論過(guò)去狀態(tài)和決策如何,對(duì)前面的決策所形成的狀態(tài)而言,余下的諸決策必須構(gòu)成最優(yōu)策略。簡(jiǎn)單來(lái)說(shuō)就是一個(gè)最優(yōu)策略的子策略也是必須是最優(yōu)的,而所有子問(wèn)題的局部最優(yōu)解將導(dǎo)致整個(gè)問(wèn)題的全局最優(yōu)。如果一個(gè)問(wèn)題能滿足最優(yōu)化原理,就稱其具有最優(yōu)子結(jié)構(gòu)性質(zhì)。這是判斷問(wèn)題能否使用動(dòng)態(tài)規(guī)劃解決的先決條件,如果一個(gè)問(wèn)題不能滿足最優(yōu)化原理,那么這個(gè)問(wèn)題就不適合用動(dòng)態(tài)規(guī)劃來(lái)求解。這樣說(shuō)可能比較模糊,來(lái)舉個(gè)栗子吧:

如上圖,求從A點(diǎn)到E點(diǎn)的最短距離,那么子問(wèn)題就是求從A點(diǎn)到E點(diǎn)之間的中間點(diǎn)到E點(diǎn)的最短距離,比如這里的B點(diǎn)。那么這個(gè)問(wèn)題里,怎么證明最優(yōu)化原理呢?我們假設(shè)從A點(diǎn)到E點(diǎn)的最短距離為d,其最優(yōu)策略的子策略假設(shè)經(jīng)過(guò)B點(diǎn),記該策略中B點(diǎn)到E點(diǎn)的距離為d1,A點(diǎn)到B點(diǎn)的距離為d2。我們可以使用反證法,假設(shè)存在B點(diǎn)到E點(diǎn)的最短距離d3,并且d3 < d1,那么?d3 + d2 < d1 + d2 = d,這與d是最短距離相矛盾,所以,d1是B點(diǎn)到E點(diǎn)的最短距離。為了增加理解,這里再舉一個(gè)反例:

圖中有四個(gè)點(diǎn),A、B、C、D,相鄰兩點(diǎn)有兩條連線,代表兩條通道,d1,d2,d3,d4,d5,d6代表的是道路的長(zhǎng)度,求A到D的所有通道中,總長(zhǎng)度除以4得到的余數(shù)最小的路徑為最優(yōu)路徑,求一條最優(yōu)路徑。這里如果還是按照上面的思路去求解,就會(huì)誤入歧途了。按照之前的思路,A的最優(yōu)取值應(yīng)該可以由B的最優(yōu)取值來(lái)確定,而B(niǎo)的最優(yōu)取值為(3+5)mod 4 = 0。所以應(yīng)該選d2和d6這兩條道路,而實(shí)際上,全局最優(yōu)解是d4+d5+d6或者d1+d5+d3。所以這里子問(wèn)題的最優(yōu)解并不是原問(wèn)題的最優(yōu)解,即不滿足最優(yōu)化原理。所以就不適合使用動(dòng)態(tài)規(guī)劃來(lái)求解了。

0|1無(wú)后效性

無(wú)后效性指的是某狀態(tài)下決策的收益,只與狀態(tài)和決策相關(guān),與到達(dá)該狀態(tài)的方式無(wú)關(guān)。某個(gè)階段的狀態(tài)一旦確定,則此后過(guò)程的演變不再受此前各種狀態(tài)及決策的影響。換句話說(shuō),未來(lái)與過(guò)去無(wú)關(guān),當(dāng)前狀態(tài)是此前歷史狀態(tài)的完整總結(jié),此前歷史決策只能通過(guò)影響當(dāng)前的狀態(tài)來(lái)影響未來(lái)的演變。再換句話說(shuō),過(guò)去做的選擇不會(huì)影響現(xiàn)在能做的最優(yōu)選擇,現(xiàn)在能做的最優(yōu)選擇只與當(dāng)前的狀態(tài)有關(guān),與經(jīng)過(guò)如何復(fù)雜的決策到達(dá)該狀態(tài)的方式無(wú)關(guān)。這也是用來(lái)驗(yàn)證問(wèn)題是否可以使用動(dòng)態(tài)規(guī)劃來(lái)解答的重要方法。

我們?cè)倩仡^看看上面的最短路徑問(wèn)題,如果在原來(lái)的基礎(chǔ)上加上一個(gè)限制條件:同一個(gè)格子只能通過(guò)一次。那么, 這個(gè)題就不符合無(wú)后效性了,因?yàn)榍耙粋€(gè)子問(wèn)題的解會(huì)對(duì)后面子問(wèn)題的選擇策略有影響,比如說(shuō),如果從A到B選擇了一條如下圖中綠色表示的路線,那么從B點(diǎn)出發(fā)到達(dá)E點(diǎn)的路線就只有一條了。也就是說(shuō)從A點(diǎn)到B點(diǎn)的路徑選擇會(huì)影響B(tài)點(diǎn)到E點(diǎn)的路徑選擇。

理論部分就此打住,接下來(lái)我們實(shí)戰(zhàn)一下。

0|101背包問(wèn)題

假設(shè)你是一名經(jīng)驗(yàn)豐富的探險(xiǎn)家,背著背包來(lái)到野外進(jìn)行日常探險(xiǎn)。天氣晴朗而不燥熱,山間的風(fēng)夾雜著花香,正當(dāng)你欣賞這世外桃源般的美景時(shí),突然,你發(fā)現(xiàn)了一個(gè)洞穴,這個(gè)洞穴外表看起來(lái)其貌不揚(yáng),但憑借著驚為天人的直覺(jué),這個(gè)洞穴不簡(jiǎn)單。

于是,你開(kāi)始往洞穴內(nèi)探索,希望能發(fā)現(xiàn)一些有意思的東西。終于,皇天不負(fù)有心人,你在洞穴的盡頭,發(fā)現(xiàn)了一堆不世出的珠寶,憑借你驚人的閱歷,一眼便看出了它們各自的價(jià)值,心想著下下下下下下下下半輩子都有著落了。

然而,天有不測(cè)風(fēng)云,正準(zhǔn)備將它們收入囊中,卻不小心觸碰到一個(gè)防御機(jī)關(guān),洞穴馬上就要崩塌了。在此危機(jī)時(shí)刻,你只有一個(gè)背包,你必須盡快做出抉擇,從中選擇最值錢的珠寶塞到你的背包,讓背包中珠寶的總價(jià)值最大。

好了好了,啰里啰嗦了大半天,我還是來(lái)精簡(jiǎn)一下問(wèn)題吧。簡(jiǎn)而言之,你只有一個(gè)容量有限的背包,總?cè)萘繛閏,有n個(gè)可待選擇的物品,每個(gè)物品只有一件,它們都有各自的重量和價(jià)值,你需要從中選擇合適的組合來(lái)使得你背包中的物品總價(jià)值最大。

0|1問(wèn)題分析

那還不簡(jiǎn)單,不管是什么,先往背包里塞,塞滿趕緊走,狗命要緊,狗命要緊。。。

好了好了,開(kāi)個(gè)玩笑,言歸正傳。簡(jiǎn)單起見(jiàn),我們來(lái)將上面的問(wèn)題具體化,舉一個(gè)更具體的栗子:假設(shè)有4個(gè)物品,它們的價(jià)值(v)和重量(w)如下圖:

背包總?cè)萘繛?0,現(xiàn)在要從中選擇物品裝入背包中,要求物品的重量不能超過(guò)背包的容量,并且最后放在背包中物品的總價(jià)值最大。emmm,等等,為什么叫做0/1背包呢?為什么不叫1/2背包,2/3背包???仔細(xì)想想,這里每個(gè)物品只有一個(gè),對(duì)于每個(gè)物品而言,只有兩種選擇,盤它或者不盤,盤它記為1,不盤記為0,我們不能將物品進(jìn)行分割,比如只拿半個(gè)是不允許的。這就是這個(gè)問(wèn)題被稱為0/1背包問(wèn)題的原因。所以究竟選還是不選,這是個(gè)問(wèn)題。

讓我們先來(lái)體驗(yàn)一下將珠寶裝入背包的感覺(jué),為了方便起見(jiàn),用xi代表第i個(gè)珠寶的選擇(xi = 1?代表選擇該珠寶,0則代表不選),vi代表第i個(gè)珠寶的價(jià)值,wi代表第i個(gè)珠寶的重量。于是我們就有了這樣的限制條件:

我們的初始狀態(tài)是背包容量為10,背包內(nèi)物品總價(jià)值為0,接下來(lái),我們就要開(kāi)始做選擇了。對(duì)于1號(hào)珠寶,當(dāng)前容量為10,容納它的重量2綽綽有余,因此有兩種選擇,選它或者不選。我們選擇一個(gè)珠寶的時(shí)候,背包的容量會(huì)減少,但是里面的物品總價(jià)值會(huì)增加。就像下面這樣:

這樣就分出了兩種情況,我們繼續(xù)進(jìn)行選擇,如果我們選擇了珠寶1,那么對(duì)于珠寶2,當(dāng)前剩余容量為8,大于珠寶2的容量3,因此也有兩種選擇,選或者不選。

現(xiàn)在,我們得到了四個(gè)可能結(jié)果,我們每做出一個(gè)選擇,就會(huì)將上面的每一種可能分裂成兩種可能,后續(xù)的選擇也是如此,最終,我們會(huì)得到如下的一張決策圖:

這里被涂上色的方框代表我們的最終待選結(jié)果,本來(lái)應(yīng)該有16個(gè)待選結(jié)果,但有三個(gè)結(jié)果由于容量不足以容納下最后一個(gè)珠寶,所以就沒(méi)有繼續(xù)進(jìn)行裂變。然后,我們從這些結(jié)果中,找出價(jià)值最大的那個(gè),也就是13,這就是我們的最優(yōu)選擇,根據(jù)這個(gè)選擇,依次找到它的所有路徑,便可以知道該選哪幾個(gè)珠寶,最終結(jié)果是:珠寶4,珠寶2,珠寶1。

0|1分治法

接下來(lái),我們就來(lái)分析一下,如何將它擴(kuò)展到一般情況。為了實(shí)現(xiàn)這個(gè)目的,我們需要將問(wèn)題進(jìn)行抽象并建模,然后將其劃分為更小的子問(wèn)題,找出遞推關(guān)系式,這是分治思想中很重要的一步。

  • 抽象問(wèn)題,背包問(wèn)題抽象為尋找組合(x1,x2,x3...xn,其中xi取0或1,表示第i個(gè)物品取或者不取),vi代表第i個(gè)物品的價(jià)值,wi代表第i個(gè)物品的重量,總物品數(shù)為n,背包容量為c。
  • 建模,問(wèn)題即求max(x1v1 + x2v2 + x3v3 + ... + xnvn)。
  • 約束條件,x1w1 + x2w2 + x3w3 + ... + xnwn < c
  • 定義函數(shù)KS(i,j):代表當(dāng)前背包剩余容量為j時(shí),前i個(gè)物品最佳組合所對(duì)應(yīng)的價(jià)值;
  • 那這里的遞推關(guān)系式是怎樣的呢?對(duì)于第i個(gè)物品,有兩種可能:

  • 背包剩余容量不足以容納該物品,此時(shí)背包的價(jià)值與前i-1個(gè)物品的價(jià)值是一樣的,KS(i,j) = KS(i-1,j)
  • 背包剩余容量可以裝下該商品,此時(shí)需要進(jìn)行判斷,因?yàn)檠b了該商品不一定能使最終組合達(dá)到最大價(jià)值,如果不裝該商品,則價(jià)值為:KS(i-1,j),如果裝了該商品,則價(jià)值為KS(i-1,j-wi) + vi,從兩者中選擇較大的那個(gè),所以就得出了遞推關(guān)系式:
  • 對(duì)于這個(gè)問(wèn)題的子問(wèn)題,這里有必要詳細(xì)說(shuō)明一下。原問(wèn)題是,將n件物品放入容量為c的背包,子問(wèn)題則是,將前i件物品放入容量為j的背包,所得到的最優(yōu)價(jià)值為KS(i,j),如果只考慮第i件物品放還是不放,那么就可以轉(zhuǎn)化為一個(gè)只涉及到前i-1個(gè)物品的問(wèn)題。如果不放第i個(gè)物品,那么問(wèn)題就轉(zhuǎn)化為“前i-1件物品放入容量為j的背包中的最優(yōu)價(jià)值組合”,對(duì)應(yīng)的值為KS(i-1,j)。如果放第i個(gè)物品,那么問(wèn)題就轉(zhuǎn)化成了“前i-1件物品放入容量為j-wi的背包中的最優(yōu)價(jià)值組合”,此時(shí)對(duì)應(yīng)的值為KS(i-1,j-wi)+vi。所以,就可以很容易的寫出遞歸解法了:

    public class Solution{int[] vs = {0,2,4,3,7};int[] ws = {0,2,3,5,5};@Testpublic void testKnapsack1() {int result = ks(4,10);System.out.println(result);}private int ks(int i, int c){int result = 0;if (i == 0 || c == 0){// 初始條件result = 0;} else if(ws[i] > c){// 裝不下該珠寶result = ks(i-1, c);} else {// 可以裝下int tmp1 = ks(i-1, c);int tmp2 = ks(i-1, c-ws[i]) + vs[i];result = Math.max(tmp1, tmp2);}return result;} }

    這里為了方便處理,將數(shù)組ws和vs都增加了一個(gè)補(bǔ)位數(shù)0,防止數(shù)組越界,輸出結(jié)果:

    13

    這樣,我們就輕松加愉快的解決了這個(gè)問(wèn)題。但是Math.max(ks(i-1, c),ks(i-1, c-ws[i]) + vs[i])遞歸計(jì)算的時(shí)候,會(huì)導(dǎo)致重復(fù)計(jì)算,如下圖所示:

    0|1動(dòng)態(tài)規(guī)劃解法

    驗(yàn)證可行性

    既然開(kāi)頭已經(jīng)說(shuō)了兩個(gè)驗(yàn)證問(wèn)題是否可以使用動(dòng)態(tài)規(guī)劃求解的方法,那么為何不試一試呢?先來(lái)看看最優(yōu)化原理。同樣,我們使用反證法:假設(shè)(x1,x2,…,xn)是01背包問(wèn)題的最優(yōu)解,則有(x2,x3,…,xn)是其子問(wèn)題的最優(yōu)解,假設(shè)(y2,y3,…,yn)是上述問(wèn)題的子問(wèn)題最優(yōu)解,則有(v2y2+v3y3+…+vnyn)+v1x1 > (v2x2+v3x3+…+vnxn)+v1x1。說(shuō)明(X1,Y2,Y3,…,Yn)才是該01背包問(wèn)題的最優(yōu)解,這與最開(kāi)始的假設(shè)(X1,X2,…,Xn)是01背包問(wèn)題的最優(yōu)解相矛盾,故01背包問(wèn)題滿足最優(yōu)性原理。

    至于無(wú)后效性,其實(shí)比較好理解。對(duì)于任意一個(gè)階段,只要背包剩余容量和可選物品是一樣的,那么我們能做出的現(xiàn)階段的最優(yōu)選擇必定是一樣的,是不受之前選擇了什么物品所影響的。即滿足無(wú)后效性。

    自上而下記憶法

    就像上一篇里的解法一樣,自上而下的解法與分治法的區(qū)別就是增加了一個(gè)數(shù)組用來(lái)存儲(chǔ)計(jì)算的中間結(jié)果來(lái)減少重復(fù)計(jì)算。這里,我們只需要多定義一個(gè)二維數(shù)組。

    表格中,每一個(gè)格子都代表著一個(gè)子問(wèn)題,我們最終的問(wèn)題是求最右下角的格子的值,也就是i=4,j=10時(shí)的值。這里,我們的初始條件便是i=0或者j=0時(shí)對(duì)應(yīng)的ks值為0,這很好理解,如果可選物品為0,或者剩余容量為0,那么最大價(jià)值自然也是0。代碼如下:

    public class Solution{int[] vs = {0,2,4,3,7};int[] ws = {0,2,3,5,5};Integer[][] results = new Integer[5][11];@Testpublic void testKnapsack2() {int result = ks2(4,10);System.out.println(result);}private int ks2(int i, int c){int result = 0;// 如果該結(jié)果已經(jīng)被計(jì)算,那么直接返回//通過(guò)這行代碼解決分治法中重復(fù)計(jì)算的問(wèn)題if (results[i][c] != null) return results[i][c];if (i == 0 || c == 0){// 初始條件result = 0;} else if(ws[i] > c){// 裝不下該珠寶result = ks(i-1, c);} else {// 可以裝下int tmp1 = ks(i-1, c);int tmp2 = ks(i-1, c-ws[i]) + vs[i];result = Math.max(tmp1, tmp2);results[i][c] = result;}return result;} }

    可以看到,其實(shí)只比分治多了三行代碼。而且通過(guò)數(shù)組保存以前計(jì)算結(jié)果的方式解決分治法中重復(fù)計(jì)算的問(wèn)題

    自下而上填表法

    自上而下填表需要用到遞歸,效率較低,接下來(lái),我們用自下而上的方法來(lái)解一下這道題,思路很簡(jiǎn)單,就是不斷的填表,回想一下上一篇中的斐波拉契數(shù)列的自下而上解法,這里將使用同樣的方式來(lái)解決。還是使用上面的表格,我們開(kāi)始一行行填表。

    當(dāng)i=1時(shí),即只有珠寶1可供選擇,那么如果容量足夠的話,最大價(jià)值自然就是珠寶1的價(jià)值了。

    當(dāng)i=2時(shí),有兩個(gè)物品可供選擇,此時(shí)應(yīng)用上面的遞推關(guān)系式進(jìn)行判斷即可。這里以i=2,j=3為例進(jìn)行分析:

    剩下的格子使用相同的方法進(jìn)行填充即可:

    這樣,我們就得到了最后的結(jié)果:13。根據(jù)結(jié)果,我們可以反向找出各個(gè)物品的選擇,尋找的方法很簡(jiǎn)單,就是從i=4,j=10開(kāi)始尋找,如果ks(i-1,j)=ks(i,j),說(shuō)明第i個(gè)物品沒(méi)有被選中,從ks(i-1,j)繼續(xù)尋找。否則,表示第i個(gè)物品已被選中,則從ks(i-1,j-wi)開(kāi)始尋找。

    ?

    轉(zhuǎn)化成代碼:

    public class Solution{int[] vs = {0,2,4,3,7};int[] ws = {0,2,3,5,5};Integer[][] results = new Integer[5][11];@Testpublic void testKnapsack3() {int result = ks3(4,10);System.out.println(result);}private int ks3(int i, int j){// 初始化for (int m = 0; m <= i; m++){results[m][0] = 0;}for (int m = 0; m <= j; m++){results[0][m] = 0;}// 開(kāi)始填表for (int m = 1; m <= i; m++){for (int n = 1; n <= j; n++){if (n < ws[m]){// 裝不進(jìn)去results[m][n] = results[m-1][n];} else {// 容量足夠if (results[m-1][n] > results[m-1][n-ws[m]] + vs[m]){// 不裝該珠寶,最優(yōu)價(jià)值更大results[m][n] = results[m-1][n];} else {results[m][n] = results[m-1][n-ws[m]] + vs[m];}}}}return results[i][j];} }

    嗯,完美解決。時(shí)間復(fù)雜度即填表耗時(shí)O(n * c),這里用了一個(gè)二維數(shù)組來(lái)存儲(chǔ)子問(wèn)題的解,所以空間復(fù)雜度為O(n * c);

    0|1總結(jié)

    回過(guò)頭再看看上面的分析,會(huì)發(fā)現(xiàn)動(dòng)態(tài)規(guī)劃里最關(guān)鍵的問(wèn)題其實(shí)是尋找原問(wèn)題的子問(wèn)題,并寫出遞推表達(dá)式,只要完成了這一步,代碼部分都是水到渠成的事情了。那么問(wèn)題來(lái)了,怎樣把問(wèn)題拆分成子問(wèn)題呢?emmm,這個(gè)問(wèn)題有點(diǎn)超綱了,說(shuō)實(shí)話,我也沒(méi)有掌握到訣竅,還是得具體情況具體分析,但是很多經(jīng)典的問(wèn)題都有其經(jīng)典的套路,其它問(wèn)題都可以歸結(jié)到這些問(wèn)題上面來(lái),可以看做是它們的變種和延伸,把這些經(jīng)典的問(wèn)題吃透的話,自然能舉一反三。比如采藥問(wèn)題,本質(zhì)上就是01背包問(wèn)題,而硬幣問(wèn)題,本質(zhì)上就是我們之后要介紹的完全背包問(wèn)題。個(gè)人認(rèn)為,算法不在于刷多少個(gè),而在于歸納總結(jié),就跟做數(shù)學(xué)題一樣,總有一些范式和套路,不管形式如何變化,其本質(zhì)是一樣的,萬(wàn)變不離其宗,說(shuō)的就是這么回事。

    本篇到此就告一段落了,如果覺(jué)得有收獲,不要吝嗇你的贊哦,也歡迎關(guān)注我的公眾號(hào)留言交流。

    __EOF__

    作  者:弗蘭克的貓
    出  處:https://www.cnblogs.com/mfrank/p/10533701.html
    關(guān)于博主:編程路上的小學(xué)生,熱愛(ài)技術(shù),喜歡專研。評(píng)論和私信會(huì)在第一時(shí)間回復(fù)。或者直接私信我。
    版權(quán)聲明:署名 - 非商業(yè)性使用 - 禁止演繹,協(xié)議普通文本?|?協(xié)議法律文本。
    聲援博主:如果您覺(jué)得文章對(duì)您有幫助,可以點(diǎn)擊文章右下角【推薦】一下。您的鼓勵(lì)是博主的最大動(dòng)力!

    總結(jié)

    以上是生活随笔為你收集整理的动态规划—01背包问题的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。