動態規劃總結

生活中我們經常能夠碰到這樣一類問題,它的過程可以按照時間順序分成若干個相互聯繫的子階段。每一個子階段都可以得出一個結論,學術上我們稱之爲決策,則這一整個過程的決策就是這些子階段的決策的總和,即決策序列。這種過程稱爲多階段決策過程。

針對這種多階段決策過程,本文提到一種算法對於解決該類問題非常適用,即動態規劃算法。動態規劃(Dynamic Programming)算法是解決多階段決策過程最優化問題的一種常用方法,難度比較大,技巧性也很強。利用動態規劃算法解決此類問題,可以減少很多無謂的計算量。

動態規劃算法的基本思想是:將待求解的問題分解成若干個相互聯繫的子問題,先求解子問題,然後從這些子問題的解得到原問題的解;對於重複出現的子問題,只在第一次遇到的時候對它進行求解,並把答案保存起來,以後再次遇到時直接引用答案,不必重新求解。動態規劃算法將問題的解決方案視爲一系列決策的結果。另外,在動態規劃算法中,還要考察每個最優決策序列中是否包含一個最優決策子序列,即問題是否具有最優子結構性質。

必要條件

一般來說,在判定一個問題是否需要採用動態規劃方法求解時,我們需要觀察分析該問題是否滿足以下幾個條件:

(1)動態規劃的子問題重疊性質:

         這裏所說的子問題是指整個過程中的子階段:假設某n個成員的不規則數據序列中,需要找出該序列中一個遞增子序列的最大成員個數。例如給定5個數據分別爲2、6、3、9、8,由於該問題比較容易解答,我們在此通過表格的形式來展示各個階段的最優解。下圖中第一行代表的是數據的個數,第二行爲給定的數據,第三行在個數爲n時的最優解,當有5個數據的時候,此時的遞增成員變量的最大個數爲3個(S(5))。而圖中用橢圓形圈住的即該系統的子系統,也就是我們標題中所說的子問題。

 

從圖中可以看出,我們如果要向計算棕色圈住的即5個數據的系統中的最優解,我們其實可以先將深藍色的即4個數據的最優解求出,然後使用該最優解中的最大數據與第5個數據進行比較,從而決定第5個數據是否也能放在最大遞增子序列中;而如果要求解深藍色系統的最優解,我們可以先將綠色系統的最優解求出,而綠色系統又是深藍色系統的子系統。由此可見,該系統中的最優解可以由其子系統中的最優解間接求出。

         子問題的重疊性描述的是在用遞歸算法自頂向下對問題進行求解時,每次產生的子問題並不總是新問題,有些子問題會被重複計算多次。動態規劃算法正是利用了這種子問題的重疊性質,對每一個子問題只計算一次,然後將其計算結果保存在一個表格中,當再次需要計算已經計算過的子問題時,只是在表格中簡單地查看一下上一次計算的結果,因此相較於簡單的利用遞歸處理的要有更高的效率。本文認爲子問題重疊是動態規劃使用的基礎,如果子問題之間沒有相關聯繫,動態規劃處理也就不適合使用。

(2)動態規劃的最優化原理:

動態規劃解決問題的思路一般就是求解問題的最優解,當問題呈現出子問題重疊性之後,問題的最優解即可以理解爲求解子問題的局部最優解。所有子問題的最優解會導致整個問題的全局最優,因此稱該問題具有最優子結構的性質,也就是說一個問題的最優解只取決於其子問題的最優解,而非最優解對問題的求解沒有影響。

(3)動態規劃的無後效性原則:

 所謂無後效性原則,指的是這樣一種性質:某階段的狀態一旦確定,則此後過程的演變不再受此前各狀態及決策的影響。也就是說,“未來與過去無關”,當前的狀態是此前歷史的一個完整的總結。

設計步驟

當我們已經確定待解決的問題需要用動態規劃算法求解時,通常可以按照以下步驟設計動態規劃算法:

1、分析問題的最優解;

2、遞歸地定義最優值;

3、採用自底向上的方式計算問題的最優值;

4、根據計算最優值時得到的信息,構造最優解。

1~3步是動態規劃算法解決問題的基本步驟,在只需要計算最優值的問題中,完成這三個基本步驟就可以了。如果問題需要構造最優解,還要執行第4步;此處的最優解是對最優值的一個通用表達,可以表達該系統內任意子問題的最優值。因此在第3步通常需要記錄更多的信息,以便在步驟4中,有足夠的信息快速地構造出最優解。

舉例說明

下面我們以一個例子對這個算法的使用過程進行講解,例子取自Leetcode網站(https://leetcode.com/problems/minimum-swaps-to-make-sequences-increasing/description/),題目(801. Minimum Swaps To Make Sequences Increasing)原文如下:

We have two integer sequences A and B of the same non-zero length.

We are allowed to swap elements A[i] and B[i].  Note that both elements are in the same index position in their respective sequences.

At the end of some number of swaps, A and B are both strictly increasing.  (A sequence is strictly increasing if and only if A[0] < A[1] < A[2] < ... < A[A.length - 1].)

Given A and B, return the minimum number of swaps to make both sequences strictly increasing.  It is guaranteed that the given input always makes it possible.

Example:

Input: A = [1,3,5,4], B = [1,2,3,7]

Output: 1

Explanation:

Swap A[3] and B[3].  Then the sequences are: A = [1, 3, 5, 7] and B = [1, 2, 3, 4]which are both strictly increasing.

題目翻譯過來我們可以這麼理解:給定兩個成員數量均相等的整數序列A和B,要求在最小的調換次數內使兩個序列嚴格遞增。調換數據的標準爲A序列的第i個數據與B序列對應位置的第i個數據調換,即A[i]與B[i]調換。

首先對於問題進行分析,我們來確定該問題是否可以使用動態規劃的方法來解決,因此我們需要判斷該問題是否滿足動態規劃的三個條件。

1、子問題重疊性:

對該問題進行分析,爲了保證n個成員變量的數組都可以遞增,那麼首先需要保證前n-1的數據是嚴格遞增的,那麼再進行第n個數據的判定就會容易很多。因此爲了求解n個數據需要調換的次數,我們可以先確定前n-1個數據調換的次數,然後再判斷第n個數據是否調換從而確定n個數據滿足題目要求的調換次數。這樣我們就將整個問題拆分成了若干個子問題來解決,而且子問題的狀態是可以依次影響下一級問題的狀態,由此可見,該問題具有子問題的重疊性。

2、最優化原理:

由於本題要求的是滿足數據調換後兩個序列嚴格遞增的最少的調換次數,因此這也是一個求解最優值的問題。

3、無後效性原則:

該問題中,每一個子問題(例如n-1子系統)的狀態可以影響下一級子問題(n子系統)的狀態,但下一級(n)的狀態卻不能影響上一級(n-1)的結果。因此該問題滿足無後效性原則

既然問題可以按照動態規劃的思路來解決,我們不妨按照上問提到的設計步驟來逐步進行分析:

  1. 首先確定問題的最優解,本題要求以最小的調換次數來讓兩個序列都達到嚴格遞增的效果,因此最優解其實就是使序列能夠滿足要求的最小次數。
  2. 通過對題目的分析,該問題具有子問題重疊性,因此我們就先求出每個子問題的狀態,然後通過子問題的狀態進而確定該問題的最有狀態。由於我們求解n個數據的最優值時,需要調用n-1個數據的最優值;並且求解n-1個數據時要用到n-2個數據的最優值,因此需要遞歸定義子系統最優值。
  3. 第2步分析問題時,我們採用的是從頂向下的順序來分析並且遞歸定義最優值,但對於該問題的求解,如果要確定n個數據的最優解時,我們必然需要先確定n-1個數據的最優解以備後面計算使用。因此,在計算最優值時,我們採用自底向上的順序來依次確定子系統的最優值。
  4. 針對不同的個數的系統求解最優值,我們需要構造一個通用的表達式來表示所有子問題的最優值。因此這一步需要通過前面構造每一級最優值時的規律來構造最優解。

題目要求中,兩個序列在調換對應數據後最終都可以實現各自嚴格遞增,所以給定的數據序列所能滿足可以實現數據調換的條件有兩種:第一種是A[i-1]<A[i]並且B[i-1]<B[i],此時數據由於本來就是遞增,所以i和i-1兩組數據可以選擇都調換也可以選擇都不調換;第二種就是A[i]>B[i-1]並且B[i]>A[i-1],此時需要調換一個數據來實現兩組數據遞增。

第一種情況很好理解,當滿足A[i-1]<A[i]並且B[i-1]<B[i]的條件時,此時兩個數據i以及i-1均可以調換。第二種情況比較複雜,如圖所示,假設A和B兩個數據序列我們用圖示的兩個線段來表示分別含有兩個成員變量的A和B數組,其中線段的長度代表數組成員的大小。由於題目要求最終兩個數組都是嚴格遞增的,所以按照這個要求,圖中的兩組線段在交換之後應該達到的狀態是左邊線段高度低於右邊的線段。所以針對線段A[i-1],當右邊的線段A[i]不高於線段A[i-1]時,必然存在線段B[i]高於線段A[i-1],纔可以滿足題目要求。

否則,當線段A[i]低於左邊的線段A[i-1]時,如果線段B[i]也低於線段A[i-1],此時情形如下圖所示,即使交換之後數據序列B中A[i]高於B[i-1],但數據序列A中B[i]是比A[i-1]要低的。所以,最終不管如何調換數據,都是不能滿足兩個數據序列均保持嚴格遞增。

因此,在該題目要求前提下,數據序列中如果A[i]不大於A[i-1]時,必然有B[i]大於A[i-1]。同理可證,在B[i]不大於B[i-1]時,必然存在A[i]大於B[i-1]。並且當A[i]大於B[i-1]時,B[i]也需要大於A[i-1],因此我們將該題拆分成這兩種情況來分析。下面我們詳細分析在這兩種情況下需要交換數據的方式。

  1. 當A[i-1]<A[i]並且B[i-1]<B[i]時,兩組數據均調換或者是兩組數據均不調換;
  2. 當A[i]>B[i-1]並且B[i]>A[i-1]時,A[i]與B[i]調換,A[i-1]與B[i-1]不變或者是A[i]與B[i]不變,A[i-1]與B[i-1]交換。

針對以上兩種情況,我們可以給出表達方程,基於動態規劃中對於所有階段的結果學術上稱之爲狀態,同樣,這個表達式也有一個更高端的名字叫做狀態轉移方程。在狀態轉移方程中,我們將數據調換的結果表達爲兩種狀態:一種是第i個數據不換時兩個序列爲遞增序列所需要的調換次數,我們稱爲nature[i]。雖然第i個數據不調換,但前i-1個數據可能會有交換的情況存在,所以這個nature[i]不一定爲0;另一種是第i個數據調換後兩個序列嚴格遞增所需要的次數,我們稱爲swap[i]。

根據動態規劃的基本思想,我們需要對該問題進行子問題重疊的思考,找出第i個狀態與前序狀態的關係。根據上述分析,得出的狀態轉移方程如下。

情景1:A[i-1]<A[i]並且B[i-1]<B[i]

如果第i個數據不交換的話,那麼第i-1個數據也不用交換,此時它與第i-1次不交換的狀態是一致的,即交換次數沒有增加,因此

                            NatureCnt[i] = NatureCnt[i-1];                                                       公式1

如果第i個數據交換的話,那麼第i-1個數據必然也需要交換,因此它比第i-1個數據交換的狀態多一次交換,狀態方程表示爲:

                            SwapCnt[i] = SwapCnt[i-1] + 1;                                                     公式2              

情景2:A[i]>B[i-1]並且B[i]>A[i-1]

該種情景下狀態比較複雜,因爲第i個數據交換與否和前序的狀態都可能有關係。假如第i個數據保持不變的話,第i-1個數據可以交換也可以保持原位,而當第i-1個數據保持原位時,該種情形和情景1完全一致,所以狀態方程如公式1所示;當第i-1個數據進行交換時,其狀態轉移方程寫爲:

                            NatureCnt[i] = SwapCnt[i-1];                                                         公式3

同樣的,第i個數據交換時也存在兩種狀態方程:即第i-1交換時,狀態轉移方程和公式2完全相同,當第i-1個數據不進行交換時,第i個數據交換的狀態方程爲:

                            SwapCnt[i] =NatureCnt[i-1]+1;                                                      公式4

如果某些數據序列既滿足情景1,又滿足情景2,例如數組A[2]={1,5},B[2]= {2,4}我們將會分別通過公式1和公式3得到兩個NatureCnt[i]的值;同樣,通過公式2和4,我們可以得到兩個SwapCnt[i]的值。由於題目要求最優解是最小的交換次數,所以我們調用庫函數min來實現最小次數的求解,NatureCnt[i]和SwapCnt[i]可以分別表示爲:

                            NatureCnt[i] = min(NatureCnt[i],SwapCnt[i-1]);                             公式5

                            SwapCnt[i] = min(SwapCnt[i],NatureCnt[i-1]+1);                           公式6

當然,大多數情況下,數組的關係並不都會兩個情景都滿足,所以當我們碰到兩個數組只滿足情景2的時候,此時的NatureCnt[i]是沒有初值進行初始化的,因此我們需要給其一個極大值的初始值,來保證函數min可以正常運行。

根據以上分析,我們可以對程序進行編碼。按照第3個設計步驟要求的自底向上計算最優值,因此我們可以從0開始依次遞增求解n個數據的最優狀態。由於當數組成員變量數量爲0時不需要變換數據,而且上述推導公式利用到了第i-1個數據的狀態,因此我們賦值初始值SwapCnt[0]和NatureCnt[0]均爲0,將狀態計算從1開始,代碼編程如下。

         for(i = 1; i < n; i++)

         {

                   NatureCnt[i] = 9999999999;   //give the maxmum if AB don't reach condition 1,

                   SwapCnt[i] = 99999999999;    //used in condition 2

                   if(A[i] > A[i-1] && B[i]>B[i-1])//condition 1

                   {

                            NatureCnt[i] = NatureCnt[i-1];

                            SwapCnt[i] = SwapCnt[i-1] + 1;

                   }

                   if(A[i] > B[i-1] && B[i]>A[i-1])//condition 2

                   {

                            NatureCnt[i] = min(NatureCnt[i],SwapCnt[i-1]);

                            SwapCnt[i] = min(SwapCnt[i],NatureCnt[i-1]+1);

                   }

         }

通過上面的例子我們可以看出,動態規劃解決問題的思路大致都是將問題拆分成若干個相互關聯的小問題。例如上述例程中,n個數據經過調換後遞增所需要的次數是與n-1個數據調換後遞增所需要的次數相關聯的。當然還會存在一些過程是沒有這麼直接明顯的相關關係的,所以在確定子問題的相互聯繫時,需要一定的技巧。但處理效果還是挺明顯的,雖然佔用了一些空間來記錄每一個子問題的狀態,但求解整個過程時分別調用子問題的狀態大大降低了處理的時間複雜度。

本文的初衷在於對近期學習使用動態規劃算法的心得體會進行總結並且與各位分享。本人由最初對動態規劃毫無概念的小白到現在能夠自如使用動態規劃解決問題並且稍微有一些自己的心得體會。期間上網查閱了大量資料並且對於博文中提到的經典例題先嚐試自己解決,再看答案分析,加深動態規劃使用方法的印象。當然最初針對例題,自己的解決方案完全和動態規劃沾不上邊,後來某天突然理解了“狀態”的含義,纔開始對動態規劃初窺門徑。動態規劃裏的狀態其實指的就是階段的最優解,如上述例程中最小的調換次數。

學習動態規劃期間翻閱的博文中,其中CSDN網站中博主“英雄哪裏出來”的“夜深人靜看算法”系列文章印象最爲深刻,在此向各位推薦。當然其他還有很多博文也都挺不錯,給了我很多的提示啓發,在此不一一列舉,文章附錄會將博文鏈接列舉出來。最後向提供動態規劃心得體會的前輩們表示衷心的感謝。

附錄

夜深人靜看算法:https://blog.csdn.net/whereisherofrom/article/details/78922215

01揹包問題:https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html

動態規劃算法:https://www.cnblogs.com/chuninggao/p/7295793.html

理解動態規劃:https://www.zhihu.com/question/39948290

動態規劃:http://blog.jobbole.com/83949/

漫話動態規劃:https://www.sohu.com/a/153858619_466939

動態規劃:http://www.hawstein.com/posts/dp-novice-to-advanced.html

動態規劃分析:https://blog.csdn.net/yuxin6866/article/details/52507623

動態規劃:https://www.cnblogs.com/raichen/p/5772056.html

.csdn.net/whereisherofrom/article/details/78922215

01揹包問題:https://www.cnblogs.com/Christal-R/p/Dynamic_programming.html

動態規劃算法:https://www.cnblogs.com/chuninggao/p/7295793.html

理解動態規劃:https://www.zhihu.com/question/39948290

動態規劃:http://blog.jobbole.com/83949/

漫話動態規劃:https://www.sohu.com/a/153858619_466939

動態規劃:http://www.hawstein.com/posts/dp-novice-to-advanced.html

動態規劃分析:https://blog.csdn.net/yuxin6866/article/details/52507623

動態規劃:https://www.cnblogs.com/raichen/p/5772056.html

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