解法一:动态规划
1、分析最优解的结构
计算A[1:n]的最优次序所包含的计算矩阵子链A[1:k]和A[k+1:n]的次序也是最优的。
矩阵连乘积计算次序问题的最优解包含着其子问题的最优解。这种性质称为最优子结构性质。
2、建立递归关系
设计算A[i:j],1<=i<=j<=n,所需要的最少数乘次数为m[i][j],则原问题的最优值为m[1][n]。
m[i][j]= { 0 i=j
{ min{ m[i][k]+m[k+1][j]+p(i-1)*p(k)*p(j)} i<j
ps: i<=k<j
若将对应于m[i][j]的断开位置k记为s[i][j],在计算出最优值m[i][j]后,可递归地由是s[i][j]构造出相应的最优解。
3、计算最优值
p表示矩阵的维数数组。例如,A1(30*35)*A2(35*15)*A3(15*5),则p为{30,35,15,5}
void MatrixChain(int *p, int n, int ** m, int ** s){
for(int i=1;i<=n;i++) m[i][i]=0;
for(int r=2;r<=n;r++) //r个矩阵一组相乘
for(int i=1;i<=n-r+1;i++){ //共有n-r+1组
int j=i+r-1; //i,j表示每组矩阵的头尾矩阵标号
//以下为针对m[i][j]的所有相乘顺序
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j]; //第一种相乘顺序,m[i][i]=0省略
s[i][j]=i;
for(int k=i+1;k<j;k++){ //剩余j-i-1种相乘顺序。
int t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; //p[i-1]*p[k]*p[j]表示矩阵m[i][k]和m[k+1][j]相乘所需要的数乘次。联系p的结构易知。
if(t<m[i][j]) { m[i][j]=t; s[i][j]=k;} //记录最小数乘数和断开位置。
}
}
}
}
//根据断点矩阵,输出计算A[i:j]的最优次序。
//虽然原理大概理解,但是递归层次还没搞清楚。。。待续
void Traceback(int i, int j, int ** s){
if(i==j) return;
Trackbook(i,s[i][j],s);
Tracebook(s[i][j]+1,j,s);
cout<<"Multiply A"<<i<<","<<s[i][j];
cout<<" and A"<<(s[i][j]+1)<<","<<j<<endl;
}
解法二:备忘录方法
与动态规划算法不同的是,备忘录方法的递归方式是自顶向下的,而动态规划算法则是自底向上递归的。
备忘录方法的控制结构与直接递归相同,区别在于备忘录方法为每个解过的子问题建立了备忘录以备需要时查看,避免了相同子问题的重复求解。
int MemoizedMatrixChain(int n, int ** m, int ** s){
for(int i=1;i<=n;i++)
for(int j=i;j<=n;j++) m[i][j]=0;
return LookupChain(1,n);
}
int LookupChain(int i,int j){
if(m[i][j]>0) return m[i][j];
if(i==j) return 0;
int u=LookupChain(i,i)+LookupChain(i+1,j)+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1;k<j;k++){
int t=LookupChain(i,k)+LookupChain(k+1,j)+p[i-1]*p[k]*p[j];
if(t<u) { u=t; s[i][j]=k;}
}
m[i][j]=u;
return u;
}
总结:
当一个问题的所有子问题都至少要解一次时,用动态规划算法比用备忘录方法好。此时,动态规划算法没有任何多余的计算。
当子问题空间中的部分子问题可不必求解时,用备忘录方法比较有利,因为从其结构可以看出,该方法只解那些确实需要求解的子问题。
ps:以上大部分内容都来自 计算机算法设计与分析(第3版) 王晓东 编著
这是本人博客的处女作~,从此开始记录自己漫长学习道路上的点点滴滴。不再让过去的成为逝去的。及时总结,及时行乐!进步一点点,快乐一点点!