计算三个矩阵连乘{A1,A2,A3},维数分别为10*100 , 100*5 , 5*50
按此顺序计算需要的次数((A1*A2)*A3):10X100X5+10X5X50=7500次
按此顺序计算需要的次数(A1*(A2*A3)):10X5X50+10X100X50=75000次
所以问题是:如何确定运算顺序,可以使计算量达到最小化。枚举显然不可,如果枚举的话,相当于一个“完全加括号问题”,次数为卡特兰数,卡特兰数指数增长,必然不行。
子问题状态的建模(很关键):
将AiAi+1...Aj简记为A[i:j],(0<=i<=j<=n-1),所需的最少数乘次数为m[i][j],则原问题的最优值为m[0][n-1]
显然如果i=j,则m[i][j]这段中就一个矩阵,需要计算的次数为0;
如果i>j,则m[i][j]=min{m[i][k]+m[k+1][j]+p[i-1]Xp[k]Xp[j]},其中k,在i与j之间游荡,所以i<=k<j ;
辅助数组s来记录计算m[i][j]时取得最优代价处的k的值。
m和s数组的计算顺序如图所示(只用到上三角矩阵):
#include<iostream> #include<iomanip> #define SIZE 6 //控制参与连乘矩阵的个数 using namespace std; /* * 数组m记录最少数乘次数,s记录断开的位置k * AiAi+1...Aj简记为A[i:j],(0<=i<=j<=n-1),所需的最少数乘次数为m[i][j],则原问题的最优值为m[0][n-1] */ int m[SIZE][SIZE],s[SIZE][SIZE]; void MatrixChain(int p[],int n) { for(int i=0;i<n;i++)//i=j时为单一矩阵,即矩阵链长度为1,无需计算 m[i][i]=0; for(int l=2;l<=n;l++)//l为矩阵链长度,从2开始循环 { for(int i=0;i<n-l+1;i++)//l长度的矩阵链的第一个矩阵, 即A[i:j]中的i { int j=i+l-1;//根据当前的长度算出矩阵链的最后一个矩阵 m[i][j]=m[i+1][j]+p[i]*p[i+1]*p[j+1]; s[i][j]=i+1; for(int k=i+1;k<j;k++)//遍历所有断开位置k { int min=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1];//寻找最小值 if(min<m[i][j]) { m[i][j]=min; s[i][j]=k+1; } } } } } void Traceback(int i,int j) //找出s数组中记录的最优断开点 { if(i==j) cout<<"A"<<i+1; else { cout<<"("; Traceback(i,s[i][j]-1); Traceback(s[i][j],j); cout<<")"; } } int main() { system("title 矩阵连乘问题"); int p[SIZE+1]={30,35,15,5,10,20,25}; //p记录矩阵的行列数 MatrixChain(p,SIZE); cout<<"输出m矩阵为:"<<endl; for(int i=0;i<SIZE;i++) { for(int j=0;j<SIZE;j++) { cout<<setw(5)<<m[i][j]<<" "; } cout<<endl; } cout<<endl<<endl; cout<<"输出s矩阵为:"<<endl; for(int i=0;i<SIZE;i++) { for(int j=0;j<SIZE;j++) { cout<<setw(5)<<s[i][j]<<" "; } cout<<endl; } cout<<"\n矩阵连乘加括号方式为:"; Traceback(0,SIZE-1); return 0; }
参考 http://www.cnblogs.com/liushang0419/archive/2011/04/27/2030970.html
参考《算法导论》 P197-201