動態規劃專題 01揹包問題詳解【轉】

         對於動態規劃,每一個剛接觸的人都須要一段時間來理解,特別是第一次接觸的時候老是想不通爲何這種方法可行,這篇文章就是爲了幫助你們理解動態規劃,並經過講解基本的01揹包問題來引導讀者如何去思考動態規劃。本文力求通俗易懂,無異性,不讓讀者感到迷惑,引導讀者去思考,因此若是你在閱讀中發現有不通順的地方,讓你產生錯誤理解的地方,讓你可貴讀懂的地方,請跟貼指出,謝謝!算法

初識動態規劃


 

       經典的01揹包問題是這樣的:函數

       有一個包和n個物品,包的容量爲m,每一個物品都有各自的體積和價值,問當從這n個物品中選擇多個物品放在包裏而物品體積總數不超過包的容量m時,可以獲得的最大價值是多少?[對於每一個物品不能夠取屢次,最多隻能取一次,之因此叫作01揹包,0表示不取,1表示取]優化

 

       爲了用一種生動又更形象的方式來說解此題,我把此題用另外一種方式來描述,以下:spa

      

       有一個國家,全部的國民都很是老實憨厚,某天他們在本身的國家發現了十座金礦,而且這十座金礦在地圖上排成一條直線,國王知道這個消息後很是高興,他但願可以把這些金子都挖出來造福國民,首先他把這些金礦按照在地圖上的位置從西至東進行編號,依次爲0、一、二、三、四、五、六、七、八、9,而後他命令他的手下去對每一座金礦進行勘測,以便知道挖取每一座金礦須要多少人力以及每座金礦可以挖出多少金子,而後動員國民都來挖金子。數學

 

       題目補充1:挖每一座金礦須要的人數是固定的,多一我的少一我的都不行。國王知道每一個金礦各須要多少人手,金礦i須要的人數爲peopleNeeded[i]。io

       題目補充2:每一座金礦所挖出來的金子數是固定的,當第i座金礦有peopleNeeded[i]人去挖的話,就必定能剛好挖出gold[i]個金子。不然一個金子都挖不出來。變量

       題目補充3:開採一座金礦的人完成開採工做後,他們不會再次去開採其它金礦,所以一我的最多隻能使用一次。打包

       題目補充4:國王在全國範圍內僅招募到了10000名願意爲了國家去挖金子的人,所以這些人可能不夠把全部的金子都挖出來,可是國王但願挖到的金子越多越好。循環

       題目補充5:這個國家的每個人都很老實(包括國王),不會私吞任何金子,也不會弄虛做假,不會說謊言。程序

       題目補充6:有不少人拿到這個題後的第一反應就是對每個金礦求出平均每一個人能挖出多少金子,而後從高到低進行選擇,這裏要強調這種方法是錯的,若是你也是這樣想的,請考慮揹包模型,當有一個揹包的容量爲10,共有3個物品,體積分別是三、三、5,價值分別是六、六、9,那麼你的方法取到的是前兩個物品,總價值是12,但明顯最大值是後兩個物品組成的15。

       題目補充7:咱們只須要知道最多能夠挖出多少金子便可,而不用關心哪些金礦挖哪些金礦不挖。

 

       那麼,國王究竟如何知道在只有10000我的的狀況下最多能挖出多少金子呢?國王是如何思考這個問題的呢?

 

       國王首先來到了第9個金礦的所在地(注意,第9個就是最後一個,由於是從0開始編號的,最西邊的那個金礦是第0個),他的臣子告訴他,若是要挖取第9個金礦的話就須要1500我的,而且第9個金礦能夠挖出8888個金子。聽到這裏國王哈哈大笑起來,由於原先他覺得要知道十個金礦在僅有10000個 人的狀況下最多能挖出多少金子是一件很難思考的問題,可是,就在剛纔聽完他的臣子所說的那句話時,國王已經知道總共最多能挖出多少金子了,國王是如何在不 瞭解其它金礦的狀況下知道最多能挖出多少金子的呢?他的臣子們也不知道這個謎,所以他的臣子們就問他了:「最聰明的國王陛下,咱們都沒有告訴您其它金礦的 狀況,您是如何知道最終答案的呢?」

 

       得意的國王笑了笑,而後把他最得意的「左、右手」叫到跟前,說到:「我並不須要考慮最終要挖哪些金礦才能獲得最多的金子,我只須要考慮我面前的這座金礦就能夠了,對於我面前的這座金礦不外乎僅有兩種選擇,要麼挖,要麼不挖,對吧?」

 

       「固然,固然」大臣們回答倒。

      

       國王繼續說道:「若是我挖取第9座金礦的話那麼我如今就能得到8888個金子,而我將用去1500我的,那麼我還剩下8500我的。我親愛的左部下,若是你告訴我當我把全部剩下的8500我的和全部剩下的其它金礦都交給你去開採你最多能給我挖出多少金子的話,那麼我不就知道了在第9個金礦必定開採的狀況下所能獲得的最大金幣數嗎?」

      

       國王的左部下聽後回答道:「國王陛下,您的意思是若是我能用8500我的在其它金礦最多開採出x個金幣的話,那您一共就可以得到 x + 8888個金子,對嗎?」

      

       「是啊,是啊……若是第9座金礦必定開採的話……」大臣們點頭說到。

      

       國王笑着繼續對着他的右部下說到:「親愛的右部下,也許我並不打算開採這第9座金礦,那麼我依然擁有10000我的,若是我把這10000我的和剩下的金礦都給你的話,你最多能給我挖出多少個金子呢?」

      

       國王的右部下聰明地說道:「尊敬的國王陛下,我明白您的意思了,若是我回答最多能購開採出y個金幣的話,那您就能夠在y和x+8888之間選擇一個較大者,而這個較大者就是最終咱們能得到的最大金幣數,您看我這樣理解對嗎?」

      

 

       國王笑得更燦爛了,問他的左部下:「那麼親愛的左部下,我給你8500我的和其他金礦的話你能告訴我最多能挖出多少金子嗎?」

 

       「請您放心,這個問題難不倒我」。左部下向國王打包票說到。

 

       國王高興地繼續問他的右部下:「那右部下你呢,若是我給你10000我的和其他金礦的話你能告訴我最多能挖出多少金子嗎?」

 

       「固然能了!交給我吧!」右部下同左部下同樣自信地回答道。

 

       「那就拜託給大家兩位了,如今我要回到我那溫馨的王宮裏去享受了,我期待着大家的答覆。」國王說完就開始動身回去等消息了,他是多麼地相信他的兩個大臣可以給他一個準確的答覆,由於國王其實知道他的兩位大臣要比他聰明得多。

 

       故事發展到這裏,你是否在想國王的這兩個大臣又是如何找到讓國王滿意的答案的呢?他們爲何可以如此自信呢?事實上他們的確比國王要聰明一些,由於他們從國王的身上學到了一點,就是這一點讓他們充滿了自信。

 

       國王走後,國王的左、右部下來到了第8座金礦,早已在那裏等待他們的金礦勘測兵向兩位大臣報道:「聰明的兩位大臣,您們好,第8座金礦須要1000我的才能開採,能夠得到7000個金子」。

 

       由於國王僅給他的左部下8500我的,因此國王的左部下叫來了兩我的,對着其中一我的問到:「若是我給你7500我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」

 

       而後國王的左部下繼續問另外一我的:「若是我給你8500我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」

 

       國王的左部下在內心想着:「若是他們倆都能回答個人問題的話,那國王交給個人問題不就解決了嗎?哈哈哈!」

 

       由於國王給了他的右部下10000我的,因此國王的右部下一樣也叫來了兩我的,對着其中一我的問:「若是我給你9000我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」

 

       而後國王的右部下繼續問他叫來的另外一我的:「若是我給你10000我的和除了第八、第9的其它全部金礦的話,你能告訴我你最多能挖出多少金子嗎?」

 

       此時,國王的右部下同左部下同樣,他們都在爲本身如此聰明而感到知足。

      

       固然,這四個被叫來的人一樣自信地回答沒有問題,由於他們一樣地從這兩個大臣身上學到了相同的一點,而兩位自認爲本身同樣很聰明的大臣得意地笑着回到了他們的府邸,等着別人回答他們提出來的問題,如今你知道了這兩個大臣是如何解決國王交待給他們的問題了嗎?

 

       那麼你認爲被大臣叫去的那四我的又是怎麼完成大臣交給他們的問題的呢?答案固然是他們找到了另外八我的!

 

       沒用多少功夫,這個問題已經在全國傳開了,更多人的人找到了更更多的人來解決這個問題,而有些人卻不須要去另外找兩我的幫他,哪些人不須要別人的幫助就能夠回答他們的問題呢?

 

       很明顯,當被問到給你z我的和僅有第0座金礦時最多能挖出多少金子時,就不須要別人的幫助,由於你知道,若是z大於等於挖取第0座金礦所須要的人數的話,那麼挖出來的最多金子數就是第0座金礦可以挖出來的金子數,若是這z我的不夠開採第0座金礦,那麼能挖出來的最多金子數就是0,由於這惟一的金礦不夠人力去開採。讓咱們爲這些不須要別人的幫助就能夠準確地得出答案的人們鼓掌吧,這就是傳說中的底層勞動人民!

 

       故事講到這裏先暫停一下,咱們如今從新來分析一下這個故事,讓咱們對動態規劃有個理性認識。

 

       子問題:

       國王須要根據兩個大臣的答案以及第9座金礦的信息才能判斷出最多可以開採出多少金子。爲了解決本身面臨的問題,他須要給別人製造另外兩個問題,這兩個問題就是子問題。

 

       思考動態規劃的第一點——最優子結構

       國王相信,只要他的兩個大臣可以回答出正確的答案(對於考慮可以開採出的金子數,最多的也就是最優的同時也就是正確的),再加上他的聰明的判斷就必定能獲得最終的正確答案。咱們把這種子問題最優時母問題經過優化選擇後必定最優的狀況叫作「最優子結構」。

 

       思考動態規劃的第二點----子問題重疊

       實 際上國王也好,大臣也好,全部人面對的都是一樣的問題,即給你必定數量的人,給你必定數量的金礦,讓你求出可以開採出來的最多金子數。咱們把這種母問題與 子問題本質上是同一個問題的狀況稱爲「子問題重疊」。然而問題中出現的不一樣點每每就是被子問題之間傳遞的參數,好比這裏的人數和金礦數。

      

       思考動態規劃的第三點----邊界

       想一想若是不存在前面咱們提到的那些底層勞動者的話這個問題能解決嗎?永遠都不可能!咱們把這種子問題在必定時候就再也不須要提出子子問題的狀況叫作邊界,沒有邊界就會出現死循環。

 

       思考動態規劃的第四點----子問題獨立

       要 知道,當國王的兩個大臣在思考他們本身的問題時他們是不會關心對方是如何計算怎樣開採金礦的,由於他們知道,國王只會選擇兩我的中的一個做爲最後方案,另 一我的的方案並不會獲得實施,所以一我的的決定對另外一我的的決定是沒有影響的。咱們把這種一個母問題在對子問題選擇時,當前被選擇的子問題兩兩互不影響的 狀況叫作「子問題獨立」。

 

 

       這就是動態規劃,具備「最優子結構」、「子問題重疊」、「邊界」和「子問題獨立」,當你發現你正在思考的問題具有這四個性質的話,那麼恭喜你,你基本上已經找到了動態規劃的方法。

 

       有了上面的這幾點,咱們就能夠寫出動態規劃的轉移方程式,如今咱們來寫出對應這個問題的方程式,若是用gold[mineNum]表示第mineNum個金礦可以挖出的金子數,用peopleNeeded[mineNum]表示挖第mineNum個金礦須要的人數,用函數f(people,mineNum)表示當有people我的和編號爲0、一、二、三、……、mineNum的金礦時可以獲得的最大金子數的話,f(people,mineNum)等於什麼呢?或者說f(people,mineNum)的轉移方程是怎樣的呢?

 

       答案是:

 當mineNum = 0且people >= peopleNeeded[mineNum]時 f(people,mineNum) = gold[mineNum]

       當mineNum = 0且people < peopleNeeded[mineNum]時 f(people,mineNum) = 0

       當mineNum != 0時 f(people,mineNum) = f(people-peopleNeeded[mineNum], mineNum-1) + gold[mineNum]與f(people, mineNum-1)中的較大者,前兩個式子對應動態規劃的「邊界」,後一個式子對應動態規劃的「最優子結構」請讀者弄明白後再繼續往下看。

