如果看过我上一篇文章(动态规划——最长公共子序列)的同学,相信已经对动态规划的思想有了一定的了解和认识,即把之前求解得出的子问题存储起来,以便后续求解大问题直接使用。
本章要讲解的问题是矩阵链相乘。因为要照顾一些小白同学和方便撰写文章,我会写得比较详细,如果有基础、想看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个矩阵链乘M1M2...Mn运算量最小(即数量乘法次数最少)的计算次序.。
输入格式:输入的第一行包含一个整数n(1≤n≤20),接下来一行是n+1个数依次是n个矩阵的行数以及最后一个矩阵的列数。注意,根据矩阵乘法定义,两个矩阵相乘MiMi+1意味着后一矩阵Mi+1的行数与前一矩阵Mi的列数相同。
输出格式:在一行内输出这n个矩阵链乘时数量乘法的最少次数,以及一种对应的最优计算次序。最优计算次序采用对M1M2...Mn加括号的形式输出,一共n−1对括号(参见输出样例)。
如果有多种最优计算次序,则取每层括号尽量靠前的那种次序(即每层括号划分的两个矩阵子序列中前面子序列尽可能短的那种)。例如,如果M1M2M3M4M5的最优计算次序的最外层括号有两种方式:(M1M2M3)(M4M5)和(M1M2)(M3M4M5), 则最外层括号取后一种,因为其括号更靠前。
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
希望能帮助到大家!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)