您的位置:首页 > 其它

动态规划之矩阵连乘问题

2013-03-19 19:48 204 查看
    问题描述:给定n个矩阵(A1,A2,A3.....An},其中Ai与Ai+1是可乘的,i=1,2,...n-1。考察n个矩阵的连乘积A1A2A3,....An。由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序,这种计算次序可以用加括号的方式来确定。加括号的方式决定了整个计算量(指的是乘法调用的次数)。所以自然会提出矩阵连乘积的最优计算次序问题。

    自然,首先想到的是用枚举法,算出每一种加括号情况下的计算量,取最小的情况。工程之庞大可想而知。溯其源,会发现,"枚举“的这种想法是不可避免的,只有所有情况都考虑比较后,才会出现那个最小量乘的结果。普通的枚举导致庞大工程的一个重要因素就是”子问题重复计算“。这里先要明确,什么是矩阵连乘的子问题。

    以A1A2A3A4为例,它的子问题为:A1    A2    A3    A4     A1A2    A2A3     A3A4  A1A2A3   A2A3A4   A1A2A3A4 。你要求A1A2A3A4的最优次序,势必要先求段长为3的子问题的最优次序,而段长为3的子问题是基于段长为2的子问题的基础之上的(这就是一种自底向上的递归)。以此推下去,你很容易会发现两个有意思的现象:第一,假如你已计算出段长为3的子问题的最优次序,那该最优次序下的子问题也是最优的(你可以通过反证法获知);第二,计算完段长为2的子问题,再计算段长为3的子问题时,你还会用到段长为2的子问题的计算结果,那何不把先前的计算结果进行保存,避免重复计算。

    以下就是动态规划算法解决矩阵连乘问题的相关代码,思想无非就两点:

    第一,自底向上的递归式:  
 
                                                 

 

  

    需要指出的是:m[i][j]表示Ai.....Aj的最少数乘次数,k表示求解Ai....Aj的子问题最优值时的断开位置,Pi-1PkPj表示AiAi+1.....Ak和Ak+1....Aj相乘时数乘数。

第二,在计算过程中,保存已解决的子问题答案。

   

#include<iostream>
using namespace std;
void outPut(int i,int j,int **s)
{
if(i==j)
return;
outPut(i,s[i][j],s);
outPut(s[i][j]+1,j,s);
cout<<"Multiply A "<<i<<","<<s[i][j];
cout<<" and A"<<s[i][j]+1<<","<<j<<endl;
}
void MartrixChain(int n,int *p,int **m,int **s)
{
for(int t=0;t<n;t++)
{
m[t][t]=0;             //单一矩阵的情况  一个矩阵数乘次数为0
s[t][t]=-1;
}
for(int l=2;l<=n;l++)               //l为段长
{
for(int i=0;i<=n-l;i++)
{
int j=i+l-1;              //j为每段的起点
m[i][j]=m[i][i]+m[i+1][j]+p[i]*p[i+1]*p[j+1];     //类似于赋初值的功能,其可取i<=k<j中的任意一个
s[i][j]=i;
for(int k=i+1;k<j;k++)                         //改变断点,试探出最小的情况
{
if(m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1]<m[i][j])
{
m[i][j]=m[i][k]+m[k+1][j]+p[i]*p[k+1]*p[j+1];
s[i][j]=k;                            //记录断点位置
}
}
}
}
cout<<"\n最少数乘为 "<<m[0][n-1]<<endl<<endl;
outPut(0,n-1,s);
}
int main()
{
int num;
int *dimension;
int **mm;
int **ss;
cin>>num;
dimension=new int[num+1];     //矩阵维数
mm=new int*[num];
ss=new int*[num];
for(int i=0;i<num;i++)
{
mm[i]=new int[num];
ss[i]=new int[num];
}
for(int r=0;r<=num;r++)
cin>>dimension[r];
MartrixChain(num,dimension,mm,ss);
}


    程序实现并不难,但是还是要交代几点容易犯错的细节:

    1.表示矩阵维数的数组大小:应该是矩阵个数+1,理由......呵呵;

    2.若Ai表示连乘矩阵中第i个矩阵,请你时刻记住,实际上的数组下标是从0开始的;(程序中,我是令i从0开始的)

    这两点都可以通过调试修正,只是如果一开始就能想明白,没必要花这种时间。

    运行结果如下:

    我来解释下运行结果  A  1 , 1  and  A  2 , 2     表示(A1A2)是一个分块的  依次为(A1A2)    (A0(A1A2))    (A3A4)    ((A3A4)A5)    (A0A1A2)(A3A4A5)   综合考虑后,最后加括号的方式为:(( A0 ( A1 A2 ) ) ( ( A3 A4 ) A5 ) )   。

    最后,来总结下动态规划算法的要素:1.最优子结构的性质     2.重叠子问题性质

    (在此不再赘述,从问题分析中已经体现)。

    特别想交代下的是,我在问题分析中提到”枚举“一词,个人觉得动态规划就是”变相的枚举“,只是它通过小规模的一步步枚举在缩小范围,进而减少重复枚举,能达到这个目的就是基于上述的两个要素,而动态规划能得到正确的答案,则是因为它已经考虑比较了所有可能的情况(这也是解决任何问题所不能避免的,只是有些是隐式比较而已)。

    还有一个非常欠缺的地方:怎么样直接输出加括号的表达式呢?如(( A0 ( A1 A2 ) ) ( ( A3 A4 ) A5 ) )   。如果无视空间的代价,多开几个数组,用上队列的思想,是可以实现,能不能有更简洁的方法呢?~~~communicating!!!!!!!!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息