非常特別的一個動態規划新手教程


非常特別的一個動態規划新手教程
今天在網上看到一個講動態規划的文章,是以01背包為例的,這文章和書上的解說很不一樣,令我眼前一亮,於是轉載一下下~~~
(說明一下,本人很痛恨教材公式定理漫天飛,實際的解說卻講得很枯澀難懂,這樣的中國式的教育已經延綿了幾千年了,如今中國的教材還是這個樣子,講清楚些明確些就那么難么?高中有個老師講的一句話一直認為很有道理:“教得會天才不是真本事,能把博士生的東西講到小學生都會用那才是真水平。”)
附上原文地址:
http://www.cnblogs.com/sdjl/articles/1274312.html


通過金礦模型介紹動態規划

         對於動態規划,每一個剛接觸的人都須要一段時間來理解,特別是第一次接觸的時候總是想不通為什么這樣的方法可行,這篇文章就是為了幫助大家理解動態規划,並通過解說主要的01背包問題來引導讀者怎樣去思考動態規划。本文力求通俗易懂,無異性,不讓讀者感到迷惑,引導讀者去思考,所以假設你在閱讀中發現有不通順的地方,讓你產生錯誤理解的地方,讓你難得讀懂的地方,請跟貼指出,謝謝! 

----第一節----初識動態規划--------

       經典的01背包問題是這種:
       有一個包和n個物品,包的容量為m,每一個物品都有各自的體積和價值,問當從這n個物品中選擇多個物品放在包里而物品體積總數不超過包的容量m時,可以得到的最大價值是多少?[對於每一個物品不可以取多次,最多僅僅能取一次,之所以叫做01背包,0表示不取,1表示取]

       為了用一種生動又更形象的方式來解說此題,我把此題用還有一種方式來描寫敘述,例如以下:
      
       有一個國家,全部的國民都很老實憨厚,某天他們在自己的國家發現了十座金礦,而且這十座金礦在地圖上排成一條直線,國王知道這個消息后很高興,他希望可以把這些金子都挖出來造福國民,首先他把這些金礦依照在地圖上的位置從西至東進行編號,依次為0、1、2、3、4、5、6、7、8、9,然后他命令他的手下去對每一座金礦進行勘測,以便知道挖取每一座金礦須要多少人力以及每座金礦可以挖出多少金子,然后動員國民都來挖金子。

       題目補充1:挖每一座金礦須要的人數是固定的,多一個人少一個人都不行。國王知道每一個金礦各須要多少人手,金礦i須要的人數為peopleNeeded
       題目補充2:每一座金礦所挖出來的金子數是固定的,當第i座金礦有peopleNeeded 人去挖的話,就一定能恰好挖出gold 個金子。否則一個金子都挖不出來。
       題目補充3:開採一座金礦的人完畢開採工作后,他們不會再次去開採其他金礦,因此一個人最多僅僅能使用一次。
       題目補充4:國王在全國范圍內僅招募到了10000名願意為了國家去挖金子的人,因此這些人可能不夠把全部的金子都挖出來,可是國王希望挖到的金子越多越好。
       題目補充5:這個國家的每個人都非常老實(包含國王),不會私吞不論什么金子,也不會弄虛作假,不會說謊話。
       題目補充6:有非常多人拿到這個題后的第一反應就是對每個金礦求出平均每個人能挖出多少金子,然后從高到低進行選擇,這里要強調這樣的方法是錯的,假設你也是這樣想的,請考慮背包模型,當有一個背包的容量為10,共同擁有3個物品,體積各自是3、3、5,價值各自是6、6、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個人和除了第8、第9的其他全部金礦的話,你能告訴我你最多能挖出多少金子嗎?”

       然后國王的左部下繼續問還有一個人:“假設我給你8500個人和除了第8、第9的其他全部金礦的話,你能告訴我你最多能挖出多少金子嗎?”

       國王的左部下在心里想着:“假設他們倆都能回答我的問題的話,那國王交給我的問題不就攻克了嗎?哈哈哈!”

       由於國王給了他的右部下10000個人,所以國王的右部下相同也叫來了兩個人,對着當中一個人問:“假設我給你9000個人和除了第8、第9的其他全部金礦的話,你能告訴我你最多能挖出多少金子嗎?”

       然后國王的右部下繼續問他叫來的還有一個人:“假設我給你10000個人和除了第8、第9的其他全部金礦的話,你能告訴我你最多能挖出多少金子嗎?”

       此時,國王的右部下同左部下一樣,他們都在為自己如此聰明而感到滿足。
      
       當然,這四個被叫來的人相同自信地回答沒有問題,由於他們相同地從這兩個大臣身上學到了相同的一點,而兩位自覺得自己一樣非常聰明的大臣得意地笑着回到了他們的府邸,等着別人回答他們提出來的問題,如今你知道了這兩個大臣是怎樣解決國王交待給他們的問題了嗎?

       那么你覺得被大臣叫去的那四個人又是怎么完畢大臣交給他們的問題的呢?答案當然是他們找到了另外八個人!

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

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

       故事說到這里先暫停一下,我們如今又一次來分析一下這個故事,讓我們對動態規划有個理性認識。

       子問題:
       國王須要依據兩個大臣的答案以及第9座金礦的信息才干推斷出最多可以開採出多少金子。為了解決自己面臨的問題,他須要給別人制造另外兩個問題,這兩個問題就是子問題。

       思考動態規划的第一點----最優子結構:
       國王相信,僅僅要他的兩個大臣可以回答出正確的答案(對於考慮可以開採出的金子數,最多的也就是最優的同一時候也就是正確的),再加上他的聰明的推斷就一定能得到終於的正確答案。我們把這樣的子問題最優時母問題通過優化選擇后一定最優的情況叫做“最優子結構”。

       思考動態規划的第二點----子問題重疊:
       實際上國王也好,大臣也好,全部人面對的都是相同的問題,即給你一定數量的人,給你一定數量的金礦,讓你求出可以開採出來的最多金子數。我們把這樣的母問題與子問題本質上是同一個問題的情況稱為“子問題重疊”。然而問題中出現的不同點往往就是被子問題之間傳遞的參數,比方這里的人數和金礦數。
      
       思考動態規划的第三點----邊界:
       想想假設不存在前面我們提到的那些底層勞動者的話這個問題能解決嗎?永遠都不可能!我們把這樣的子問題在一定時候就不再須要提出子子問題的情況叫做邊界,沒有邊界就會出現死循環。

       思考動態規划的第四點----子問題獨立:
       要知道,當國王的兩個大臣在思考他們自己的問題時他們是不會關心對方是如何計算如何開採金礦的,由於他們知道,國王僅僅會選擇兩個人中的一個作為最后方案,還有一個人的方案並不會得到實施,因此一個人的決定對還有一個人的決定是沒有影響的。我們把這樣的一個母問題在對子問題選擇時,當前被選擇的子問題兩兩互不影響的情況叫做“子問題獨立”。


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

       有了上面的這幾點,我們就行寫出動態規划的轉移方程式,如今我們來寫出相應這個問題的方程式,假設用gold[mineNum]表示第mineNum個金礦可以挖出的金子數,用peopleNeeded[mineNum]表示挖第mineNum個金礦須要的人數,用函數f(people,mineNum)表示當有people個人和編號為0、1、2、3、……、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)中的較大者,前兩個式子相應動態規划的“邊界”,后一個式子相應動態規划的“最優子結構”請讀者弄明確后再繼續往下看。






