0
点赞
收藏
分享

微信扫一扫

DP算法 —— 矩阵连乘问题

紫荆峰 2022-04-05 阅读 40

1. 问题描述:

矩阵链乘法问题:
输入:<A1,A2,…,An> ,Ai是矩阵
输出:计算矩阵连乘A1A2…An过程中,乘法运算次数最少的乘法次序。

2. 题解:

在解决此题之前,我们需要知道 —— 设A是p×q矩阵,B是q×r矩阵,则计算A×B的时间是0(pqr).接下来分析一下此题:
① 矩阵乘法满足结合律,计算一个矩阵链的乘法可有多种方法;
② 复杂性测度定义为乘法的次数;
③ 三个以上矩阵的乘法 AB… C 的代价依赖于计算的顺序,不同解法有不同的代价。我们通过一个例子来理解一下:
在这里插入图片描述
由此可见,其实解决此题主要就是找到最佳的划分,如果将矩阵链进行拆分使得运算次数最少。这是一个最优解问题,某一问腿的解依赖于其子问题的解,因此我们很容易想到用动态规划来求解。而DP算法的精华就是去寻找规划方程和边界条件,下面我们一起来看一下:
在这里插入图片描述
在写出规划方程之前我们需要明白矩阵相乘的条件,不是任意两个矩阵都能做乘法运算,必须保证前一个矩阵的列数为下一个矩阵的行数,这就对输入产生了限制,其实换个方向思考就是对我们处理数据产生了方便,也就是说,N个矩阵,我们只需要设置一个含有N+1个元素的数组P即可(这应该很好理解叭~)。下面给出规划方程:
在这里插入图片描述
乍一看可能有点像分治算法,将问题拆分成两个子问题(当然子问题内部还可能继续拆分)然后再合并。大家可能会对pi-1存在疑问,我们合并i~j的矩阵,和i-1有啥关系呢?这就是上一段我们提到的矩阵相乘的限制,第i个矩阵的行数就是第i-1个矩阵的列数。下面解决边界条件:
在这里插入图片描述
上图中蓝色的0就是我们的边界条件,即只有一个矩阵的时候不需要进行乘法,即运算次数为0。随后依次解决2个矩阵连乘,3个··· 最红得到总问题的解,当然如果题目要求我们不仅仅得到最小运算次数的数值,还要求我们得到如何进行结合(划分),那就需要备忘录s[ ][ ],记录断点。如:S[1][5]=3可表示:(A1A2A3)(A4A5),当然内部断链还会继续划分A1A2A3

3. 示例:

在这里插入图片描述
上面我们给出了6个矩阵连乘的例子,希望通过这个例子,大家能够彻底领悟此题的奥秘。粉色的箭头有7个,正是我们上面说的输入的P数组,即6个矩阵只需要7个输入。然后将m[i,i]全部初始化为0,最为边界条件。然后循环一步一步填满整个表格,作图为m[ ][ ]即存储最小运算次数,右图为备忘录s数组,记录断点。
在这里插入图片描述
为了让大家明白m[ ][ ]中的数是怎么算出来的,我们以m[2][5]为例,一起来分析一下:因为m[2][5]是4个矩阵连乘的例子,因此直接的划分有3种,如下:
在这里插入图片描述
运算次数的 最小值为7125,于是将7125填入到对应i = 2, j = 5的位置进行存储。
DP问题通常是自底向上求解,即求解最小的子问题,即边界条件0,然后一步步向顶计算,最终得到全局最优解,当我们计算m[2,5]时,只需设置变量k,循环3次分别对应4个矩阵连乘的3种划分,而子问题的子问题之前一定是解决完的。其实看完下面的代码,大家就会发现,我们其实是先将一个矩阵的子问题全部初始化为0,然后解决所有的2个矩阵连乘的问题,然后解决所有3个矩阵连乘的问题·····最终解决所有6个矩阵连乘的问题,即最终的最优解

4. 代码:

//
// Created by 23011 on 5/4/2022.
//

#include<iostream>
using namespace std;
const int maxn = 7;  //maxn为7表示有6个矩阵
int MatrixChain(int *p, int n, int m[][maxn], int s[][maxn]){
    for(int i=1;i<=n;i++){
        m[i][i]=0;
    }
    for(int r=2;r <= n;r++){    //矩阵链长度,从长度为2开始
        for(int i=1;i <= n-r+1;i++){    //根据链长度,控制链最大的可起始点
            int j = i+(r-1);  // j记录右端点(根据长度r和左端点i计算得到右端点j)
            m[i][j] = m[i][i]+m[i+1][j]+p[i-1]*p[i]*p[j];   //先设置最好的划分方法就是直接右边开刀,后续改正,也可合并到下面的for循环中
            s[i][j]=i;
            for(int k=i+1;k < j;k++){   //将断点从i+1开始,可以断的点直到j-1为止
                int t = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
                if(t<m[i][j]){
                    m[i][j] = t;
                    s[i][j] = k;
                }

            }

        }
    }
}
/*
*追踪函数:根据输入的i,j限定需要获取的矩阵链的始末位置,s存储断链点
*/
void Traceback(int i,int j, int s[][maxn]){
    if(i==j)       //回归条件
    {
        printf("A%d", i);
    }
    else       //按照最佳断点一分为二,接着继续递归
    {
        printf("(");
        Traceback(i,s[i][j],s);
        Traceback(s[i][j]+1,j,s);
        printf(")");
    }
}
int main(){
    int p[maxn]={30,35,15,5,10,20,25};
    int m[maxn][maxn],s[maxn][maxn];
    MatrixChain(p,maxn-1,m,s);//N-1因为只有六个矩阵
    Traceback(1,6,s);
    return 0;
}

举报

相关推荐

0 条评论