白天什么也沒學,晚上才終於拿着筆,對着代碼,寫寫畫畫,終於看明白是怎么計算的了。
以這6個矩陣連乘作為例子
A1 | A2 | A3 | A4 | A5 | A6 |
30*35 | 35*15 | 15*5 | 5*10 | 10*20 | 20*25 |
1 首先,要明白兩個矩陣相乘所需要做的乘法次數:
2 由於連乘的矩陣必須滿足,前一個矩陣的列數=后一個矩陣的行數,所以可以使用一個數組來存儲連乘矩陣的行列數:
p[7]={30,35,15,5,10,20,25};
3 分析最優解的結構:
A[i:j]表示矩陣i到矩陣j的連乘,那么A[i:j]=A[i:k]+A[k+1:j]+p[i-1]*p[k]*p[j](這兩個矩陣相乘所需的乘法次數);
如果A[i:j]的所需要的乘法次數是最少的,那么A[i:k]和A[k+1:j]所需要的乘法次數也應該是最少的,否則A[i:j]就不是最優的,所以滿足最優子結構性質;
4 建立遞歸關系:
m[i][j]表示矩陣i連乘到矩陣j的最少乘次數,那么原問題的最優值是m[1,n];
當i=j時,為單個矩陣,m[i][i]=0;
當i<j時,利用最優子結構性質來計算;
獲得以下遞歸定義:
m[i][j]=0; i=j
m[i][j]=mini<=k<j{m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]}; i<j
如果需要得出最優解的加括號結果,需要另一個二維數組s[i][j],用於記錄使m[i][j]獲得最優解的斷點,k值;
5 分析代碼
1 int m[8][8]={0},s[8][8]={0}; 2 3 void MaxtrixChain(int *p,int n){ 4 for(int i=1;i<=n;i++) m[i][i]=0; 5 for(int r=2;r<=n;r++) 6 for(int i=1;i<=n-r+1;i++){ 7 int j=i+r-1; 8 m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j]; 9 s[i][j]=i; 10 11 for(int k=i+1;k<j;k++){ 12 int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; 13 if(t<m[i][j]){ 14 m[i][j]=t; 15 s[i][j]=k; 16 } 17 } 18 } 19 }
書上寫了“依據其遞歸式自底向上的方式進行計算”,沒拿出筆畫之前,我真的沒看懂這句話到底是怎么自底向上的,於是我要開始寫寫畫畫了;
我們先暫時只看下面這部分代碼:
void MaxtrixChain(int *p,int n){ for(int i=1;i<=n;i++) m[i][i]=0; for(int r=2;r<=n;r++){ for(int i=1;i<=n-r+1;i++){ int j=i+r-1; m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j]; }
} }
具體用數據分析一下
把右圖補充完整,將得到以下結果:這是我理解的自底向上地計算;
接下來看另一部分代碼:
for(int r=2;r<=n;r++){ for(int i=1;i<=n-r+1;i++){ int j=i+r-1; for(int k=i+1;k<j;k++){ int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; if(t<m[i][j]){ m[i][j]=t; s[i][j]=k; } } } }
所以我們需要把這兩部分代碼結合起來使用:
那么對於m[1][4]我們是枚舉了所有加括號的可能情況,並存儲了其中最小的一個,獲得了最優值;
那么對於m[2][5]、m[3][6]也能得到最優值;並且沒有做任何重復計算;
當r=5時,m[1][5]、m[2][6]也能得到最優值;
當r=6時,m]1][6]也能得到最優值;如此就得到了原問題的最優解;
每一次得到更小值的時候,s[i][j]=k,記錄(更新)這個斷點情況;最后可以用來幫助構造最優解的加括號形式;
6 以下給出完整源代碼:
1 #include<iostream> 2 using namespace std; 3 4 //矩陣連乘:完全加括號問題 5 //A1(p1*p2),A2(p2*p3),兩個矩陣相乘,總乘法次數為p1*p2*p3,其中p2表示結果的每個元素所需要的乘法次數 6 //m[i][j]表示,第i個矩陣到第j個矩陣的連乘,所需要的乘法次數 7 //對於多個矩陣連乘,可以得出以下遞推式 8 //m[i][j]=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j] 9 //問題是如何找到這個k,使乘法次數最少 10 int m[7][7]={0}; //m[i][j]用於存儲,第i個矩陣連乘到第j個矩陣的乘法次數,那么m[1][n]為最終結果 11 int s[7][7]={0}; //s[i][j]用於存儲,矩陣i和矩陣j之間所取的斷點,k值,用於構造結果的加括號情況 12 13 void MatrixChain(int n,int p[]){ 14 for(int i=1;i<=n;i++) m[i][i]=0; //單個矩陣乘法次數為0 15 16 //采用自底向上的方式實現遞推式 17 for(int r=2;r<=n;r++){ //表示r個矩陣的連乘 18 for(int i=1;i<=n-r+1;i++){ //從第i個矩陣開始,作為連乘的起點,有r個矩陣連乘,所以要<=n-r+1 19 int j=i+r-1; //j-i=r-1,表示j是r個矩陣連乘中的終點 20 m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j]; 21 //相當於m[i][j]=m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j],顯然m[i][i]=0 22 //即表示Ai……Aj=Ai(Ai+1……Aj),從最后一個開始加括號 23 s[i][j]=i; 24 25 for(int k=i+1;k<j;k++){ 26 int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; //在i~j中尋找k 27 if(t<m[i][j]){ 28 m[i][j]=t; //記錄t歷史上的最小值,最終結果即為連乘中的最優值 29 s[i][j]=k; //記錄斷點k值 30 } 31 } 32 33 } 34 } 35 } 36 37 //利用s[i][j]進行構造連乘的加括號情況 38 void TraceBack(int i,int j){ 39 if(i==j) return; 40 TraceBack(i,s[i][j]); //以s[i][j]記錄的k為分界點,找左邊,i~k 41 TraceBack(s[i][j]+1,j); //找右邊,k+1~j 42 cout<<"Matrix A"<<i<<","<<s[i][j]; 43 cout<<" and A"<<s[i][j]+1<<","<<j<<endl; 44 } 45 46 int main(){ 47 int arr[7]={30,35,15,5,10,20,25}; 48 int n=6; 49 MatrixChain(n,arr); 50 TraceBack(1,n); 51 cout<<m[1][n]<<endl; 52 system("pause"); 53 return 0; 54 }
哇本渣終於弄清楚矩陣連乘了!!