難以捉摸的動態規劃

不知在你眼中,動態規劃在衆多算法中處於什麼地位呢?是ACM比賽中不可或缺的技巧之一,又或者是征戰POJ水題必備的利器?倘若果真如此,那我多少有些羨慕你,因爲我至始至終都沒有領悟動態規劃的精髓。

        自我閱讀《算法導論》已久,我對書上給出的解決特定問題所用到的算法並無太多不明朗之處,而且在遇到相似的問題時,可以很快意識到自己熟知的某個算法能夠高效的解決這個問題。但是我不太明朗的地方在於:動態規劃並非是解決某個特定問題的算法,他是解決一類問題的算法思想(尋求問題的最優解)。或許我們能夠很輕鬆的理解某個基於動態規劃而形成的算法(比如說Bellman-Ford算法),但是理解算法背後動態規劃的思想則需要頗費一番功夫。

 


        我們還是從最簡單的0-1揹包問題說起,因爲很多人都是從這個問題開始接觸動態規劃。在揹包問題上,給出最詳細,最清晰解答的當之無愧是揹包問題九講。但是作者自己也明言因爲存在思維上的跳躍,他的語言向來不以易於理解爲長,所以若不是已經對動態規劃有了完善認識的高手和準高手們,思考起來還是頗爲費力的。

 

0-1揹包問題:有N件物品和一個容量爲V的揹包。對於i≤N,第i件物品的體積是c[i],價值是w[i]。求解將哪些物品裝入揹包可使價值總和最大。

 

        在未系統學習算法之前呢,估計面對這種問題時我會毫不猶豫的直接使用強力法,將N種物品的所有組合枚舉出來,然後依次比較在不超過容量V的情況下各組合的總價值。沒有懸念的,這是非常天真以及笨拙的做法,其時間複雜度爲O(2^N)。

        那麼如何使用動態規劃呢?對於i≤N,考慮將前i件物品放入容量爲V的揹包中這個子問題,若只考慮第i件物品的策略(放或不放),那麼就可以轉化爲一個只牽扯前i-1件物品的問題。如果不放第i件物品,那麼問題就轉化爲前i-1件物品放入容量爲V的揹包中的最大價值;如果放第i件物品,那麼問題就轉化爲第i件物品的價值加上前i-1件物品放入剩下的容量爲V-c[i]的揹包中的最大價值。

        上面這段話是來自於揹包問題九講的講解(我略去了轉移方程),其正確性當然是毋庸置疑的。但是有一點,並且是很重要的一點是,我們爲什麼會這樣思考呢?換句話講,我腦海中的疑問並不是會不會解決這個問題,而是解決這個問題的思想究竟是如何創造出來的。一想到有可能自己純粹只是理解算法的內容,而不能理解算法本身來歷的前中後末,以及產生的靈感,我就有些坐臥難安。就說現在,如果要我解釋0-1揹包問題,我也能說的頭頭是道,但是一旦別人問起我是如何想到這個方法的,我便腦中一愣,呆若木雞。

 


        知其然知其所以然,這是在算法學習中久經考驗的黃金法則。如果不能做到知其所以然,知其然也是白搭。對於算法而言,不僅在於你會不會運用這個算法,更重要的在於是否理解這個算法的思想。在這種條件下,也許在某些時日之後,你也能創造出屬於自己的算法。

        事實上,對於同一個問題,如果我們多琢磨琢磨,總能有意外的收穫。思考所帶來的裨益性無可爭議,我相信思考得越多,對問題的解法就理解得越爲深刻。比如讓我們使揹包問題更爲具體些,假設這些物品都是珠寶,分別爲鑽石,瑪瑙,翡翠,水晶等等。我們希望在總價值最大的情況下,珠寶類別的種類同時也最多(物品中存在重複的類別)。這樣在求解過程中我們不得不更爲詳細的記錄當前揹包中的物品類別,比較的過程也變得複雜了。但是有一點不變的是,我們仍然可以使用動態規劃來求解。不僅是揹包問題,在最短路徑問題之中,動態規劃也得到了廣泛的使用。就像前文所說的那樣,動態規劃是爲了解決最優化這一類問題的算法思想。

        熟讀《算法導論》的讀者都知道,最優子結構和重疊子問題是動態規劃能否使用的兩點主要特徵。0-1揹包問題就具備這樣的性質,我們總是構造子問題的最優解,並且最終的結果中也包含子問題的最優解。我們把每次對子問題求解的結果用一個數組存儲起來,就避免了重複求解的過程。但並不是所有最優化的問題都能用動態規劃求解,比如書中列舉出的無權最長路徑便不存在最優子結構,也就是說子問題中的最優解相互干擾,合併爲原問題之後的解不能滿足原問題所要求的限制。所以這類問題通常被我們劃分爲NP完全問題,即迄今爲止還未能發現更高效的算法解決此類問題,其時間複雜度不能降爲多項式時間。

        動態規劃之所以難以理解,一方面在於不容易確定問題的最優子結構,只要我們能迅速確立其轉移方程,問題多半也就解決了。另一方面在於其自底向上的思想與我們習慣的思維方式是違背的,我們往往會從全局的層次上考慮問題,自頂向下來思考子問題的解,這樣我們很容易陷入遞歸分解問題的困境,以至於對動態規劃有些捉摸不透了。

 

 

        我一向以爲,貫穿這無數美妙算法之中的算法思想歸根到底來自於兩點:一是分治,二是遞歸(其實嚴格來講,分治的算法思想也能解決遞歸問題)。分治的思想是把原本龐大的問題分解爲幾個較小的問題,分而治之後再合併,最爲典型的例子自然是歸併排序了。遞歸的思想是把原來的問題轉換爲一個遞推式,如果我們想求原問題,那麼我們需要先解決子問題,在求解子問題的時候我們發現先需要解決子問題的子問題,然後依此類推。遞歸算法中的翹楚自然就屬於動態規劃了。

        那麼分治思想與遞歸思想的同異之處又是什麼呢?相同之處自然都是將原本的問題轉化爲更小的問題。相異之處在於分治算法遵從平均主義,總是將問題分解爲幾個規模等同的子問題,若寫成函數屬於f(n) = af(n/b) + g(n)的形式;而遞歸思想則只是將原問題轉化爲單獨較爲簡單的子問題,轉化的速度遠遠不及分治,若寫成函數則屬於f(n) = f(n-c) + f(c) + g(n)的形式(兩個函數中的a, b ,c均爲常數,函數g(n)爲合併子問題的開銷)。

        在我看來,算法學習的曲線是相當陡峭和漫長的。這不僅僅體現在要理解每個具體的算法結構,更要理解分治與遞歸這些處於更底層的算法思想。倘若能明確其算法思想,理解動態規劃也不在話下,而其他問題自然也迎刃而解了。

發佈了37 篇原創文章 · 獲贊 6 · 訪問量 23萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章