-
動態規划算法定義:
動態規划,英文描述為Dynamic programming. 是一種可以把原始問題分解為若干相關聯的子解問題,並通過求取和保存子問題的解,獲得原問題的解。
動態規划算法可以解決的問題通常包含如下特征:
- 重疊子問題
- 最優子結構
對於第一個特征,比較容易理解,即分解的若干子問題,包含着重復的解。舉例如:斐波那契數列,F(n) = F(n-1) + F(n-2), 求解的F(n-1)的過程中,包含着求解F(n-2)的結果。
對於第二個特征,參考網上的說法為:
假設當前決策結果是f[n],則最優子結構就是要讓f[n-k]最優,最優子結構性質就是能讓轉移到n的狀態是最優的,並且與后面的決策沒有關系,即讓后面的決策安心地使用前面的局部最優解的一種性質。
關鍵字解讀為:
- 當前的決策與后面的決策是無關的,
- f[n-k]是最優的,轉移到f[n]的狀態是最優的
2. 動態規划算法的一般步驟和難點
使用動態規划算法解決問題的一般步驟是:
- 找到問題的最優解的性質,用數學公式或者算法描述
- 拆解子問題,確定問題的遞推結構,保證可以收斂。
用知乎大神們的總結就是:找到問題的狀態描述和狀態轉移方程。
3. 動態規划算法的分類和理解
根據我的理解,以及網上的說法,我把動態規划算法分為三個類別和層次:
-
簡單動態規划算法,即狀態方程是用一個維度的變量的描述的,常見的問題如:斐波那契數列,爬台階問題等
爬台階問題問題描述: 有一座高度是10級台階的樓梯,從下往上走,每跨一步只能向上1級或者2級台階。要求用程序來求出一共有多少種走法。
狀態描述: 我們使用變量n表示台階的級數,F(n)表示n級台階一共有多少種走法
狀態轉移方程與問題分解: 根據每次能跨越的台階數目:1級台階或者2級台階,因為走到N級台階之前,人一定是處於N-1級台階或者N-2級台階。F(n)的走法,一定是n-1級別的台階的所有的走法和n-2級別台階的所有走法之和。
F(n) = F(n-1) + F(n-2); 關於狀態的分解,更詳細的說明,可以看這篇文章:http://blog.csdn.net/baidu_37107022/article/details/73188963。 作者講的非常的通俗易懂。佩服這么辛苦的編輯。
Java的代碼實現
public static int getSumStep(int n){ if(n < 1){ return 0; } else if(n == 1){ return 1; } else if(n == 2){ return 1; } else { int f1 = 1; int f2 = 1; int f = 0; for(int i=3; i<=n; ++i){ f = f1 + f2; f1 = f2; f2 = f; } return f; } }
-
二維的變量變化的動態規划算法,即最優解和遞推關系需要兩個維度變量來描述的,比如01背包問題,兩個字符串的公共子序列問題
這類問題通常需要兩個維度的變量,狀態的描述比較晦澀,不容易理解,遞推關系不是很直觀。我自己的學習方法是牢記一個例子,這里以01背包問題為例:
問題描述:有編號分別為a,b,c,d的四件物品,它們的重量分別是2,3,4,5,它們的價值分別是3,4,5,6,現在給你個承重為8的背包,如何讓背包里裝入的物品具有最大的價值總和?
編號 | a | b | c | d |
w(重量) | 2 | 3 | 4 | 5 |
v(價值) | 3 | 4 | 5 | 6 |
這類問題我覺得抽象的比較好的一篇文章是這篇文章:
http://www.cnblogs.com/Christal-R/p/Dynamic_programming.html, 不過我當時是在手機上看到的,好了好久才找到這篇文章。
作者抽象的實在太好了,我覺得我都沒法用語言去寫出這么嚴格的數學公式表達和證明,這里就不贅述了。
下面寫的,僅供自己理解使用, 總結下來就是:
Xi的取值為0,1 ;表示物品是否選取, i的取值為 1,2,3,4表示a,b,c,d4見物品
Wi表示物品的重量, w1=2, 表示 a物品的重量為2
Vi 表示物品的價值, v3 - 5, 表示物品c的價值為5;
其中n 表示 前 n個物品,這個表述是很重要的,如果是第一次思考這個問題,很多人都會卡在這里,
m表示背包的重量;
約束條件:
遞推關系:
第一個公式表示 n == 0 或者 m == 0 , 即物品的數量為0 或者背包的重量為0的時候,可以算是起始條件
第二個公式表示: 表示包的重量小於新增加的物品, 新增加的物品,無法裝入,如下圖的F(2, 2 ) 表示前兩個物品,包的重量2 , 2 < (w[2] = 3), 此時F(2,2 )= F(1 , 2) = 3;
第三個公式表示: 包的重量能夠容納w[n],新增加的物品,這個時候,最大的價值就要在 F(n-1, m) 和 F(n-1, m- Wn) + V[n]) 這兩個價值中選取了。
舉例如下圖打表的 F(4, 8), 因為 8 - (w[n] ,4) > 0 F(4, 8) = max(F(3, 8), F(3,3 ) + v[4]) = 10;
表的過程如下:
java代碼如下:
public static int getMaxValue(int[] wArray, int[] vArray, int bagWeight){ int lenght = wArray.length; // init set zero // manipulator the talbe int [][] result = new int[lenght+1][bagWeight+1]; int [][] bRecord = new int[lenght+1][bagWeight+1]; for(int i=1; i<= lenght; ++i){ for(int j=1;j <= bagWeight; ++j){ if(j<wArray[i-1]){ result[i][j] = result[i-1][j]; bRecord[i][j] = 1; }else{ if( result[i-1][j] > result[i-1][j-wArray[i-1]]+ vArray[i-1]) { result[i][j] = result[i-1][j]; bRecord[i][j] = 1; } else{ result[i][j] = result[i-1][j-wArray[i-1]]+ vArray[i-1]; bRecord[i][j] = 2; } } } } return result[lenght][bagWeight]; //return bRecord; }
需要注意的是因為java數組的索引下標為從0,開始,所以
result[i][j] = result[i-1][j-wArray[i-1]]+ vArray[i-1];
brecord是記錄操作的過程,用於回溯使用,這部分代碼,后續實現。
-
帶有額外條件的動態規划問題(這類問題,我暫時還沒有學習)
4. 動態規划與分治法的區別和聯系
分治法是指將問題划分成一些獨立地子問題,遞歸地求解各子問題,然后合並子問題的解而得到原問題的解。
動態規划適用於子問題獨立且重疊的情況,也就是各子問題包含公共的子子問題。動態規划算法對每個子子問題只求解一次,將其結果保存在一張表中,從而避免每次遇到各個子問題時重新計算答案。
分治法主要在於子問題的獨立性,比如排序算法等, 動態規划算法主要適用於處理 子問題重復性和最優子結構的的問題。
目前的理解還比較淺顯,只能先這么記錄了。