我们就不具体阐述这个问题的定义了。为了能够更加直白地让读者了解什么是矩阵链相乘的最小乘积次数问题,我们举个简单的例子:
假设有三个矩阵,分别是
M 1 M_1 M1:
( 1 1 1 1 1 1 1 1 1 1 ) \begin{pmatrix} 1 & 1 \\ 1 & 1 \\ 1 & 1 \\ 1 & 1 \\ 1 & 1 \\ \end{pmatrix} ⎝⎜⎜⎜⎜⎛1111111111⎠⎟⎟⎟⎟⎞
M 2 M_2 M2:
( 1 1 1 1 1 1 1 1 ) \begin{pmatrix} 1 & 1 & 1 & 1\\ 1 & 1 & 1 & 1\\ \end{pmatrix} (11111111)
M 3 M_3 M3:
( 1 1 1 1 1 1 1 1 1 1 1 1 ) \begin{pmatrix} 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \\ 1 & 1 & 1 \\ \end{pmatrix} ⎝⎜⎜⎛111111111111⎠⎟⎟⎞
这个问题就是要找到一个分割方式,使得矩阵相乘 M 1 ∗ M 2 ∗ . . . ∗ M n M_1*M_2*...*M_n M1∗M2∗...∗Mn最后在乘法上计算的次数最少。
比如我们这个问题, M 1 M_1 M1是一个 5 ∗ 2 5*2 5∗2(5行2列)的矩阵, M 2 M_2 M2是一个 2 ∗ 4 2*4 2∗4的矩阵, M 3 M_3 M3是一个 4 ∗ 3 4*3 4∗3的矩阵,其实这个问题和矩阵的元素无关,只和矩阵的行数和列数相关,因此,上边的矩阵的元素我都填的1,没有什么影响。3个矩阵相乘有两种乘法,即
显然,第二种分割法乘法的运算次数较小,此时乘法次数为54。
我们需要做的就是,针对输入的矩阵的情况,求出这样的一个最少的乘积次数,同时能够给出分割的方法。
对于乘积序列 M i M i + 1 . . . M j M_iM_{i+1}...M_j MiMi+1...Mj,我们假设使得其乘积最少的次数是C[i,j]。
取 k ∈ ( i , j ] k \in (i,j] k∈(i,j],我们将上边的序列在k的位置分割开,即得到 ( M i M i + 1 . . . M k − 1 ) ( M k . . . M j ) (M_iM_{i+1}...M_{k-1})(M_k...M_j) (MiMi+1...Mk−1)(Mk...Mj)。
此时,
上式标黄的式子便是这个问题用动态规划方法求解的递推式子。
为了能够将这个式子正确地运用在程序中,我们用式(1)来分析一下博文开始的时候我们举的例子。
很明显,对于这个例子,我们的目标是计算出C[1,3]。而
C [ 1 , 3 ] = m i n { C [ 1 , 1 ] + C [ 2 , 3 ] + ( r 1 ∗ r 2 ∗ r 4 ) , C [ 1 , 2 ] + C [ 3 , 3 ] + ( r 1 ∗ r 3 ∗ r 4 ) } C[1,3]=min\{C[1,1]+C[2,3]+(r_1*r_2*r_4),C[1,2]+C[3,3]+(r_1*r_3*r_4)\} C[1,3]=min{C[1,1]+C[2,3]+(r1∗r2∗r4),C[1,2]+C[3,3]+(r1∗r3∗r4)}
C [ 1 , 2 ] = C [ 1 , 1 ] + C [ 2 , 2 ] + ( r 1 ∗ r 2 ∗ r 3 ) C[1,2]=C[1,1]+C[2,2]+(r_1*r_2*r_3) C[1,2]=C[1,1]+C[2,2]+(r1∗r2∗r3)
C [ 2 , 3 ] = C [ 2 , 2 ] + C [ 3 , 3 ] + ( r 2 ∗ r 3 ∗ r 4 ) C[2,3]=C[2,2]+C[3,3]+(r_2*r_3*r_4) C[2,3]=C[2,2]+C[3,3]+(r2∗r3∗r4)
同时,经过简单的分析,我们可以得到 C [ 1 , 1 ] = 0 C[1,1]=0 C[1,1]=0, C [ 2 , 2 ] = 0 C[2,2]=0 C[2,2]=0, C [ 3 , 3 ] = 0 C[3,3]=0 C[3,3]=0
也就是说,如果想要求 C [ 1 , 3 ] C[1,3] C[1,3],得先知道 C [ 1 , 2 ] C[1,2] C[1,2]和 C [ 2 , 3 ] C[2,3] C[2,3];如果想要求 C [ 1 , 2 ] C[1,2] C[1,2],得先知道 C [ 1 , 1 ] C[1,1] C[1,1]和 C [ 2 , 2 ] C[2,2] C[2,2];如果想要求 C [ 2 , 3 ] C[2,3] C[2,3],得先知道 C [ 2 , 2 ] C[2,2] C[2,2]和 C [ 3 , 3 ] C[3,3] C[3,3]。如果画一个表格的话,我们很容易地可以看到,这是一个先求斜对角线上的元素值的问题。
1 | 2 | 3 | |
---|---|---|---|
1 | C[1,1] | C[1,2] | C[1,3] |
2 | C[2,2] | C[2,3] | |
3 | C[3,3] |
对于上述的例子: r 1 = 5 , r 2 = 2 , r 3 = 4 , r 4 = 3 r_1=5,r_2=2,r_3=4,r_4=3 r1=5,r2=2,r3=4,r4=3
至此,我们简单地整理了一下计算的步骤,接下来就是很好写算法了。
for i in 1...n:
C[i,i]=0;
for d in 1...n-1:
for i in 1...n-d:
j=i+d;
for k in i+1...j
C[i,j]=min{C[i,k-1]+C[k,j]+(r_i*r_k*r_(j+1))}
print C[1,n]
//动态规划求解矩阵链相乘的最少乘积次数问题
//输入:矩阵的个数N,以及每一个矩阵的行数l和列数c
//输出:最少乘积次数,以及对应的乘积顺序
/*
示例输入:
5
4 10
10 5
5 6
6 3
3 10
示例输出:
i=1,j=2:k= 2
i=2,j=3:k= 3
i=3,j=4:k= 4
i=4,j=5:k= 5
i=1,j=3:k= 3
i=2,j=4:k= 3
i=3,j=5:k= 5
i=1,j=4:k= 3
i=2,j=5:k= 5
i=1,j=5:k= 5
result: 470
*/
#include
#include
using namespace std;
const int MAX_N = 100; //假设输入的矩阵的个数小于100个
const int MAX_INT = 65530; //假设最后的结果不会大于MAX_INT值
int C[MAX_N][MAX_N]; //C[i][j]的意思是,从第i个矩阵乘到第j个矩阵需要的最少乘积次数
int Index_K[MAX_N][MAX_N];
/*Index_K[i][j]与C[i][j]对应,意思是 先做第i个矩阵乘到第Index_K[i][j]个矩阵,然后做第Index_K[i][j]个矩阵乘到第j个矩阵,接着将这两个结果矩阵相乘,这样的分法可以使C[i][j]最小*/
struct matrix
{
int m_l, m_c;
};
matrix mats[MAX_N]; //保存输入的矩阵
int R[MAX_N]; //R[i]保存输入的矩阵i的行数,R[N+1]保存输入的矩阵N的列数
int N; //输入矩阵的个数
bool isMatricesLegal(); //判断输入的矩阵是否可以做乘法运算
int main()
{
cin>>N;
int l,c; //输入矩阵的行数和列数
for(int i=1;i<=N;i++)
{
cin>>l>>c;
mats[i].m_l=l;
mats[i].m_c=c;
}
//这里可以判断一下输入的矩阵行列值是否符合运算的要求
if(!isMatricesLegal()) return 1;
for(int i=1;i<=N;i++) R[i]=mats[i].m_l;
R[N+1]=mats[N].m_c;
for(int i=1;i<=N;i++)
{
C[i][i]=0; //无需做乘法运算
}
for(int d=1;d<=N-1;d++)
{
for(int i=1;i<=N-d;i++)
{
int j=i+d;
int min_tmp=MAX_INT;
for(int k=i+1;k<=j;k++)
{
if(min_tmp>(C[i][k-1]+C[k][j]+R[i]*R[k]*R[j+1]))
{
min_tmp=C[i][k-1]+C[k][j]+R[i]*R[k]*R[j+1];
Index_K[i][j]=k;
}
//min_tmp = min(min_tmp,C[i][k-1]+C[k][j]+R[i]*R[k]*R[j+1]);
}
C[i][j]=min_tmp;
cout<<"i="<<i<<",j="<<j<<":"<<"k= "<<Index_K[i][j]<<endl;
}
}
cout<<"result:\t"<<C[1][N]<<endl; //从矩阵1乘到矩阵N的最少的乘积次数
return 0;
}
bool isMatricesLegal()
{
for(int i=1;i<N;i++)
{
if(mats[i].m_c!=mats[i+1].m_l) return false; //如果前一个矩阵的列数不等于后一个矩阵的行数,则不能进行乘法运算
}
return true;
}
对于代码中注释的输入例子,我们照样可以手动计算一下对应的表格,该表格内容如下:
1 | 2 | 3 | 4 | 5 | |
---|---|---|---|---|---|
1 | 0 | 200 | 320 | 350 | 470 |
2 | 0 | 300 | 240 | 540 | |
3 | 0 | 90 | 240 | ||
4 | 0 | 180 | |||
5 | 0 |
对应的最优划分为 ( ( M 1 M 2 ) ( M 3 M 4 ) ) ( M 5 ) ((M_1M_2)(M_3M_4))(M_5) ((M1M2)(M3M4))(M5)