矩阵链乘法问题

题目:

                                  

                                                

拿到这道题,我们首先要知道矩阵相乘的规则和过程,具体的乘法过程如下:

                                                                                                 

要求A的列数=B的行数,并且结果的C矩阵的元素值满足如下关系:

                                                                                           

  从上式中我们可以求得矩阵C中每个元素值,但是我们这题关心的并不是值,而是相乘的此时,这里我们可以看到求每个元素,需要m个乘积的和,这样的话,求取完整的矩阵C需要进行l*m*m次相乘。同时由于相邻矩阵满足前一个列数等于后一个行数的关系,这样,我们把每个矩阵行列数保存在数组p中,并且相邻矩阵的相等行列书只存储一次,这样的话,对于矩阵M[i](i=1,2,3,.;.....n),其是p[i-1]行p[i]列的矩阵。

  确定好上述的数据存储,接下来考虑本题,如果检查每种不同的计算顺序那么福复杂度高达O(n!),显然是不靠谱的,这样我们可不可以考虑使用动态规划呢?

  答案是可以的,我们对于一个大矩阵M[i]*M[i+1]*...*M[j],我们不考虑过多,只想在得到这个矩阵的前一步乘积的情况,有哪些情况呢?比如对于M1*M2*M3*M4*M5,显然的我们有以下几种情况,并由此获得完整的M1*M2*M3*M4*M5的公式:

     1.(M1)*(M2*M3*M4*M5)的次数 =(M1)的次数+ (M2*M3*M4*M5)的次数+p[0]*p[1]*p[5]                     

     2.(M1*M2)*(M3*M4*M5)的次数=(M1*M2)的次数+(M3*M4*M5)的次数+p[0]*p[2]*p[5]

     3.(M1*M2*M3)*(M4*M5)的次数=(M1*M2*M3)的次数+(M4*M5)的次数+p[0]*p[3]*p[5]

     4.(M1*M2*M3*M4)*(M5)的次数=(M1*M2*M3*M4)的次数+(M5)的次数+p[0]*p[4]*p[5]

其中单个矩阵相乘的次数显然为0,我们再取其中求得最小的次数作为最终M1*M2*M3*M4*M5最小链乘次数.这样的话,我们其实就可以很简单的获得动态规划下数组的意义和递推关系式,dp[i][j];表示下标从i~j的这部分矩阵的最小链乘次数,其中矩阵下标从1到n,递推关系式如下:

dp[i][j]=0                                                                                                                (i=j)

           =min{dp[i][k]+dp[k+1][j]+p[i-1]*p[k]*p[j]} (其中i<=m<=j)                         (i<j)

这样就和容易得到下面得解题代码:

#include<iostream>
#include<algorithm>
using namespace std;
#define Max_N 100+1
int p[Max_N];                //存放各个矩阵的行列数 ,第i个矩阵大小为p[i-1]*p[i] 
int dp[Max_N][Max_N];         //矩阵下标从1~n,dp[i][j]表示下标从i~j的这部分矩阵的最小链乘次数 
int main()
{
	int n=6;
	cin>>n;
	
	for(int i=1;i<=n;i++)
	{
		cin>>p[i-1]>>p[i];
		dp[i][i]=0;
	}
    
    //int p[]={30,35,15,5,10,20,25};      //测试使用 
	//cout<<endl;
	for(int l=2;l<=n;l++)
	{
		for(int i=1;i<=n-l+1;i++)
		{
			int j=i+l-1;
			dp[i][j]=999999999;
		    for(int k=i;k<j;k++) 
		    {   //测试使用 
		    	//cout<<"dp["<<i<<"]["<<k<<"]:"<<dp[i][k]<<"-";	
			    //cout<<"dp["<<k+1<<"]["<<j<<"]:"<<dp[k+1][j]<<"-"; 
			    //cout<<p[i-1]*p[k]*p[j]<<endl;
		    	dp[i][j]=min(dp[i][j],dp[i][k]+dp[k+1][j]+p[i-1]*p[k]*p[j]);
		    	//cout<<"dp["<<i<<"]["<<j<<"]:"<<dp[i][j]<<endl;
		    }
		  	//cout<<"------------"<<endl;
		}
	}
	cout<<dp[1][n]<<endl;
	return 0;
}

  但是到这并不结束,下面我想说的是在解题时我的一个错误,并分析给大家,以此借鉴:接下来是我的源码:
