Introduction to Algorithm ( chapter 15: Dynamic Programming)

Dynamic programming is a very powerful algorithmic paradigm in which a problem is solved by identifying a collection of subproblems and tackling them one by one, smallest first, using the answers to small problems to help figure out larger ones, util the whole lot of them is solved. In dynamic programming we are not given a dag, the dag is implicit. Its nodes are the subproblems we define, and its edges are the dependencies between the subproblems: if to solve subproblem B we need the answer to subproblem A, then there is a (conceputal) edge from A to B. In this case, A is thought of as a smaller subproblem than B--and it will always be smaller, in an obvious sense.

Dynamic Programming: The adjective "dynamic", indicates that we are interested in processes in which time plays a significant role and in which time the order of operations may be crucial. It usually entails mutil-stage problems which involve multi-decisions in each stage.

動態規劃一般包含四個性質:最優子結構子問題重疊邊界和子問題獨立。當問題滿足具備這四個性質時,也許動態規劃就能派上用場了。

最優子結構:將問題劃分爲子問題,當子問題最優時,母問題通過優化選擇後一定達到最優。

子問題重疊:母問題劃分成子問題後,母問題和子問題本質上是同一個問題的情況稱爲子問題重疊,然而母問題和子問 題中出現的不同點往往就是母、子問題之間傳遞的參數,一般來說母問題的參數要比子問題“大“

邊界:將母問題劃分成子問題後,子問題在一定時候就不再需要提出子子問題的情況叫做邊界,沒有邊界就會出現死循環,邊界往往是”最小“的子問題。

子問題獨立:母問題再對子問題進行選擇時,當前被選擇的子問題兩兩互不影響的情況稱爲子問題獨立。

當子問題有了解答時,母問題往往優化選擇的方式是max,min。。。

動態規劃的另外兩個特點:

 

備忘錄:這是動態規劃實施的第5個特點,可以將問題的解放在一個變量中,如果再次遇到這個問題就直接從變量中獲得答案,因此每個子問題僅會計算一遍,如果不做備忘錄的話動態規劃就變成了遞歸解法失去了優勢。備忘錄的通常做法是利用數組來記錄子問題的解。

時間分析:對於多子問題多決策(即對應多階段多決策),如果不採用動態規劃的話時間規模往往是指數級別的(例如每個子問題有多種決策,那麼累積起來就是指數級別的),如果採用動態規劃後,可以將時間降爲多項式級別的。例如對於0-1揹包問題,包的容量爲1000,有100個物品,對於不採用動態規劃而言,每個物品或放或不放入,則有

2^100種情況,而採用動態規劃後,大概只有1000*100種子問題,而考慮到物品的實際重量,實際情況並不會出現1000*100個子問題,遠遠小於2^100種情況,因此動態規劃可以大大地降低時間規模。
動態規劃是解決多階段多決策問題的模型,這裏的母問題就是整個階段,而子問題往往是細化後的小的階段,而優化選擇就是決策的過程。
使用動態規劃需要滿足的兩個主要條件爲:

1.狀態必須滿足最優化原理:無論過去的狀態和決策如何,對前面的決策所形成的當前狀態而言,餘下的諸決策必須構成最優決策,通俗地理解爲子問題的局部最優將導致整個問題的全局最優,即問題具有最優子結構的性質,也就是說一個問題的最優解只取決於其子問題的最優解,非最優解對問題的求解沒有影響。

2.狀態必須滿足無後效性:即過去的決策只能通過當前狀態影響未來的發展,當前的狀態是對以往決策的總結。即:某階段的狀態一旦確定,則此後過程的演變不再受此前各狀態及決策的影響,也就是說,“未來與過去無關”,當前的狀態是此前歷史的一個完整總結,此前的歷史只能通過當前的狀態去影響過程未來的演變,具體地說,如果一個問題唄劃分各個階段之後,階段I中的狀態只能由階段I+1中的狀態通過狀態轉移方程得來,與其他狀態沒有關係,特別是與未發生的狀態沒有關係,這就是無後效性。

(個人認爲理解動態規劃應該從宏觀的四個性質入手而不是從實現的細節入手,之前認爲用數組來實現斐波納契數列的求取也屬於動態規劃,它沒有什麼最優子結構所以只是利用了空間換時間的方式即僅僅採用了備忘錄的方式,記錄下了子問題的結果避免了重複計算子問題而已,所以並不屬於動態規劃。因此理解動態規劃的本質還應從4條性質入手,滿足4條性質後才考慮動態規劃採用了備忘錄的方式的優點。)