動態規劃的優勢


 

      如今我假設讀者你已經搞清楚了爲何動態規劃是正確的方法,可是咱們爲何須要使用動態規劃呢?請先繼續欣賞這個故事:

       國王得知他的兩個手下使用了和他相同的方法去解決交代給他們的問題後,不但沒有認爲他的兩個大臣在偷懶,反而很高興,由於他知道,他的大臣必然會找更多的人 一塊兒解決這個問題,而更多的人會找更更多的人,這樣他這個聰明的方法就會在不經意間流傳開來,而全國人民都會知道這個聰明的方法是他們偉大的國王想出來 的,你說國王能不高興嗎?

 

       可是國王也有一些擔心,由於他實在不知道這個「工程」要動用到多少人來完成,若是幫助他解決這個問題的人太多的話那麼就太勞民傷財了。「會不會影響到今年的收成呢?」國王在內心想着這個問題,因而他請來了整個國家裏惟一的兩個數學天才,一個叫作小天,另外一個叫作小才。

 

       國王問小天:「小天啊,我發覺這個問題有點嚴重,我知道其實這能夠簡單的當作一個組合問題,也就是從十個金礦中選取若干個金礦進行開採,看看哪一種組合獲得的金子最多,也許用組合方法會更好一些。你能告訴我一共有多少種組合狀況嗎?」

 

       「國王陛下,若是用組合方法的話一共要考慮2的10次方種狀況,也就是1024種狀況。」小天思考了一會回答到。

 

       「嗯……,若是每一種狀況我交給一我的去計算能獲得的金子數的話,那我也要1024我的,其實仍是挺多的。」國王好像再次感受到了本身的方法是正確的。

 

       國王心理期待着小纔可以給它一個更好的答案,問到:「小才啊,那麼你能告訴我用個人那個方法總共須要多少人嗎?其實,我也計算過,好像須要的人數是1+2+4+8+16+32+64+……,畢竟每個人的確都須要找另外兩我的來幫助他們……」

 

       不辜負國王的期待,小才微笑着說到:「親愛的國王陛下,其實咱們並不須要那麼多人,由於有不少問題實際上是相同的,而咱們只須要爲每個不一樣的問題使用一我的力即可。」

 

       國王高興的問到:「此話如何講?」

 

       「打個比方,若是有一我的須要知道1000我的和3個金礦能夠開採出多少金子,同時另外一我的也須要知道1000我的和3個金礦能夠開採出多少金子的話,那麼他們能夠去詢問相同的一我的,而不用各自找不一樣的人浪費人力了。」

      

       國王思考着說到:「嗯,頗有道理,若是問題是同樣的話那麼就不須要去詢問兩個不一樣的人了,也就是說一個不一樣的問題僅須要一我的力,那麼一共有多少個不一樣的問題呢?」   

 

       「由於每一個問題的人數能夠從0取到10000,而金礦數能夠從0取到10,因此最多大約有10000 * 10 等於100000個不一樣的問題。」 小才一邊算着一邊回答。

 

       「什麼?十萬個問題?十萬我的力?」國王有點失望。

 

       「請國王放心,事實上咱們須要的人力遠遠小於這個數的,由於不是每個問題都會遇到,也許咱們僅須要1、兩百我的力就能夠解決這個問題了,這主要和各個金礦所須要的人數有關。」 小才馬上回答到。

 

       故事的最後,天然是國王再一次向他的臣民們證實了他是這個國家裏最聰明的人,如今咱們經過故事的第二部分來考慮動態規劃的另外兩個思考點。

 

       思考動態規劃的第五點----作備忘錄

       正如上面所說的同樣,當咱們遇到相同的問題時,咱們能夠問同一我的。講的通俗一點就是,咱們能夠把問題的解放在一個變量中,若是再次遇到這個問題就直接從變量中得到答案,所以每個問題僅會計算一遍,若是不作備忘的話,動態規劃就沒有任何優點可言了。             

 

       思考動態規劃的第六點----時間分析

       正如上面所說,若是咱們用窮舉的方法,至少須要2^n個常數時間,由於總共有2^n種狀況須要考慮,若是在揹包問題中,包的容量爲1000,物品數爲100,那麼須要考慮2^100種狀況,這個數大約爲10的30次方。

 

       而若是用動態規劃,最多大概只有1000*100 = 100000個不一樣的問題,這和10的30次方比起來優點是很明顯的。而實際狀況並不會出現那麼多不一樣的問題,好比在金礦模型中,若是全部的金礦所需人口都是1000我的,那麼問題總數大約只有100個。

 

       非正式地,咱們能夠很容易獲得動態規劃所需時間,若是共有questionCount個相同的子問題,而每個問題須要面對chooseCount種選擇時,咱們所需時間就爲questionCount * chooseCount個常數。在金礦模型中,子問題最多有大概people * n 個(其中people是用於開採金礦的總人數,n是金礦的總數),所以questionCount = people * n,而就像國王須要考慮是採用左部下的結果仍是採用右部下的結果同樣,每一個問題面對兩個選擇,所以chooseCount = 2,因此程序運行時間爲 T = O(questionCount * chooseCount) =O(people * n),別忘了實際上須要的時間小於這個值,根據所遇到的具體狀況有所不一樣。

 

       這就是動態規劃的魔力,它減小了大量的計算,所以咱們須要動態規劃!

