動態規劃(Dynamic Programming):與分治法相似,其基本思想也是將待求解問題分解成若干個子問題,先求解子問題,而後從這些子問題的解獲得原問題的解。與分治法不一樣的是,適用於動態規劃法求解的問題,經分解獲得的子問題每每不是互相獨立的。算法
使用動態規劃法求解的問題須要符合一些條件:數組
(1):所求解問題必需要符合最優子結構;(最優子結構即:原問題的最優解中包含了子問題的最優解)spa
(2):原問題分解出來的子問題相互之間存在聯繫,即遞歸時會重複解決以前已解決過的子問題。code
先說明一些前提:blog
(1):矩陣相乘的條件是:前一個矩陣的行數=後一個矩陣的列數;遞歸
(2):能夠用數組p[0:n]來存放n個連乘矩陣的行數和列數(p[i-1]表示Ai的行數,p[i]表示Ai的列數);class
(3):用A[i:j]表示Ai連乘到Aj,假設最優的加括號方式(最外層)是:(Ai*……*Ak)(Ak+1*……*Aj) 。方法
下面咱們先從動態規劃解決矩陣連乘問題的最初始的方法入手,代碼以下:二維數組
1 private static int recurMatrixChain(int i,int j,int[] p) //最初始的矩陣連乘問題算法 2 { 3 if(i == j) return 0; //i == j,即只有一個矩陣,計算次數固然爲零 4 int min = recurMatrixChain(i,i,p) + recurMatrixChain(i+1,j,p) + p[i-1] * p[i] * p[j]; 5 for(int k = i + 1; k < j; k++){ 6 int t = recurMatrixChain(i,k,p) + recurMatrixChain(k+1,j,p) + p[i-1] * p[k] * p[j]; 7 if(t < min) min = t; //從k處斷開,若是t比min更小,則說明存在更優的解決方法,把t賦值給min 8 } 9 return min; 10 }
遞歸掌握得好的話,結合註釋,理解起來並不會以爲困難。但這個方法存在一個嚴重的弊端,雖然能計算出最優解,可是在計算過程當中,其實調用了指數級別次的方法,而這麼屢次調用其實都在解決重複子問題而已。static
下面介紹一種能解決上邊提到的問題,動態規劃解決矩陣連乘問題的第二種方法——備忘錄方法,代碼以下:
1 private static int lookupChain(int i,int j,int[][] m,int[] p) 2 { 3 if(m[i][j] > 0) return m[i][j]; //若是m[i][j]非零,則說明該子問題被計算過,只需取出這個數,無需進行計算 4 if(i == j) return 0; 5 int min = lookupChain(i,i,m,p) + lookupChain(i+1,j,m,p) + p[i-1] * p[i] * p[j]; 6 for(int k = i + 1; k < j; k++){ 7 int t = lookupChain(i,k,m,p) + lookupChain(k+1,j,m,p) + p[i-1] * p[k] * p[j]; 8 if(t < min) min = t; 9 } 10 m[i][j] = min; //對於未記錄的子問題,經過計算把該子問題的最優解求出後,存放在數組中 11 return min; 12 } 13 14 private static int memoizedMatrixChain(int n,int[][] m,int[] p) //這個就是解決矩陣連乘問題的備忘錄方法 15 { 16 for(int i = 1; i <= n; i++) 17 for(int j = 1; j <= n; j++) 18 m[i][j] = 0; //對數組進行初始化 19 return lookupChain(1,n,m,p); 20 }
咱們能夠看出,lookupChain方法跟recurMatrixChain方法其實差很少,只是加插了兩行代碼而已,而這就是兩種算法之間的不一樣之處,就是最重要的地方。第一次調用時跟第一種方法同樣,同時記錄了子問題的最優解,當第二次調用時,即可以直接從備忘錄中提取子問題的最優解,大大地減小了方法的調用次數。
下面介紹另外一種動態規劃方法——填表法,個人老師將其稱爲真·動態規劃,代碼以下:
1 private static void matrixChain(int n,int[][] m,int[] p){ //填表法,個人老師也叫這方法爲真·動態規劃方法 2 for(int i = n-1; i >= 1; i--) 3 for(int j = i+1; j <= n; j++){ 4 int min = m[i][j] + m[i+1][j] + p[i-1] * p[i] * p[j]; 5 for(int k = i+1; k < j; k++){ 6 int t = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j]; 7 if(t < min) min = t; 8 } 9 m[i][j] = min; 10 } 11 }
咱們會發現,該算法其實跟第一種方法幾乎相同,可是這個算法是直接用數組來存放子問題的最優解,跟備忘錄方法稍有不一樣,並且這個算法並沒有使用遞歸。並且這個填表法其實須要我的有比較強的能力,咱們能夠先舉個簡單點的例子,而後本身畫一個二維數組,分析第一個數是填入到哪裏,第二個數是填入到哪裏,如此重複,找出規律後就能夠開始編寫本身的填表法了,在這裏的是自下而上的填表法。
到這裏結束了,若是有不對的地方或者對這個算法有更好的建議,歡迎指出!