動態規劃不是算法,而是解決問題的一種方法,他是在一件事情發生的過程中尋找最優值得方法,因此,需要對這件事情所發生的過程進行考慮,而通常我們從過程的最後一步開始考慮,而不是先考慮過程的開始。

遇到一個問題時,可以根據下面的步驟來考慮使用動態規劃:

1.構造問題所對應的過程

2.思考過程的最後一個步驟,看看有哪些選擇情況

3.找到最後一步的子問題,確保符合"子問題重疊",把子問題中不相同的地方設置爲參數

4.使子問題符合“最優子結構"

5.找到邊界,考慮邊界的各種處理方式

6.確保滿足”子問題獨立“,一般而言,如果我們是在多個子問題中選擇一個作爲實施方案,而不會同時實施多個方案,那麼子問題就是獨立的

7.考慮如何做備忘錄

8.分析所需時間是否滿足要求

9.寫出轉移方程式

“能用動態規劃解決的問題,肯定能用搜索解決,但是搜索的時間複雜度太高,怎麼優化呢?聯想到記憶搜索,就是搜完某個解之後把它保存起來,下一次搜到這個地方的時候調用上一次的搜索出來的結果,這樣就解決了處理重複狀態的問題,動態規劃之所以速度快是因爲解決了重複處理某個狀態的問題,記憶搜索是動態規劃的一種實現方法,搜索到i狀態,首先確定要解決i首先要解決什麼狀態,那麼那些狀態必然可以轉移給i狀態,於是就確定了狀態轉移方程。”

使用備忘錄的方法而不是遞歸解決動態規劃,而對於分治法往往使用遞歸方法,有如下原因:

Then why did recursion work so well with divide-and-conquer? The key point is that in divide-and-conquer, a problem is expressed in terms of subproblems that are substantially smaller, say half the size. For instance, mergesort sorts an array of size n by recursively sorting two subarrays of size n/2. Because of this sharp drop in problem size, the full recursion tree has only logarithmic depth and a polynomial number of nodes.

In contrast, in typical dynamic programming formulation, a problem is reduced to subproblems that are only slightly smaller--for instance, L(j) relies on L(j-1). Thus the full recursion tree generally has polynomial depth and an exponential number of nodes. However, it turns out that most of these noes are repeats, that there are not too many distinct subproblems among them. Efficiency is therefore obtained by explicitly enumerating the distinct subproblems and solving them in the right order.

即:

分治法主要將一個問題分解成獨立的子問題,然後遞歸地解決這些子問題。

而動態規劃適合子問題非獨立的情況,即這些子問題包含一些公共的子子問題,它對每個子子問題只求解一次,將其結果保存在一張表中,從而避免每次遇到各個子問題時重複的計算。


用動態規劃解問題時,往往依據遞歸式以自底向上的方式進行計算,在計算過程中,保存已解決的子問題答案,每個子問題只計算一次,而在後面需要時只要簡單查一下,從而避免大量的重複計算,最終得到多項式時間的算法。

動態規劃的基礎:

1.最優子結構,如果一個問題的最優解中包含了子問題的最優解,則該問題具有最優子結構,DP首先需要找到子問題的最優解,解決了子問題,然後找到問題的最優解,以自底向上的方式進行。(而貪心算法是自頂向下的,即會先做出選擇在當時看起來是最優的選擇,然後再求解一個結果子問題而不是先尋找子問題的最優解,然後再做出選擇)。

DP的運行時間依賴於兩個因素的乘積:子問題的個數 和 每一個子問題中有多少種選擇。例如,在矩陣鏈中,有O(n^2)個子問題,每個子問題至多有n-1種選擇,故執行時間爲O(n^3)

注意:最優子結構中,各個子問題相互獨立這點很重要,如果不是相互獨立的話很可能不適用動態規劃

2.重疊的子問題:即子問題的空間要“小”,也就是用來解原問題的遞歸算法可反覆地解同樣的子問題,而不是總在產生新的子問題。典型的,不同的子問題是輸入規模的一個多項式,當一個遞歸算法不斷地調用同一問題時,我們就說該最優問題包含重疊子問題,注意,如果同一個問題的兩個子問題不共享資源,則他們就是獨立的,對兩個子問題而言,如果它們確實是相同的子問題,只是作爲不同問題的子問題出現的話,它們是重疊的。動態規劃算法總是充分利用重疊子問題,即通過每個子問題只解一次,把解保存在一個在需要時就可以查看的表中,而每次查表的時間爲常數。

部分內容引自http://www.cnblogs.com/SDJL/archive/2008/08/22/1274312.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章