老规矩,我们先由题切入:
矩阵链乘法问题(matrix-chain multiplication problem)可描述如下:
给定n个矩阵的链<A1,A2,...,An>,其中,Ai和Ai+1是可乘的,( 矩阵Ai的规模为p(i-1)×p(i) (1<=i<=n) ),求完全括号化方案,使得计算乘积A1A2...An所需标量乘法次数最少。
即:
题干描述就是如此,除此之外,我们必须清楚矩阵的几个性质:
1.矩阵链式乘法满足结合律
2.计算同一个矩阵链的乘法有很多种,虽然最后结果一样,但是所需乘法的代价是不一样的
3.A是p×q的矩阵,B是q*r的矩阵,那么矩阵C=A*B是p×r的矩阵。C所需的标量乘法的次数为pqr。
我们举个例子:
接下来我们说解题思路:
首先定义m[i,j]:定义为矩阵Ai*A(i+1)*...*Aj所需乘法代价最小的次数。
所以:
pi-1*pk*pj是计算到最后两个矩阵(i-1)*k 和矩阵k*j相乘所需的乘法代价。
c++代码如下:
#include <iostream>
using namespace std;
const int intmax=2147483647;
int const M=8; //M为存储矩阵边的数组的大小,M=n+1(n为矩阵个数)
int MatrixChainOrder(int *p,int Length,int m[][M],int s[][M]) //Length为存储边的数组的长度,n=Length-1为矩阵个数
{
int q,n=Length-1;
for(int i=1;i<=n;i++) m[i][i]=0; //初始化矩阵m[][]都为0
for(int l=2;l<=n;l++) //l为要拆成的矩阵链的长度
{
for(int i=1;i<=n-l+1;i++)
{
int j=i+l-1; //为什么j=i+l-1呢,因为矩阵链长度最大为l,所以根据i我们可以得到j
m[i][j]=intmax; //m[i][j]初始值为0,因为后面要比较m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j] 和m[i][j]谁更小,所以我们在这里给m[i][j]赋一个很大的值
for(int k=i;k<=j-1;k++) //比较m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j] 和m[i][j]谁小
{
q=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(q<m[i][j]) //如果m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]更小(所需乘法次数更少),那么就更新m[i][j]=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j],并且将此时的k值储存在s[i][j]中
{
m[i][j]=q;
s[i][j]=k;
}
}
}
}
return m[1][n]; //最后结果
}
void PrintPath(int s[][M],int i,int j)
{
if(i == j) cout<<"A"<<i;
else
{
cout<<"(";
PrintPath(s,i,s[i][j]);
PrintPath(s,s[i][j]+1,j);
cout<<")";
}
}
int main()
{
int p[M]={30,35,15,5,10,20,25,10};//7个矩阵,但是存了8个边
int m[M][M],s[M][M];
int k=MatrixChainOrder(p,M,m,s);
cout<<"当有7个矩阵时,最优解为: \n"<<k<<endl;
PrintPath(s,1,7);
return 0;
}
输出结果:
代码详解:
涉及到两个函数:
1.(动态规划)MatrixChainOrder():得到最小乘法代价时的乘法的结合方式,并且求出最小乘法代价的数值
2.(递归)PrintPath():根据MatrixChainOrder()得到的s[i][j],得到输出乘法结合方式
代码讲解:
本代码涉及到的矩阵例子是:六个矩阵,如下图
1.函数MatrixChainOrder():得到最小乘法代价时的乘法的结合方式,并且求出最小乘法代价的数值
输入数组:p[M ]:存储n个矩阵的M(即n+1)条边
数组s[i][j]:存储i和j之间的k值,最后我们要的是这个
代码中三层循环中第一层循环:l为矩阵链的长度 ,即将矩阵从两个两个拆分,然后在三个三个拆以此类推。
第二层循环:m[i][j]中的i从一开始循环,j=i+l-1, 为什么呢,因为矩阵链长度最大为l,所以根据i和l我们可以得到j=i+l-1
第三层循环:从i开始依次寻找断点k,然后比较m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j] 和m[i][j]谁小。
如果m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]更小(所需乘法次数更少),那么就更新m[i][j]=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j],并且将此时的k值储存在s[i][j]中
我们根据图片可以得到更加直观的理解:
假设现在有一个包含七个矩阵的矩阵链:
那么m[i][j]的计算顺序如图所示:
2.(递归)PrintPath():根据MatrixChainOrder()函数得到的s[i][j],计算并输出乘法结合方式
由上文可知,s[i,j]记录了k值,指出A(i)A(i+1)...A(j)的最优括号化方案的分割点应在A(k)和A(k+1)之间。
A1..An的最优方案中最后一次矩阵乘法运算应该是以s[1,n]为分界的( A1*A2..*A(s[1,n]) )*( A(s[1,n]+1)*..*An
而s[1,s[1,n]]指出了计算A1*A2*..A(s[1,n])时应进行的最后一次矩阵乘法运行;s[s[1,n]+1,n]指出了计算A(s[1,n]+1)*..*An时应进行的最后一次矩阵乘法运算。
所以我们可以递归过程输出<A(i),A(i+1),...,A(j)>的最优括号化方案。