【問題描述】ios
給定n個矩陣{A1,A2,…,An},其中Ai與Ai+1是可乘的,i=1,2…,n-1。如何肯定計算矩陣連乘積的計算次序,使得依這次序計算矩陣連乘積須要的數乘次數最少。例如,給定三個連乘矩陣{A1,A2,A3}的維數分別是10*100,100*5和5*50,採用(A1A2)A3,乘法次數爲10*100*5+10*5*50=7500次,而採用A1(A2A3),乘法次數爲100*5*50+10*100*50=75000次乘法,顯然,最好的次序是(A1A2)A3,乘法次數爲7500次。算法
分析:
矩陣鏈乘法問題描述:
給定由n個矩陣構成的序列{A1,A2,...,An},對乘積A1A2...An,找到最小化乘法次數的加括號方法。spa
1)尋找最優子結構
此問題最難的地方在於找到最優子結構。對乘積A1A2...An的任意加括號方法都會將序列在某個地方分紅兩部分,也就是最後一次乘法計算的地方,咱們將這個位置記爲k,也就是說首先計算A1...Ak和Ak+1...An,而後再將這兩部分的結果相乘。
最優子結構以下:假設A1A2...An的一個最優加括號把乘積在Ak和Ak+1間分開,則前綴子鏈A1...Ak的加括號方式一定爲A1...Ak的一個最優加括號,後綴子鏈同理。
一開始並不知道k的確切位置,須要遍歷全部位置以保證找到合適的k來分割乘積。code
2)構造遞歸解
設m[i,j]爲矩陣鏈Ai...Aj的最優解的代價,則blog
3)構建輔助表,解決重疊子問題
從第二步的遞歸式能夠發現解的過程當中會有不少重疊子問題,能夠用一個nXn維的輔助表m[n][n] s[n][n]分別表示最優乘積代價及其分割位置k 。
輔助表s[n][n]能夠由2種方法構造,一種是自底向上填表構建,該方法要求按照遞增的方式逐步填寫子問題的解,也就是先計算長度爲2的全部矩陣鏈的解,而後計算長度3的矩陣鏈,直到長度n;另外一種是自頂向下填表的備忘錄法,該方法將表的每一個元素初始化爲某特殊值(本問題中能夠將最優乘積代價設置爲一極大值),以表示待計算,在遞歸的過程當中逐個填入遇到的子問題的解。遞歸
第一種 自底向上 的方式io
1 #include<iostream> 2 using namespace std; 3 4 const int N=7; 5 //p爲矩陣鏈,p[0],p[1]表明第一個矩陣,p[1],p[2]表明第二個矩陣,length爲p的長度 6 //因此若是有六個矩陣,length=7,m爲存儲最優結果的二維矩陣,s爲存儲選擇最優結果路線的 7 //二維矩陣 8 void MatrixChainOrder(int *p,int m[N][N],int s[N][N],int length) 9 { 10 int n=length-1; 11 int l,i,j,k,q=0; 12 //m[i][i]只有一個矩陣,因此相乘次數爲0,即m[i][i]=0; 13 for(i=1;i<length;i++) 14 { 15 m[i][i]=0; 16 } 17 //l表示矩陣鏈的長度 18 // l=2時,計算 m[i,i+1],i=1,2,...,n-1 (長度l=2的鏈的最小代價) 19 for(l=2;l<=n;l++) 20 { 21 for(i=1;i<=n-l+1;i++) 22 { 23 j=i+l-1; //以i爲起始位置,j爲長度爲l的鏈的末位, 24 m[i][j]=0x7fffffff; 25 //k從i到j-1,以k爲位置劃分 26 for(k=i;k<=j-1;k++) 27 { 28 q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; 29 if(q<m[i][j]) 30 { 31 m[i][j]=q; 32 s[i][j]=k; 33 } 34 } 35 } 36 } 37 cout << m[1][N-1] << endl; 38 } 39 void PrintAnswer(int s[N][N],int i,int j) 40 { 41 if(i==j) 42 { 43 cout<<"A"<<i; 44 } 45 else 46 { 47 cout<<"("; 48 PrintAnswer(s,i,s[i][j]); 49 PrintAnswer(s,s[i][j]+1,j); 50 cout<<")"; 51 } 52 } 53 int main() 54 { 55 int p[N]={30,35,15,5,10,20,25}; 56 int m[N][N],s[N][N]; 57 MatrixChainOrder(p,m,s,N); 58 PrintAnswer(s,1,N-1); 59 return 0; 60 }
對於 p={30 35 15 5 10 20 25}:class
計算順序爲:效率
第二種 自頂向下 (備忘錄)的方式stream
#include<iostream> using namespace std; const int N=7; int MatrixChainOrder2(int *p,int m[N][N],int s[N][N],int i, int j) { if (i == j) { return 0; } if (m[i][j] < 0x7fffffff) { return m[i][j]; } for (int k=i; k<j; ++k) { int tmp = MatrixChainOrder2(p,m,s,i,k) + MatrixChainOrder2(p,m,s,k+1,j) + p[i-1]*p[k]*p[j]; if (tmp < m[i][j]) { m[i][j] = tmp; s[i][j] = k; } } return m[i][j]; } void PrintAnswer(int s[N][N],int i,int j) { if(i==j) { cout<<"A"<<i; } else { cout<<"("; PrintAnswer(s,i,s[i][j]); PrintAnswer(s,s[i][j]+1,j); cout<<")"; } } int main() { int p[N]={30,35,15,5,10,20,25}; int m[N][N],s[N][N]; for (int i=0; i<N; ++i) { for (int j=0; j<N; ++j) m[i][j] = 0x7fffffff; } cout << MatrixChainOrder2(p,m,s,1,6)<<endl; PrintAnswer(s,1,N-1); return 0; }
矩陣連乘計算次序問題的最優解包含着其子問題的最優解。這種性質稱爲最優子結構性質。
•在分析問題的最優子結構性質時,所用的方法具備廣泛性:首先假設由問題的最優解導出的子問題的解不是最優的,而後再設法說明在這個假設下可構造出比原問題最優解更好的解,從而致使矛盾。
•利用問題的最優子結構性質,以自底向上的方式遞歸地從子問題的最優解逐步構造出整個問題的最優解。最優子結構是問題能用動態規劃算法求解的前提。
•遞歸算法求解問題時,每次產生的子問題並不老是新問題,有些子問題被反覆計算屢次。這種性質稱爲子問題的重疊性質。
•動態規劃算法,對每個子問題只解一次,然後將其解保存在一個表格中,當再次須要解此子問題時,只是簡單地用常數時間查看一下結果。
•一般不一樣的子問題個數隨問題的大小呈多項式增加。所以用動態規劃算法只須要多項式時間,從而得到較高的解題效率。