• 动态规划之矩阵连乘问题


    问题描述:给定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<<"
    最少数乘为 "<<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!!!!!!!!

  • 相关阅读:
    hadoop中namenode发生故障的处理方法
    开启虚拟机所报的错误:VMware Workstation cannot connect to the virtual machine. Make sure you have rights to run the program, access all directories the program uses, and access all directories for temporary fil
    Hbase的安装与部署(集群版)
    分别用反射、编程接口的方式创建DataFrame
    用Mapreduce求共同好友
    SparkSteaming中直连与receiver两种方式的区别
    privot函数使用
    Ajax无刷新显示
    使用ScriptManager服务器控件前后台数据交互
    数据库知识
  • 原文地址:https://www.cnblogs.com/yaowen/p/4476487.html
Copyright © 2020-2023  润新知