動態規劃的思考角度


 

       那麼什麼是動態規劃呢?我我的以爲,若是一個解決問題的方法知足上面六個思考點中的前四個,那麼這個方法就屬於動態規劃。而在思考動態規劃方法時,後兩點一樣也是須要考慮的。

       面對問題要尋找動態規劃的方法,首先要清楚一點,動態規劃不是算法,它是一種方法,它是在一件事情發生的過程當中尋找最優值的方法,所以,咱們須要對這件事情所發生的過程進行考慮。而一般咱們從過程的最後一步開始考慮,而不是先考慮過程的開始。

       打個比方,上面的挖金礦問題,咱們能夠認爲整個開採過程是從西至東進行開採的(也就是從第0座開始),那麼總有面對最後一座金礦的時候(第9座),對這座金礦不外乎兩個選擇,開採與不開採,在最後一步肯定時再去肯定倒數第二步,直到考慮第0座金礦(過程的開始)。

       而過程的開始,也就是考慮的最後一步,就是邊界。

       所以在遇到一個問題想用動態規劃的方法去解決時,不妨先思考一下這個過程是怎樣的,而後考慮過程的最後一步是如何選擇的,一般咱們須要本身去構造一個過程,好比後面的練習。

 總結


 

       那麼遇到問題如何用動態規劃去解決呢?根據上面的分析咱們能夠按照下面的步驟去考慮:

       一、構造問題所對應的過程。

       二、思考過程的最後一個步驟,看看有哪些選擇狀況。

       三、找到最後一步的子問題,確保符合「子問題重疊」,把子問題中不相同的地方設置爲參數。

       四、使得子問題符合「最優子結構」。

       五、找到邊界,考慮邊界的各類處理方式。

       六、確保知足「子問題獨立」,通常而言,若是咱們是在多個子問題中選擇一個做爲實施方案,而不會同時實施多個方案,那麼子問題就是獨立的。

       七、考慮如何作備忘錄。

       八、分析所需時間是否知足要求。

       九、寫出轉移方程式。

相關文章
相關標籤/搜索