动态规划——矩阵链相乘

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

本章要讲解的问题是矩阵链相乘。因为要照顾一些小白同学和方便撰写文章,我会写得比较详细,如果有基础、想看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<m[i][j])
			{
				m[i][j]=temp;
				s[i][j]=k;
			}
		} 
	}
} 

(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<iostream>
#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<m[i][j])
				{
					m[i][j]=temp;
					s[i][j]=k;
				}
			} 
		}
	} 
	printf("%d ",m[1][n]);
	m_print(1, n);
	return 0;
}

希望能帮助到大家!

  • 29
    点赞
  • 151
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
动态规划矩阵乘法问题是指给定一系列矩阵,求解它们相乘的最少乘法次数。根据引用\[2\]中的解释,我们可以使用一个二维数组m来表示矩阵相乘所需要的最少乘法次数。其中,m\[i, j\]表示从矩阵i到矩阵j相乘所需要的最少乘法次数。 根据引用\[3\]中的思路,当只有一个矩阵时,乘法次数为0;当有两个矩阵时,乘法次数为矩阵行列直接相乘的结果。对于三个及以上的矩阵,我们可以通过动态规划的方式来求解最少乘法次数。 具体的动态规划算法如下: 1. 初始化二维数组m的对角线元素为0,表示只有一个矩阵时的乘法次数为0。 2. 从长d=2开始,逐步增加长,计算m\[i, j\]的值。对于每个长d,遍历所有可能的分割点k,计算m\[i, j\]的值。具体计算方式为:m\[i, j\] = min{m\[i, k\] + m\[k+1, j\] + a\[i\]*a\[k\]*a\[j+1\]},其中a\[i\]表示第i个矩阵的行数,a\[j+1\]表示第j+1个矩阵的列数。 3. 最终,m\[1, n\]即为所求的最少乘法次数,其中n为矩阵的长度。 通过以上算法,我们可以得到矩阵相乘的最少乘法次数。这个问题的动态规划解法可以有效地减少重复计算,提高计算效率。 #### 引用[.reference_title] - *1* *2* *3* [动态规划——矩阵相乘](https://blog.csdn.net/Wu_L7/article/details/124327310)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v91^control_2,239^v3^insert_chatgpt"}} ] [.reference_item] [ .reference_list ]
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值