动态规划与排列组合
轉:動態規劃與排列組合
2008年11月22日 星期六 下午 09:54
最近在學習動態規劃,感覺很難徹底理解,也許這真的不是一時半會能會的。
有人說:DP非一日之功。我想也是,先轉個帖子做啟發吧。
<動態規劃與排列組合>
原作者不詳
?? 1. 像所有的新手一樣,對一種算法思想的理解需要經歷從膚淺(流于表面形式)到逐漸觸摸到本質的過程。為什么說"逐漸"觸摸到本質,是因為很多時候你并不確定一個解釋是不是最本質的,有時候會有好幾個等價的解釋,各自在不同的場景下具有啟發。
?? 2. 比如對動態規劃(DP)的理解,一開始我理解為"遞推",但實際上這是最膚淺的理解,對于如何在特定的問題中找到遞推關系毫無幫助和啟發。換言之,這只是一個描述性的總結,而不是一個建設性的總結,不含方法論。
?? 3. 做(看)了一些題目之后我開始總結關于"How"的方法,怎樣尋找到遞推關系(遞推關系蘊含著子問題)。得到一個簡單的觀察,如果一個問題里面含有n這個變量,考慮把n變成n-1的情況。
?? 4. 當然,這個方法是非常特殊(狹窄)的。實際上更具一般性的方法是不僅可以從n-1后面切一刀,還可以在任意地方切(譬如切成兩個n/2規模的),以任意標準切(比如像快排那樣的)。所有考慮各種切法中哪種能夠最有利于建立子問題是有幫助的。
?? 5. 這個我姑且把它叫做通過直接切分問題來尋找子問題。
?? 6. 可是。這還是特殊了。因為顯然這個方法不能解決所有的DP問題,連"多數"DP問題都未必能解決。譬如大家熟悉的"最大(小)和連續子序列"問題,就難以通過這種方法求解(可行,但思維難度較大。);再譬如上次Lee給的敲石頭問題,以及DD出的不聽話的機器人問題。因此,在知道了這幾個問題的解法之后我繼續思考這些解法里面是不是蘊含著更具一般性的解題方法。
?? 7. 看起來我找到了一個,就是分類討論法,具體做法是:首先,寫出可行解的一般形式,譬如a1, a2, ..., an。然后對其中的某個不確定的ai進行討論。譬如旅行商問題里面對"下一個城市"進行討論。當不確定時,討論。討論的每個分支都帶來了進一步的確定性,從而將問題轉化成一個子問題。
?? 8. 然后Lee提到,這個方法是自頂向下的,有時候不適于思考,譬如對敲石頭問題。并提到一種自底向上,著重于構造"狀態"的外推法。
?? 9. 于是,到目前為止,DP的一般性啟發式思考方法就已經有了三種:
10. 1. 通過直接對問題切分來探索子問題。
11. 2. 通過對可行解的分類討論來探索子問題。
12. 3. 通過建立"狀態"來自底向上地推導最終解。
13. 可是,我心里還是不踏實,因為"離散"的知識是極其不利于記憶的,需要更多的練習才能在運用的時候"不由自主"的聯想起來,而且也容易遺忘。每次做DP題的時候,我都得把好幾種指導性的思路一一費勁從腦袋里翻出來,然后嘗試。實在很不爽。雖然DP題也許并沒有萬用的解題手法,但我還是希望能夠盡量提取出不同手法之間的本質聯系,如果能夠將一組看上去離散的知識點統一在一個更具一般性,更本質的知識點下面,我們的知識樹就多出了一個根節點,于是下次提取的時候只要提取出那個根節點,下面的幾個子節點就會一一乖乖閃現,極大的降低記憶的復雜性。
14. 那么,上面三種手法的本質聯系到底是什么呢?有一次在路上走的時候我想出了一種解釋。它也許不是最本質的,也許大牛們早就想到過,但是我覺得至少有兩個好處:
15. 1. 它涵蓋以上三種手法,通過增加一個根節點,減少知識的記憶復雜性和易提取性。
16. 2. 歸納抽象的過程本身就是一種鍛煉,鍛煉的是歸納抽象的能力。
17. 這個解釋就是:大多DP問題的可行解的形式是一個排列組合(典型的——旅行商問題、最短路徑問題)。大家都知道,窮舉一個規模為N的排列組合復雜性是 a^n的,也就是"組合復雜性"。而求解DP問題的核心步驟:發現“子問題”,這個“子問題”實際上就是對應最終解的那個排列組合的某個"子排列組合" (某種子集);而這里的“子排列組合”的數目則往往是多項式的(silwile,XuYou,g9指出并非總是如此),這就是為什么一個組合復雜性的窮舉問題可以DP優化為多項式復雜度的問題。將重復出現的子排列組合對應的子問題的解緩存起來,就是DP 的緩存優化了。
18. 此外,這一解釋也提供了如何探索子問題的一個通用方案:尋找形式相同的子排列組合。還是拿最大和連續子序列說事,其解的形式是:A[i], A[i+1], .., A[j-1], A[j],其中i,j不確定。那么如何得到形式相同的子組合呢?首先討論A[j],為什么要討論,因為只要A[j]不確定,A[j-1]就不確定,就拿不出子組合來。對于一個確定的A[j0],解的可能性為A[i], A[i+1], .., A[j0-1], A[j0]。其最優解依賴于子組合A[i], A[i+1], .., A[j0-1],到這里子問題就不請子現了:A[i], A[i+1], .., A[j0-1], A[j0]和A[i], A[i+1], .., A[j0-1]的形式相同,意味著它們是同一個問題的不同階表示。
19. 當然,由于這個指導思想一般性大了點,所以實際問題中往往沒有前面提到的三種手法尋找方案來得快——眾所周知的是,越特定的手法解題面雖然越窄,但如果題目對口了解決起來也越快。但它至少有兩個好處(前面說過了)。所以考慮一般性和特殊性的手法都是有幫助的。
20. 類似的,敲石頭問題也可以通過這種手法來探索子問題。至少目前我做過的DP題似乎都可以借助這種手法來探索,當然,剛才說過了,未必是最快的,所以也許可以考慮用來做后備方案,當其它方案沒有頭緒的時候試試。
21. 我的解題經驗還很有限,所以不清楚這個手法的覆蓋范圍有多廣。實際上一個更廣的領域是"組合優化"。更上面提到的很像。但針對的問題就不僅止于DP了。
22. 參考
23. 1. 《Introduction To Algorithms》的DP章節。
24. 2. 《Algorithms》的DP章節。
25. 3. 《Algorithm Design Manual》的DP章節。
26. 4. DD的《背包問題九講》
27. 5. wikipedia
28. 6. 網絡搜索出來的一堆題目和講解。(如《動態規劃經典題集》)
29. 7. TopLanguage上的帖子:矩陣也瘋狂、DPDP、DPDP(二)
回帖:
#Eastsun 發表于2008-06-05 20:21:14 IP: 202.113.19.*
??? 我對DP的理解就是:用空間換時間
??? 將計算過程中可能重復使用的結果保存起來避免重復計算就是DP
#kusk 發表于2008-06-05 21:22:11 IP: 123.112.70.*
??? 同意Eastsun的觀點。“問題可分解為相互重疊子問題”也是一般對DP的定義吧。
??? 基本上關于離散數據處理方面的難題都可以歸結為組合問題。因為基本上能見得到的有意義的題目,通常只有涉及到組合才會帶來爆炸式的復雜度,否則一般都只能是多項式級的。
??? DP適用于組合問題,也適用于非組合問題。但對于非組合問題,因為本身就是多項式的問題,所以我們通常以直觀的方式去理解它,直接就把它作為問題的解法,不把它叫DP了……比如求斐波那契數列,如果你寫作:
??? int f(int n)
??? {
??? if (n == 0) return 0;
??? else if (n == 1) return 1;
??? else return f(n - 1) + f(n - 2);
??? }
??? 那么這就是遞歸。但因為計算f(n-1)和f(n-2)時包含了重復計算,所以滿足了DP的條件,使用DP求解,把重復的部分緩存:
??? int f_DP(int n)
??? {
??? int cache[MAX_SIZE] = { -1, -1, ... };
??? if (cache[n] != -1) return cache[n];
??? else
??? {
??? cache[n] = f(n);
??? return cache[n];
??? }
??? }
??? 此即“自頂向下”的DP。因為計算順序仍然是從結果向下推。
??? 另外,由于是尾遞歸,因此容易改寫成"自底向上"的DP,即從邊界值開始一層一層往目標推:
??? int f_DP_bu(n)
??? {
??? int a = 0, b = 1;
??? while (n-- > 0)
??? {
??? b = a + b;
??? a = b - a;
??? }
??? return a;
??? }
??? 這實際上已經是問題的迭代解法了。你完全可以把它視為DP。但只是可能沒有人把它作為DP的例子,并冠以DP解法之名而已……
#劉未鵬 發表于2008-06-05 22:37:22 IP: 222.94.44.*
??? @Eastsun:
??? 你說得沒錯。用空間換時間是DP的一個重要性質,事實上,它還是許多算法的一個性質。所以說,“用空間換時間”并沒有“充分”地描述DP的特點,更沒有對 “如何探索一個DP問題的解”提供建設性的幫助。對于后者,如何尋找遞推關系或“子問題”才是重點,一旦找到了子問題之后通過緩存子問題的解來優化復雜度就相對比較trivial了。
??? 用“空間換時間”來總結DP的本質就好比用“它們都是一種算法思想”來總結各大算法分支的本質一樣,因過于泛化和描述性而并不能對解決實際問題帶來什么幫助。
??? @kusk:
??????? 基本上關于離散數據處理方面的難題都可以歸結為組合問題。因為基本上能見得到的有意義的題目,通常只有涉及到組合才會帶來爆炸式的復雜度,否則一般都只能是多項式級的。
??? 沒錯。
#Eastsun 發表于2008-06-05 23:14:22 IP: 202.113.19.*
??? 其實我覺得把動態規劃當成是一種思想也未嘗不可。
??? 大多數“空間換時間”的算法本質上可以歸結為DP。
??? 不過通常的DP有個很明顯的特征:就是可以寫出狀態轉移方程。kusk所舉的斐波那契數列的遞歸公式在DP里就是狀態轉移方程。然后根據狀態轉移方程就可以算出該算法的時間/空間復雜度,并寫出具體的程序代碼。
??? 劉汝佳的《算法藝術與信息學競賽》一書里面列舉并講解了很多非常不錯的關于DP的題目。
#pongba 發表于2008-06-06 00:11:29 IP: 222.94.44.*
??? @EastSun:
??? 我倒是認為轉移方程只是表象,是在尋找出了子問題,并構建出了遞推關系之后,自底向上地編寫程序的時候的事情,而不屬于一開始的事情。
??? 一開始的事情是“如何”寫出(找到)轉移方程,而我想總結的則是關于如何找出那個關鍵的轉移方程的一些啟發式探索方法。
??? DP的思想理解起來容易,一開始的時候我看了幾本書的DP章節,覺得,哦,這很容易,遞推嘛。但真正自己做題的時候,發現尋找遞推關系是最困難的一步,non-trivial的DP題里面遞推關系并不是顯而易見的。
??? TopLang里面也有人推薦《算法藝術與信息學競賽》,但提到里面的題目偏難,不適合初學者。作為題集應該還是不錯的。據說幾本眾所周知的經典算法書里面最適合程序員學習的是《Algorithm Design》,以下引一段g9的介紹:
??????? 我也喜歡Algorithm Design這本書。個人覺得非常適合初學者和程序員。書一開始就給出穩定婚姻問題。從解決問題的角度入手,層層遞進地講解怎么設計,引出算法設計的一般方法。然后用幾道常見題目引入常用算法的設計思路。奠定總綱后,后面的書就從不同的角度深化算法設計的方法。例題也有趣。個人相當喜歡從設計到分析的講解思路。這種方法給讀者(至少是程序員)誘人的動機。畢竟很多人讀算法書是為了寫出更強大的程序。尤其像我這種老人家,NADD癥狀嚴重,尤其需要令人信服的動機,不然就老走神。而且作者強調設計,反而提供了鉆研算法分析的充分理由,很好地解釋了要設計出正確高效的算法,形式化的分析是必要手段。為什要擴展?為什么要到處一堆引理?為什么要追究復雜度?為什么要泛化?為什么要特化?這些看來學究的行為自然地有了現實基礎。加上這本書寫得曉暢通透,就算你只看書不動手,也可以獲得"好舒服,原來我也可以設計出這些算法啊。一點都不難嘛"這種類似K粉后的波浪狀快感和幻覺。:-D
#Eastsun 發表于2008-06-06 01:25:03 IP: 202.113.19.*
??? [quote="pongba"]我倒是認為轉移方程只是表象,是在尋找出了子問題,并構建出了遞推關系之后,自底向上地編寫程序的時候的事情,而不屬于一開始的事情。
??? 一開始的事情是“如何”寫出(找到)轉移方程,而我想總結的則是關于如何找出那個關鍵的轉移方程的一些啟發式探索方法。
??? DP的思想理解起來容易,一開始的時候我看了幾本書的DP章節,覺得,哦,這很容易,遞推嘛。但真正自己做題的時候,發現尋找遞推關系是最困難的一步,non-trivial的DP題里面遞推關系并不是顯而易見的。[/quote]
??? 非常認同你的看法
??? 像“最大和連續子序列”這種問題都是很顯而易見的DP問題
??? 有些問題表明上看來與DP毫無干系,需要通過一些巧妙的變換或構造才能得到狀態以及狀態轉移方程。
??? 《算法藝術與信息學競賽》里面就有一些這樣的問題。
??? 不過這本書里面好像沒有講Tree型DP,我覺得這也是一類常見且相對復雜的DP
總結
- 上一篇: 微信长按识别二维码 -- 页面多个二维码
- 下一篇: 飞凌嵌入式 全志A40i开发板试用体验