矩阵连乘问题解题报告
1.分析最优解的结构
将矩阵乘积A(i)*A(i+1)*...*A(j)记为A[i:j],则原问题为计算A[1:n]的最有计算次序。
假设在A(k)和A(k+1)之间将矩阵链断开,其中1 <= k < n,则其相应的完全加括号式为(A(1)...A(k))*(A(k+1)...A(n)),
则依此计算顺序总计算量为计算A[1:k]的计算量加上计算A[k+1:n]的计算量再加上A[1:k]和A[k+1:n]相乘的计算量,其中两个矩阵( 如A(p)和A(q) )相乘的计算量为Row(p)*Col(p)*Col(q),其中Row(q)与Col(p)相等。
这个问题的一个关键特征是:计算A[1:n]的最优次序所包含的计算矩阵A[1:k]和A[k+1:n]的次序也是最优的,因此矩阵连乘积计算次序问题的最优解包含着其子问题的最优解,这是用动态规划算法求解的显著特征。
2.建立递归关系
假设计算A[i:j],其中1<=i<=j<=n,所需的最少数乘次数为m[i][j],则原问题的最优解为m[1][n],
当i=j时,即单一矩阵连乘时,无需计算m[i][i] = 0, i = 1, 2, 3, ...,n。
当i<j时,m[i][j] = min{ m[i][k] + m[k+1][j] + Row(i:k)*Col(i:k)*Col(k+1:j) },其中k的位置为从i到 j-1共j-i种可能。
设数组p[n],其中p[i]表示矩阵A(i)的列数,则m[i][j]可递归地定义为m[i][j] = 0 / min{ m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j] },
因为矩阵A[i:k]的行数等于矩阵A[i-1]的列数,矩阵A[k+1:j]的列数等于矩阵A[j]的列数。
因此m[i][j]给出了最优解,且确定了计算A[i:j]的最优次序中的断开位置k,将断开位置k记为s[i][j],在计算出最优值m[i][j]后,可递归地由s[i][j]够造出相应的最优解。
3.计算最优值
由上述可见,在递归计算时,许多子问题被重复计算多次,因此可在计算过程中保存已解决的子问题答案,每个子问题只计算一次,而在后面需要时只要简单查一下,从而避免了大量的重复计算。
主要算法程序如下:
//p数组表示每个矩阵的列数
//m[i][j]表示计算A[i:j]所需最少数乘次数
void matrixChain(int n, int *p, int **m, int **s)
{
//单一矩阵时,无需计算,因此m[i][i] = 0
for(int i = 1; i <= n; i++)
m[i][i] = 0;
//r表示链长,从2到n
for(int r = 2; r <= n; r++)
{
//链的起始位置
for(int i = 1; i <= n; i++)
{
//链的结束位置
int j = i + r - 1;
//以i为断开点
m[i][j] = m[i][i] + m[i+1][j] + p[i-1]*p[i]*p[j];
s[i][j] = i;
//以k为断开点,其中k从i+1到j-1
for(int k = i + 1; k < j; k++)
{
int temp = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
if(temp < m[i][j])
{
m[i][j] = temp;
s[i][j] = k;
}
}
}
}
}
4.构造最优解
算法matrixChain只是计算出了最优值,但未给出最优解,事实上matrixChain已记录了构造最优解所需要的全部信息。
s[i][j]中的数表明,计算矩阵链A[i:j]的最佳方式应在矩阵A(k)和矩阵A(k+1)之间断开,因此可递推得A[1:n]的最优完全加括号方式,即构造出问题的一个最优解。
主要算法程序如下:
void traceBack(int i, int j, int **s)
{
if(i == j)
return;
traceBack(i, s[i][j], s);
traceBack(s[i][j]+1, j, s);
printf("链应该断在k = %d 的位置上/n", s[i][j]);
}
}