一.定义
完全加括号的矩阵连乘积可递归定义为:
(1) 单个矩阵是完全加括号的;
(2) 矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)
例如,有四个矩阵ABCD相乘,那么所有的完全加括号结果为(A((BC)D)) (A(B(CD))) ((AB)(CD)) (((AB)C)D) (A(BC))D)
如果一个矩阵的连乘积的计算次序完全确定,也就是该连乘积已完全加括号,则可以依照此次序反复调用两个矩阵相乘的标准算法计算出矩阵连乘积。
二. 问题描述
给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,其中i=1,2,…n-1。如何确定矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的数乘次数最少。
三. 问题解决
- 穷举法
列举出所有可能的计算次序,并计算出每一种计算次序所需要的数乘次数,从中找到数乘次数最少的计算次序。
详细请看——暴力求解:输出矩阵连乘所有的完全加括号形式 - 动态规划
将矩阵链记为A[i:j],动态规划的思想跟分治法的思想类似,就是将待求解的问题分解成若干个子问题求解。我们把矩阵链从某个点断开,分为左右两个子矩阵链,分别记为A[i:k]和A[k+1:j],其中i<=k<j。我们可以发现到:计算A[i:j]的最优次序所包含A[i:k]和A[k+1:j]的计算次序也是最优的,很明显具有最优子结构性质,这种性质是可用动态规划算法求解问题的显著特征。
记矩阵链A[i:j]的最少数乘次数为m(i,j),我们可以发现,
-
当i=j时,矩阵链只有一个矩阵元素,那么m[i,j]=0。
-
当i!=j时,我们要考虑将矩阵链从某个点断开,使得以该方式断开后的数乘次数最少。
m[i,j]=m[i,k]+m[k+1,j]+p(i-1)p(k)p(j),i<=k<j
其中矩阵Ai的大小为p(i-1)xp(i)。这时候我们只要找到使m[i,j]最小的k值即可。
//p储存矩阵的大小,n为矩阵链的长度,m保存最少数乘次数,s保存最佳断开位置
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++)
{
int j=i+r-1;
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
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; //保存最佳断开位置
}
}
}
}
}
- 备忘录方法
在使用备忘录方法前我先来实现一下用递归来求解矩阵连乘的最少数乘次数。
int Matrix(int i, int j)
{
int minvalue =INT_MAX, temp;
if(i==j) return 0;
for(int k=i; k<j; k++)
{
temp=Matrix(i,k) + Matrix(k+1,j) + p[i-1]*p[k]*p[j];
if(temp<minvalue)
{
minvalue=temp;
m[i][j]=k;
}
}
return minvalue;
}
因为在递归求解中会有很多重复的计算,导致算法效率不高。备忘录的方法就是用来解决这个问题的,它为每个解过的子问题建立了备忘录以备需要时查看,避免了相同子问题的重复求解。
int LookupChain(int i, int j)
{
if(m[i][j]>0) return m[i][j]; //m[i][j]大于0表示该子问题已经求解过了,可以直接返回结果
if(i==j) return 0; //从前面分析可知为0
//如果子问题没有求解过
int u=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 = LookChain(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;
}