#include<iostream>
#include<algorithm>               //注意,该方法是错误方法 
using namespace std;
#define Max_N 100+1
//int p[Max_N];                //存放各个矩阵的行列数 ,第i个矩阵大小为p[i-1]*p[i] 
int dp[Max_N][Max_N];         //矩阵下标从1~n,dp[i][j]表示下标从i~j的这部分矩阵的最小链乘次数 
int main()
{
	int n=6;
	//cin>>n;
	
	for(int i=1;i<=n;i++)
	{
		//cin>>p[i-1]>>p[i];
		dp[i][i]=0;
	}
    
    int p[]={30,35,15,5,10,20,25};
	for(int i=0;i<=n;i++)
	{
		cout<<p[i]<<" ";
	}
	cout<<endl;
	
	for(int i=1;i<=n;i++)
	{
		for(int j=i+1;j<=n;j++)
		{
		  	dp[i][j]=99999999;
		  	for(int mid=i;mid<j;mid++)       //分成两个矩阵相乘,i~k和k+1~j 
		  	{    //下标i~k的矩阵相乘结果为大小为p[i-1]*p[mid]的矩阵 
		  	    //下标k+1~j的矩阵相乘结果为大小为p[mid]*p[j]的矩阵
				  //最后两者矩阵相乘,链乘次数为p[i-1]*p[mid]*p[j] 
			  //cout<<"dp["<<i<<"]["<<mid<<"]:"<<dp[i][mid]<<"-";	
			  //cout<<"dp["<<mid+1<<"]["<<j<<"]:"<<dp[mid+1][j]<<"-"; 
			  //cout<<p[i-1]*p[mid]*p[j]<<endl;
		  	  dp[i][j]=min(dp[i][j],dp[i][mid]+dp[mid+1][j]+p[i-1]*p[mid]*p[j]);
		  	  //cout<<"dp["<<i<<"]["<<j<<"]:"<<dp[i][j]<<endl;
		  	}
		  	//cout<<"------------"<<endl;
		}
	}
	
	for(int i=1;i<=n;i++)
	{
		for(int j=i;j<=n;j++)
		{
			cout<<"dp["<<i<<"]["<<j<<"]:"<<dp[i][j]<<endl;
		}
	}
	cout<<dp[1][n]<<endl;
	return 0;
}

  我将示例中的测试案例写在了程序中,便于每次的调试。上述代码的问题就在于其循环时的变量范围,虽然说两种代码最终都保证dp[i][j]中的下标范围从1变化到n,但是第二种的方法就错在一下:

     该方法的作物之处就在于i,j下标是不断依次增大的,这样的话在进行下面一句话:

dp[i][j]=min(dp[i][j],dp[i][mid]+dp[mid+1][j]+p[i-1]*p[mid]*p[j])

时, 在 dp[mid+1][j]中mid+1>i的情况下,用 dp[mid+1][j]来求dp[i][j],相当于用之后遍历到的来求之前的数据,这样在未遍历到dp[mid+1][j]时,其值恒为0,也就是说从mid+1到j矩阵链乘次数为0,这样显然是不可能的。

  所以为了避免这种情况,首先要想到对于多个矩阵的相乘,其肯定是由其小部分的矩阵链乘结果得到的,那么就不能按照下标从小到大顺序遍历,那么就应该用下标范围的长度来作为遍历,也就是说先遍历完短范围的矩阵链乘结果,再得到大范围链乘结果,抛开一句,这里之所以不能用i,j下标作为遍历标准,就是因为其求结果的时候,并不是说先求小坐标的矩阵,而是先求小范围,也就是长度小的范围内,矩阵的链乘结果,比如,应该先求 M5*M6*M7,之后再去求M2*M3*...M7,其实就是因为后者的求取需要其一部分矩阵相乘的结果 。

   其实这题就告诉我们,动态规划解题时,我们要找准递推式中前后变量的范围,并在保证后者求出结果后再进行递推,这样就要求我们在保证动态数组变量范围不变的情况下,找到正确的下标变化过程。


PS:看书,解题越多,并且感悟越多,对于提升能力肯定是有帮助的,不能只是机械地敲书上的代码~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值