----第二節----動態規划的長處--------
      
       如今我如果讀者你已經搞清楚了為什么動態規划是正確的方法,可是我們為什么須要使用動態規划呢?請先繼續贊賞這個故事:

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

       可是國王也有一些擔憂,由於他實在不知道這個“project”要動用到多少人來完畢,假設幫助他解決問題的人太多的話那么就太勞民傷財了。“會不會影響到今年的收成呢?”國王在心里想着這個問題,於是他請來了整個國家里唯一的兩個數學天才,一個叫做小天,還有一個叫做小才。

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

       “國王陛下,假設用組合方法的話一共要考慮2的10次方種情況,也就是1024種情況。”小天思考了一會回答到。

       “嗯……,假設每一種情況我交給一個人去計算能得到的金子數的話,那我也要1024個人,事實上還是挺多的。”國王好像再次感覺到了自己的方法是正確的。

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

       不辜負國王的期待,小才微笑着說到:“親愛的國王陛下,事實上我們並不須要那么多人,由於有非常多問題事實上是同樣的,而我們僅僅須要為每個不同的問題使用一個人力便可。”

       國王高興的問到:“此話怎樣講?”

       “打個例如,假設有一個人須要知道1000個人和3個金礦能夠開採出多少金子,同一時候還有一個人也須要知道1000個人和3個金礦能夠開採出多少金子的話,那么他們能夠去詢問同樣的一個人,而不用各自找不同的人浪費人力了。”
      
       國王思考着說到:“嗯,非常有道理,假設問題是一樣的話那么就不須要去詢問兩個不同的人了,也就是說一個不同的問題僅須要一個人力,那么一共同擁有多少個不同的問題呢?”   

       “由於每一個問題的人數能夠從0取到10000,而金礦數能夠從0取到10,所以最多大約有10000 * 10 等於100000個不同的問題。” 小才一邊算着一邊回答。

       “什么?十萬個問題?十萬個人力?”國王有點失望。

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

       故事的最后,自然是國王再一次向他的臣民們證明了他是這個國家里最聰明的人,如今我們通過故事的第二部分來考慮動態規划的另外兩個思考點。

       思考動態規划的第五點----做備忘錄:
       正如上面所說的一樣,當我們遇到同樣的問題時,我們能夠問同一個人。講的通俗一點就是,我們能夠把問題的解放在一個變量中,假設再次遇到這個問題就直接從變量中獲得答案,因此每個問題僅會計算一遍,假設不做備忘的話,動態規划就沒有不論什么優勢可言了。             

       思考動態規划的第六點----時間分析:
       正如上面所說,假設我們用窮舉的方法,至少須要2^n個常數時間,由於總共同擁有2^n種情況須要考慮,假設在背包問題中,包的容量為1000,物品數為100,那么須要考慮2^100種情況,這個數大約為10的30次方。

       而假設用動態規划,最多大概僅僅有1000*100 = 100000個不同的問題,這和10的30次方比起來優勢是非常明顯的。而實際情況並不會出現那么多不同的問題,比方在金礦模型中,假設全部的金礦所需人口都是1000個人,那么問題總數大約僅僅有100個。

       非正式地,我們能夠非常easy得到動態規划所需時間,假設共同擁有questionCount個同樣的子問題,而每個問題須要面對chooseCount種選擇時,我們所需時間就為questionCount * chooseCount個常數。在金礦模型中,子問題最多有大概people * n 個(當中people是用於開採金礦的總人數,n是金礦的總數),因此questionCount = people * n,而就像國王須要考慮是採用左部下的結果還是採用右部下的結果一樣,每個問題面對兩個選擇,因此chooseCount = 2,所以程序執行時間為 T = O(questionCount * chooseCount) =O(people * n),別忘了實際上須要的時間小於這個值,依據所遇到的詳細情況有所不同。

       這就是動態規划的魔力,它降低了大量的計算,因此我們須要動態規划!

                    


      

----第三節----動態規划的思考角度----------
      
       那么什么是動態規划呢?我個人認為,假設一個解決這個問題的方法滿足上面六個思考點中的前四個,那么這種方法就屬於動態規划。而在思考動態規划方法時,后兩點相同也是須要考慮的。

       面對問題要尋找動態規划的方法,首先要清楚一點,動態規划不是算法,它是一種方法,它是在一件事情發生的過程中尋找最優值的方法,因此,我們須要對這件事情所發生的過程進行考慮。而通常我們從過程的最后一步開始考慮,而不是先考慮過程的開始。

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

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

       因此在遇到一個問題想用動態規划的方法去解決時,最好還是先思考一下這個過程是如何的,然后考慮過程的最后一步是如何選擇的,通常我們須要自己去構造一個過程,比方后面的練習。







----第四節----總結-------

       那么遇到問題怎樣用動態規划去解決呢?依據上面的分析我們能夠依照以下的步驟去考慮:

       1、構造問題所相應的過程。
       2、思考過程的最后一個步驟,看看有哪些選擇情況。
       3、找到最后一步的子問題,確保符合“子問題重疊”,把子問題中不同樣的地方設置為參數。
       4、使得子問題符合“最優子結構”。
       5、找到邊界,考慮邊界的各種處理方式。
       6、確保滿足“子問題獨立”,一般而言,假設我們是在多個子問題中選擇一個作為實施方案,而不會同一時候實施多個方案,那么子問題就是獨立的。
       7、考慮怎樣做備忘錄。
       8、分析所需時間是否滿足要求。
       9、寫出轉移方程式。
      




