矩陣鏈乘問題描述
給定n個矩陣構成的一個鏈<A1,A2,A3,.......An>,其中i=1,2,...n,矩陣A的維數為pi-1pi,對乘積 A1A2...An 以一種最小化標量乘法次數的方式進行加全部括號。
注意:在矩陣鏈乘問題中,實際上並沒有把矩陣相乘,目的是確定一個具有最小代價的矩陣相乘順序。找出這樣一個結合順序使得相乘的代價最低。
動態規划分析過程
1)最優加全部括號的結構
動態規划第一步是尋找一個最優的子結構。假設現在要計算AiAi+1....Aj的值,計算Ai...j過程當中肯定會存在某個k值(i<=k<j)將Ai...j分成兩部分,使得Ai...j的計算量最小。分成兩個子問題Ai...k和Ak+1...j,需要繼續遞歸尋找這兩個子問題的最優解。
有分析可以到最優子結構為:假設AiAi+1....Aj的一個最優加全括號把乘積在Ak和Ak+1之間分開,則Ai..k和Ak+1..j也都是最優加全括號的。
2)一個遞歸解
設m[i,j]為計算機矩陣Ai...j所需的標量乘法運算次數的最小值,對此計算A1..n的最小代價就是m[1,n]。現在需要來遞歸定義m[i,j],分兩種情況進行討論如下:
當i==j時:m[i,j] = 0,(此時只包含一個矩陣)
當i<j 時:從步驟1中需要尋找一個k(i≤k<j)值,使得m[i,j] =min{m[i,k]+m[k+1,j]+pi-1pkpj} (i≤k<j)。
3)計算最優代價
雖然給出了遞歸解的過程,但是在實現的時候不采用遞歸實現,而是借助輔助空間,使用自底向上的表格進行實現。設矩陣Ai的維數為pi-1pi,i=1,2.....n。輸入序列為:p=<p0,p1,...pn>,length[p] = n+1。使用m[n][n]保存m[i,j]的代價,s[n][n]保存計算m[i,j]時取得最優代價處k的值,最后可以用s中的記錄構造一個最優解。書中給出了計算過程的偽代碼,摘錄如下:
MAXTRIX_CHAIN_ORDER(p) 2 n = length[p]-1; 3 for i=1 to n 4 do m[i][i] = 0; 5 for t = 2 to n //t is the chain length 6 do for i=1 to n-t+1 7 j=i+t-1; 8 m[i][j] = MAXLIMIT; 9 for k=i to j-1 10 q = m[i][k] + m[k+1][i] + qi-1qkqj; 11 if q < m[i][j] 12 then m[i][j] = q; 13 s[i][j] = k; 14 return m and s;
MATRIX_CHAIN_ORDER具有循環嵌套,深度為3層,運行時間為O(n3)。如果采用遞歸進行實現,則需要指數級時間Ω(2n),因為中間有些重復計算。
4)構造一個最優解
第三步中已經計算出來最小代價,並保存了相關的記錄信息。因此只需對s表格進行遞歸調用展開既可以得到一個最優解。書中給出了偽代碼,摘錄如下:
PRINT_OPTIMAL_PARENS(s,i,j) if i== j then print "Ai" else print "("; PRINT_OPTIMAL_PARENS(s,i,s[i][j]); PRINT_OPTIMAL_PARENS(s,s[i][j]+1,j); print")";
5)編程實現
public class Q1_Matrix_chain { public static int[] atest ={30,35,15,5,10,20,25}; public static int[] a={3, 5, 2, 1, 10}; public static int[] b={2, 7, 3, 6, 10}; public static int[] c={10, 3, 15, 12, 7, 2}; public static int[] d={7, 2, 4, 15, 20, 5}; public static void main(String[] args) { System.out.println("<3, 5, 2, 1,10>"); Matrix_Chain_Order(a); System.out.println("<2, 7, 3, 6, 10>"); Matrix_Chain_Order(b); System.out.println("<10, 3, 15, 12, 7, 2>"); Matrix_Chain_Order(c); System.out.println("<7, 2, 4, 15, 20, 5>"); Matrix_Chain_Order(d); } public static void Matrix_Chain_Order(int[] a){ int n = a.length-1; int[][] m = new int[n+1][n+1]; int[][] s = new int[n+1][n+1]; int i,j,k,t; for (i=0;i<=n;i++) m[i][i] = 0; for (i=0;i<=n;i++) s[i][i] = 0; for(t=2; t<=n; t++) //t is the chain length { for(i=1;i<=n-t+1;i++)//從第一矩陣開始計算,計算長度為t的最小代價 { j = i+t-1;//長度為t時候的最后一個元素 m[i][j] = 1000000;//初始化為最大代價 for(k=i;k<=j-1;k++)//尋找最優的k值,使得分成兩部分k在i與j-1之間 { int temp = m[i][k]+m[k+1][j] + a[i-1]*a[k]*a[j]; if(temp < m[i][j]) { m[i][j] = temp; //記錄下當前的最小代價 s[i][j] = k; //記錄當前的括號位置,即矩陣的編號 } } } } System.out.println("一個最優解為:"); Display(s,1,n); System.out.println("\n計算的次數為:"); System.out.println(m[1][n]); } public static void Display(int[][] s,int i,int j) { if( i == j) { System.out.print('A'); System.out.print(i); } else { System.out.print('('); Display(s,i,s[i][j]); Display(s,s[i][j]+1,j); System.out.print(')'); } } }