动态规划之矩阵连乘问题

问题描述:给定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!!!!!!!!

posted @ 2015-05-04 16:20  无天666  阅读(9146)  评论(0编辑  收藏  举报