----第五節----練習-------

       題目一:買書
       有一書店引進了一套書,共同擁有3卷,每卷書定價是60元,書店為了搞促銷,推出一個活動,活動例如以下:
      
       假設單獨購買當中一卷,那么能夠打9.5折。
       假設同一時候購買兩卷不同的,那么能夠打9折。
       假設同一時候購買三卷不同的,那么能夠打8.5折。
      
       假設小明希望購買第1卷x本,第2卷y本,第3卷z本,那么至少須要多少錢呢?(x、y、z為三個已知整數)。

       當然,這道題全然能夠不用動態規划來解,可是如今我們是要學習動態規划,因此請想想怎樣用動態規划來做?

       答案:

       1、過程為一次一次的購買,每一次購買或許僅僅買一本(這有三種方案),或者買兩本(這也有三種方案),或者三本一起買(這有一種方案),最后直到買全然部須要的書。
       2、最后一步我必定會在7種購買方案中選擇一種,因此我要在7種購買方案中選擇一個最佳情況。
       3、子問題是,我選擇了某個方案后,怎樣使得購買剩余的書能用最少的錢?而且這個選擇不會使得剩余的書為負數。母問題和子問題都是給定三卷書的購買量,求最少須要用的錢,所以有“子問題重疊”,問題中三個購買量設置為參數,分別為i、j、k。
       4、的確符合。
       5、邊界是一次購買就能夠買全然部的書,處理方式請讀者自己考慮。
       6、每次選擇最多有7種方案,而且不會同一時候實施當中多種,因此方案的選擇互不影響,所以有“子問題獨立”。
       7、我能夠用minMoney [j][k]來保存購買第1卷i本,第2卷j本,第3卷k本時所需的最少金錢。
       8、共同擁有x * y * z 個問題,每一個問題面對7種選擇,時間為:O( x * y * z * 7) =   O( x * y * z )。
       9、用函數MinMoney(i,j,k)來表示購買第1卷i本,第2卷j本,第3卷k本時所需的最少金錢,那么有:
               MinMoney(i,j,k)=min(s1,s2,s3,s4,s5,s6,s7),當中s1,s2,s3,s4,s5,s6,s7分別為相應的7種方案使用的最少金錢:
               s1 = 60 * 0.95 + MinMoney(i-1,j,k)
               s2 = 60 * 0.95 + MinMoney(i,j-1,k)
               s3 = 60 * 0.95 + MinMoney(i,j,k-1)
               s4 = (60 + 60) * 0.9 + MinMoney(i-1,j-1,k)
               s5 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)
               s6 = (60 + 60) * 0.9 + MinMoney(i-1,j,k-1)
               s7 = (60 + 60 + 60) * 0.85 + MinMoney(i-1,j-1,k-1)

      
      


----第六節----代碼參考------


       以下提供金礦問題的程序源碼幫助讀者理解,並提供測試數據給大家練習。

       輸入文件名稱為“beibao.in”,由於這個問題實際上就是背包問題,所以測試數據文件名稱就保留原名吧。
       輸入文件第一行有兩個數,第一個是國王可用用來開採金礦的總人數,第二個是總共發現的金礦數。
       輸入文件的第2至n+1行每行有兩個數,第i行的兩個數分別表示第i-1個金礦須要的人數和能夠得到的金子數。

       輸出文件僅一個整數,表示可以得到的最大金子數。

       輸入例子:
       100 5
       77 92
       22 22
       29 87
       50 46
       99 90

       輸出例子:
       133


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM