【动态规划】快速计算矩阵连乘

题目

给定n个矩阵{A1,A2,…,An},其中Ai和A(i+1)(i=1,2,3,…,n-1)是可乘的。矩阵乘法简单来说就是A(m * n)和B (n * p)阶的两个矩阵相乘,结果矩阵C为m*p阶的,且C(ij)就是A的第i行和B的第j行每个数对应相乘再作和的结果。用加括号的方法表示矩阵连乘的次序,不同的计算次序计算量(乘法次数)是不同的,找出一种加括号的方法使得矩阵连乘的计算量最小。

问题分析

这个问题求矩阵连乘的最小计算量,首先要知道矩阵计算量的计算公式(以上面 的两个矩阵为例):mnp,那么多个矩阵也是这样,在加括号之后,就可以看作是多次进行两个矩阵的乘法运算。如果矩阵个数少的话,我们还可以一一列举出加括号的方法,但是一旦矩阵个数多了,就不能这样做了,此时就需要用到之前小规模子问题的解,这里就体现出动态规划的思想。

算法

动态规划

这个问题具有最优子结构,就是整个问题的最优解包含子问题的最优解,也就是说计算出来的计算量最小的加括号方法,在每个括号内也是计算量最小的算法,可以用反证法加以证明,这里就不再赘述了。(如感兴趣可参考游艇租赁问题编辑距离)所以我们可以用动态规划的方法,先求出子问题的最优解,然后由子问题的最优解得到整个问题的最优解。

算法核心

解决这个问题最重要的当然是递推公式啦,这就是如何由子问题的最优解得到较大规模子问题的解,最终得出整个问题的最优解的方法。
m[i][j]:第i个矩阵到第j个矩阵连乘的最小计算量;
a[i][j]:第i个矩阵到第j个矩阵连乘取得最小计算量的方法,就是加括号的位置,先算第i个到第a[i][j]个,第a[i][j]+1个到第j个,然后把两个结果矩阵相乘;
p[i]:第i个矩阵的列数,也是第i+1个矩阵的行数(由矩阵乘法应满足第一个矩阵的列数等于第二个矩阵的行数可知);
d:几个矩阵连乘;
if i==j,m[i][j]=0;
if i!=j,m[i][j]=min{m[i][k]+m[k+1][j]+p[i]*p[k]*p[j]}(k为[i,j)的整数);
就是先计算d=2的时候的最小计算量,存到m[i][j]中,并把计算方法存到a[i][j]中,然后按照递推公式求d=3,4,…,n的m[i][j]和a[i][j]直至最后求出m[1][n],然后递归输出计算方法,也就是加括号的方法。

算法流程

首先对m[][]和a[][]进行初始化,只把对角线初始化为0即可,然后d从2到n循环,先求矩阵个数少的时候的计算量,再求多的时候,在循环内部的i从1到n-d+1,计算从第i个开始的d个矩阵的最小计算量,最内部是k从i到j的循环,就是计算括号加在什么位置时计算量最小,用m[i][j]记录这个最小值,用a[i][j]记录取得最小值的k值。循环结束后回到主函数,m[1][n]就是这n个矩阵的最小计算量。如果要得到计算方法,就是加括号的方法,写一个output递归函数输出即可。(具体见代码)

代码实现

#include<iostream>
#include<cstring>
#define INF 1<<29
using namespace std;
const int maxn=105;

int n;
int p[maxn];//储存每个矩阵的行数和列数
int m[maxn][maxn];//储存第i个矩阵到第j个矩阵的最小计算量的值
int a[maxn][maxn];//储存加括号的策略

void cal()
{
          int i,j,k,d;
          int t;
         /*memset(m,0,sizeof(m));//只需初始化对角线即可
          memset(a,0,sizeof(a));*/
          for(i=0;i<maxn;++i)
          {
                    m[i][i]=0;
                    a[i][i]=0;
          }
          for(d=2;d<=n;++d)//从两个矩阵相乘开始计算
          {
                    for(i=0;i<=n-d+1;++i)//起始计算的矩阵
                    {
                              j=i+d-1;
                              m[i][j]=INF;
                              a[i][j]=i;
                              for(k=i;k<j;++k)//加括号的位置
                              {
                                        t=m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];//求计算次数
                                        if(t<m[i][j])
                                        {
                                                  m[i][j]=t;
                                                  a[i][j]=k;
                                        }
                              }
                    }
          }
}

void output(int i,int j)
{
          if(i==j)
                    cout<<"A["<<i<<"]";
          else
          {
                    cout<<"(";
                    output(i,a[i][j]);
                    output(a[i][j]+1,j);
                    cout<<")";
          }
}

int main()
{
          cout<<"请输入矩阵个数:";
          cin>>n;
          cout<<"请依次输入矩阵的行数和最后一个矩阵的列数:";
          int i;
          for(i=0;i<=n;++i)
                    cin>>p[i];
          cal();
          cout<<"最小计算量的值:"<<m[1][n]<<endl;
          cout<<"计算方法:";
          output(1,n);
          return 0;
}

参考文献:《趣学算法》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值