您的位置:首页 > 其它

动态规划经典问题---矩阵链乘

2012-02-15 19:18 239 查看
问题描述:给定n个矩阵{A1,A2,A3...An},其中Ai与Ai+1是可乘的,i=1,2,3...n-1.考虑这n个矩阵的乘积。由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算机次序。这种计算次序可以用加括号的方式确定。若一个矩阵链乘的计算次序完全确定,这时就说该链乘已完全加括号。完全加括号的矩阵链乘可递归的定义如下:

1,单个矩阵是完全加括号的。

2,矩阵链乘的乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵链乘的乘积B和C的乘积并加括号。既:A=(BC)

例如:矩阵链乘A1A2A3A4可以有以下几种完全加括号的方式:((A1A2)(A3A4)) (A1(A2(A3A4))) (A1((A2A3)A4)) ((A1(A2A3))A4) (((A1A2)A3)A4),每一种加括号的方式确定了一个计算的次序,不同的计算机次序与矩阵链乘的计算量有密切关系。

为了说明这个问题,回忆一下两个矩阵相乘的相关概念。矩阵A和矩阵B可乘的条件是矩阵A的列数等于矩阵B的行数,若A是一个pxq的矩阵, B是一个qxr的矩阵,则其乘积C=AB是一个pxr的矩阵,且标准的两个矩阵相乘所需要的计算量为pqr次乘法操作。

考虑三个矩阵{A1,A2,A3}链乘的例子,设这三个矩阵的维数分别为10x100,100x5, 5x50若按第一种加括号方式((A1A2)A3)计算,则所需要的乘法次数为10X100X5+10X5X50=7500,若按第二种加括号方式(A1(A2A3))计算,则所需要的乘法次数为100x5x50+10x100x50=75000.第二种加括号方式是第一种的10倍。由此可以看出,不同的加括号方式所确定的不同的计算次序对矩阵链乘的运算量影响是巨大的。



问题定义:给定n个矩阵{A1,A2,A3...An},矩阵Ai的维数为Pi-1XPi,i=1,2,...n. 如何给矩阵链乘A1XA2XA3X...XAn完全加上括号,使得矩阵链乘中乘法次数最少。

我们首先想到了穷举法,枚举所有加括号方法,然后选出计算量最少的那种方法。但这种方法耗费时间多不说,我到现在也没想出怎么枚举所有加括号方法,总不能挨个指定吧,当矩阵链短的时候可以,当很长的时候肿么办???不得不承认,我自己脑子太笨了,根本就不是搞算法的料。。呵呵

我们还是用老方法,要用动态规划方法,先看本题有没有最优子结构特征。上边有两句话很键,尤其是第二句:1,单个矩阵是完全加括号的。 2,矩阵链乘的乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵链乘的乘积B和C的乘积并加括号。既:A=(BC)。。什么意思呢?也就是说,如果一个矩阵链已经完全加括号了,那么它可以表示为两个已经完全加括号了的矩阵链的乘积加括号,比如((A1A2)(A3A4))可以表示为已经加完括号的(A1A2)和(A3A4)的乘积加括号。((A1(A2A3))A4)可表示为A4和(A1(A2A3))乘积加括号。而(A1(A2A3))又可表示成A1和(A2A3)乘积加括号形式,这是一个递归的形式。这就构成了原问题的一个子问题。

我们可以这样描述:矩阵链乘A1XA2XA3X...XAn已经完全加上括号,则它必然在某一个矩阵Ai上分开为两个矩阵。具体在哪个Ai上,在哪处分开,应该在让这个矩阵链分开之后再加括号的代价最少的那一个位置上,那在哪一个上最少呢,我们不知道,需要从头遍历,并记录当前最少的计算量的那个矩阵Ai,所以这个问题的出发点是在哪一个矩阵中分开

状态转移方程怎么写呢?假设是在K处分开,那么这个时候的代价就是C[1,n]=C[1,k]+C[k+1,n]+P0*Pk*Pn,就是前一个矩阵链的最少计算量+后一个矩阵最少计算量+这两个相乘的最少计算量。

既然用动态规划,那就应该遍历所有状态空间,即所有的子问题。而这里的子问题就是任一两对矩阵之间加括号的最少代价,所以我们应该设立一个数据结构来表示和存储这子问题的答案。那该用什么数据结构来表示呢。我们可以用C[i][j]来表示矩阵i和矩阵j之间加括号的代价。所以我们定义一个二维数组C

,来表示和存储,而C[1]
就是我们要求的最终状态。

数据结构保存好了,算法怎么设计呢。

我刚开始有一个错误的思路,就和前边的背包和LCS一样,从头开始依次遍历,也就是从1--n这个顺序。这样是思维惯性造成的,是错误的,因为假设当我们遍历到C[1][3]时,而我们在找分开点K的时候需要用到C[1,1]+C[2,3]+P0*P1*P3,而这个时候C[2,3]根本就还没开始算。

我们发现这样一个规律:当我们在计算某一串个数为n矩阵链加括号的最少代价时,需要知道所有这些n-1个,n-2个,n-3个...1个等这些矩阵链的代价,就像刚才的C[1][3]例子,我们在计算它时需要知道所有2个矩阵相乘和1个矩阵相乘时的代价,即需要知道,A1,A2,A3,(A1A2),(A2A3)的代价,当然,这里的A2用不上。。。

说到这里,我们知道了我们遍历的顺序是要按照矩阵链的个数从2个开始到n个结束,1个矩阵相乘的代价不用计算是0,而n个矩阵相乘的代价就是我们要求的最终结果。

核心代码如下

int C

;
int flag

;
for(i=1;i<=n;++i)
{
C[i][i]=0;//当只有一个矩阵的时候,代价为0
}
for(int l=2;l<=n;++l)
{
for(int i=1;i<=n-l+1;++i)
{
min=0XFFFFFF;//初始为一个无穷大的数
int j=i+l-1;
for(int k=i;k<j;++k)
{
temp=C[i][k]+C[k+1][j]+Pi-1*Pk*Pj;
if(temp<min)
{
min=temp;
flag[i][j]=k;
}
}
C[i][j]=min;
}
}

最少代价求完之后,该如何构造出这个最优解来呢。这个对于我来说比较难于理解,看了书上写的,虽说看明白一些,但仍然有一些模糊。

要对矩阵链进行加括号,其实本质就是找出矩阵链在哪里断开,所以我们设了一个数组flag[i][j]来保存矩阵链Ai....Aj之间的分开点K。

我们可以利用flag数组保存的信息设计一个递归结构。

OutPut(A,flag,i,j)
{
if(i==j)//递归出口,只有两个以上的矩阵相乘才需要加括号,一个矩阵不需要加括号
{
cout<<"A"<<i;
return;
}
cout<<"(";
OutPut(A,flag,i,flag[i][j]);
OutPut(A,flag,flag[i][j]+1,j);
cout<<")";
}


这个问题的动态规划问题的实质是决定矩阵链从哪一个地方断开。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: