(基於Java)算法之動態規劃——矩陣連乘問題

動態規劃(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 }

  咱們會發現,該算法其實跟第一種方法幾乎相同,可是這個算法是直接用數組來存放子問題的最優解,跟備忘錄方法稍有不一樣,並且這個算法並沒有使用遞歸。並且這個填表法其實須要我的有比較強的能力,咱們能夠先舉個簡單點的例子,而後本身畫一個二維數組,分析第一個數是填入到哪裏,第二個數是填入到哪裏,如此重複,找出規律後就能夠開始編寫本身的填表法了,在這裏的是自下而上的填表法

  到這裏結束了,若是有不對的地方或者對這個算法有更好的建議,歡迎指出!

相關文章
相關標籤/搜索