动态规划——矩阵链相乘

动态规划——矩阵链相乘,第1张

如果看过我上一篇文章(动态规划——最长公共子序列)的同学,相信已经对动态规划的思想有了一定的了解和认识,即把之前求解得出的子问题存储起来,以便后续求解大问题直接使用。

本章要讲解的问题是矩阵链相乘。因为要照顾一些小白同学和方便撰写文章,我会写得比较详细,如果有基础、想看ac代码的同学可直接跳到后面的代码段。矩阵链相乘,顾名思义就是多个矩阵相乘,那为什么同样的矩阵相乘得出来的乘法次数会不一样呢?下面来看一个例子。

三个二维矩阵M1、M2、M3,分别是2*10,10*2,2*10。如果(M1*M2)*M3,那么要进行的乘法次数是2*10*2+2*2*10=80;如果M1*(M2*M3),那么要进行的乘法次数是10*2*10+2*10*10=400(看不懂乘法次数算式的同学可以先去复习一下线性代数的知识)。所以,矩阵链相乘的相乘次数会根据矩阵间的不同相乘顺序而有所不同。看懂原理之后,那我们来做一个题。


矩阵链乘 (转自PTA)

用动态规划法确定n个矩阵链乘M1​M2​...Mn​运算量最小(即数量乘法次数最少)的计算次序.。

输入格式:

输入的第一行包含一个整数n(1≤n≤20),接下来一行是n+1个数依次是n个矩阵的行数以及最后一个矩阵的列数。注意,根据矩阵乘法定义,两个矩阵相乘Mi​Mi+1​意味着后一矩阵Mi+1​的行数与前一矩阵Mi​的列数相同。

输出格式:

在一行内输出这n个矩阵链乘时数量乘法的最少次数,以及一种对应的最优计算次序。最优计算次序采用对M1​M2​...Mn​加括号的形式输出,一共n−1对括号(参见输出样例)。
如果有多种最优计算次序,则取每层括号尽量靠前的那种次序(即每层括号划分的两个矩阵子序列中前面子序列尽可能短的那种)。例如,如果M1​M2​M3​M4​M5​的最优计算次序的最外层括号有两种方式:(M1​M2​M3​)(M4​M5​)和(M1​M2​)(M3​M4​M5​), 则最外层括号取后一种,因为其括号更靠前。

输入样例:
5
5 10 4 6 10 2
输出样例:
348 (M1(M2(M3(M4M5))))

我们先来整理一下思路。假如只有一个矩阵,那乘法次数自然为0;当有两个矩阵时,次数为矩阵行列直接相乘可得;当有三个矩阵时,就有两种相乘方式,即(M1*M2)*M3和M1*(M2*M3);以此类推,放到二维数组中可以如下表示:

m[i, j]表示的是矩阵i到矩阵j链相乘所需要的最少乘法次数。d代表的是有d个矩阵相乘,可知d=1这一斜线上的m[i, j]都等于0;d=2时,斜线上的m[i, j]等于两个矩阵行列直接相乘;d=3时,就有两种选择,例如m[1,3]=min{m[1, 2]+m[3, 3]+a[1]*a[3]*a[4], m[1, 1]+m[2, 3]+a[1]*a[2]*a[4]} //数组a存储矩阵的行和列。大家可以发现,求m[1, 3]的时候,需要用到的m[1, 2]、m[3, 3]、m[1, 1]、m[2, 3]都是前面已经求得的,直接拿来运算就可以了,这就是本题动态规划的思路。后面的原理也是一样的,就不赘述。

所以,我们可以得出一个求m[i, j]的公式:m[i, j]=min{ m[i, j],  m[i, k]+m[k+1, j]+a[i]*a[k+1]*a[j+1] }。大家可以把k理解为中转站,比如汽车高速路上行驶,如果跑长途的话,一般都会在某个服务站下车休息一下,那我们要做的就是找出在哪个服务站下车休息,会使整个的行驶时间最短。

for(int d=2;d<=n;d++)//d个矩阵相乘 
{
	for(int i=1;i<=n-d+1;i++)//斜着到第i个 
	{
		int j=i+d-1;
		m[i][j]=inf;
		for(int k=i;k<=i+d-2;k++)
		{
			int temp=m[i][k]+m[k+1][j]+a[i]*a[k+1]*a[j+1];
			if(temp

(1)d=1时,为一个矩阵的情况,m[i, i]=0,所以不需要求解。

(2)i是所在斜线中的第i个(大家在看代码的时候可以结合上面的图片一起看,方便理解),j是受到i和d的影响,并且都是由固定的表达式求得,不需要重新定义。

(3)k是从i到j之间的中转站(大家注意k的范围,最大范围是j-1), 逐一遍历,如果第k个中转站得出来的结果比之前的小,则把m[i, j]更新。

(4)二维数组s是记录i到j之间的哪个中转站可使m[i, j]最小,便于后续打印。

打印矩阵链相乘的最优计算次序

void m_print(int i, int j)
{
	if(i==j)
	{
		printf("M%d", i);
	}
	else
	{
		printf("(");
		m_print(i, s[i][j]);
		m_print(s[i][j]+1, j);
		printf(")");
	}
}

先来看else代码段,用递归输出,当i != j时,说明i到j还有中转站,即s[i][j],一直递归;直到i==j时,说明是同个矩阵,没有中转站了,可以打印输出。

本题的ac代码如下:

#include
#define inf 100000001
using namespace std;
int a[21]={0};//存储矩阵的行和列 
int m[21][21]={0};//存储i到j的最少计算次数 
int s[21][21]={0};//存储i到j的中转站k 
void m_print(int i, int j)
{
	if(i==j)
	{
		printf("M%d", i);
	}
	else
	{
		printf("(");
		m_print(i, s[i][j]);
		m_print(s[i][j]+1, j);
		printf(")");
	}
}
int main()
{
	int n=0;
	scanf("%d",&n);
	for(int i=1;i<=n+1;i++)
	{
		scanf("%d",&a[i]);
	}
	for(int d=2;d<=n;d++)//d个矩阵相乘 
	{
		for(int i=1;i<=n-d+1;i++)//斜着到第i个 
		{
			int j=i+d-1;
			m[i][j]=inf;
			for(int k=i;k<=i+d-2;k++)
			{
				int temp=m[i][k]+m[k+1][j]+a[i]*a[k+1]*a[j+1];
				if(temp

希望能帮助到大家!

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/722940.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-26
下一篇 2022-04-26

发表评论

登录后才能评论

评论列表(0条)