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


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

  • 相关阅读:
    Java基础之StringBuffer和StringBuilder的区别
    ElasticSearch初体验之使用
    oracle基础之游标的理解与使用
    Java基础之创建实例化对象的方式
    vue.$nextTick实现原理
    vue3双向数据绑定原理_demo
    css实现水平-垂直居中的方法
    中文数组转为数字
    props&attrs provide inject
    vue2和vue3比较
  • 原文地址:https://www.cnblogs.com/yaowen/p/4476487.html
Copyright © 2020-2023  润新知