动态规划-矩阵连乘问题

Posted 咸鱼成长小站

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划-矩阵连乘问题相关的知识,希望对你有一定的参考价值。

问题描述

给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i = 1,2,…,n-1。如何确定计算矩阵连乘积的计算次序,使得依次次序计算矩阵连乘积需要的数乘次数最少。

这题的意思呢,就是有n个矩阵连乘,我们知道矩阵的乘法不满足交换律,但是满足结合律。也就是说n个矩阵相乘的时候。我们可以选择不同的计算次序,也就是决定哪两个先乘。不同的计算次序我们所需要进行的数乘次数是不同的。

那么矩阵相乘的数乘次数怎么算。我们列举两个矩阵A和B:


A和B能够相乘,因为A的列数等于B的行数。我们计算A乘B,就是将A的每一行分别乘上B的每一列。对于这个A为2行3列,B为3行2列的矩阵来说。数乘次数就是2x3x2。
也就是


第一个矩阵的行数 x 第一个矩阵的列数(第二个矩阵的行数) x 第二个矩阵的列数

3个以上的矩阵相乘时,由于我们选择的计算次序不同,我们所需要进行的数乘次数也会不一样。
例如:
4个矩阵ABCD,设它们维数分别为
A:50x10 B:10x40 C:40x30 D:30x5
则它的计算次序以及数乘次数有以下几种:

加括号方式 连乘积计算量
(A(B(CD))) 40x30x5+10x40x5+50x10x5 = 10500
(A((BC)D)) 10x40x30+10x30x5+50x10x5 = 16000
((AB)(CD)) 50x10x40+40x30x5+50x40x5 = 36000
(((AB)C)D) 50x10x40+50x40x30+50x30x5 = 87500
((A(BC))D) 10x40x30+50x10x30+50x30x5 = 34500

求解这个问题,我们可以这么想,无论多少个矩阵连乘,无论选择怎样的次序,我们每次计算的都是两个矩阵的连乘积。比如ABCD,我们选择次序为(A(B(CD))),第一次我们计算的是CD,CD乘完后得出一个临时矩阵T,第二次我们计算的就是B和T。得到临时矩阵T2,接着计算的就是A乘T2。
那么,我们可以用分治法的思想,n个矩阵连乘。我们看作A[1:n]。我们就可以分为两段[1:k]和[k+1:n]( k = 1,2,…,n-1)。也就是计算次序我们从中间断开,加括号于k和k+1之间。即(A1…Ak)(Ak+1…An)。然后我们在分别递归计算A[1:k]和A[k+1:n]的计算次序。然后这两个结果相乘就得到了A[1:n]。
所以对于矩阵连乘序列A[i:j]来说,它的数乘次数M[i:j]就有一个递归公式如下:

动态规划-矩阵连乘问题

其中P(i-1)代表矩阵Ai的行数,Pk代表Ak的列数。Pj代表Aj的列数。
通过实现这个递归公式,我们就可以做到用分治法来求解这一题。但是!这么做我们的效率是不高的。我们来看一下递归求解M[1:4]将经历哪些步骤
动态规划-矩阵连乘问题

从上图我们可以看到,在我们递归求解的过程中,进行了非常多的重复运算,这就是为什么说分治法解决这一题是一个可行的方法,但不是一个有效的算法。
那么,我们怎样去避免这些重复的运算呢?
很简单,既然你计算一个问题的过程中可能会去计算很多重复的子问题。那么我就先去计算那些子问题,并将子问题的解先保存下来。那么在我需要再次去计算子问题的时候,只需要去查一下表就行了。
也就是将一个自顶向下的计算过程改为自底向上的计算,先解决规模最小的子问题。然后逐层往上解决,在你需要子问题的解时,我只需要去查一下保存的值即可。这样就不用进行重复的计算。
接下来我们来解一个具体的例题:
4个矩阵ABCD连乘,设它们维数分别为
A:50x10 B:10x40 C:40x30 D:30x5
求怎样的计算次序使得数乘次数最少?


动态规划-矩阵连乘问题

接下来,我们求次一级规模,次一级就是两个矩阵连乘。即A[1:2],A[2:3],A[3:4]三种。
根据我们之前的递归公式可以知道:(注:由于我们m矩阵的行与列均是从0开始,所以代码写出来与递归公式有所差异)


m[0][1] = m[0][0] + m[1][1] + p[0]*p[1]*p[2];
m[1][2] = m[1][1] + m[2][2] + p[1]*p[2]*p[3];
m[2][3] = m[2][2] + m[3][3] + p[2]*p[3]*p[4];

动态规划-矩阵连乘问题

然后我们再往上求,这次就得求三个矩阵连乘的情况,即A[1:3],A[2:3]两种情况。根据递归公式可得:


动态规划-矩阵连乘问题

在我们需要使用子问题的解如m[1][2]时,只用查表就行了。
最后就是计算我们题目要求的,4个矩阵连乘时的最少数乘次数。


动态规划-矩阵连乘问题

这样,m[0][3]就是A[1:4]的最少数乘次数。
可以看到我们的计算次序是这样的:
动态规划-矩阵连乘问题

我们还可以添加一个二维数组s。在每一次计算的时候呢,将最少数乘次数所选择断开的点也就是k+1给记录下来。

通过这个矩阵,我们就能够将取得最少数乘次数的计算次序还原回来
如A[1:4],我们查表s[0][3]得1,我们就知道断在第一个矩阵,所以就分为了A[1:1]和A[2:4]。序列变为(A(BCD))。
我们继续查表s[1][2]得2,则第二次断在第二个矩阵,分为A[2][2]和A[3][4]。序列变为(A(B(CD)))
这就是我们得到的最优计算次序。


下面是完整的代码:

 1#include <iostream>
2#include <string>
3void matrix(int **M, int* P, int n, int **S) {                        //计算数乘次数以及加括号位置
4    for (int r = 1; r < n; r++){
5        for (int i = 0; i < n-r; i++){
6            int j = i + r;
7            M[i][j] = M[i + 1][j] + P[i] * P[i + 1] * P[j + 1];                     //先对m[i][j]一个初始值
8            S[i][j] = i + 1;
9            for (int k = i + 1; k < j; k++){                                        //循环寻找数乘次数最少的情况
10                int t = M[i][k] + M[k + 1][j] + P[i] * P[k + 1] * P[j + 1];
11                if (t < M[i][j]){
12                    M[i][j] = t;
13                    S[i][j] = k + 1;
14                }
15            }
16        }
17    }
18}
19void show(int **S,std::string* matrixlist,int i,int j) { //输出函数
20    if (i == j)                                                    
21        std::cout << matrixlist[i];
22    else {
23        int k = S[i][j];
24        std::cout << '(';                               //先输出左括号
25        show(S, matrixlist, i, k-1);                    //递归调用输出左边部分的加括号方式
26        show(S, matrixlist, k, j);                      //递归调用输出右边部分的加括号方式
27        std::cout << ')';                               //输出右括号
28    }
29}
30void main() {
31    int n;
32    std::cout << "请输入矩阵个数n:";
33    std::cin >> n;
34    int **M = new int*[n];
35    int **S = new int*[n];
36    int *P = new int[n + 1];
37    std::string *matrixlist = new std::string[n];
38    for (int i = 0; i < n; i++) {
39        M[i] = new int[n];
40        S[i] = new int[n];
41    }
42    for (int i = 0; i < n; i++) {
43        for (int j = 0; j < n; j++){                        //初始化M矩阵
44            M[i][j] = 0;
45            S[i][j] = 0;
46        }
47    }
48    std::cout << "请输入各个矩阵的编号:";
49    for (int i = 0; i < n; i++){
50        std::cin >> matrixlist[i];
51    }
52    std::cout << "请输入各个矩阵的维度信息(n+1个整数):";
53    for (int i = 0; i <= n; i++){
54        std::cin >> P[i];
55    }
56    matrix(M, P, n, S);                                 //计算M矩阵与S矩阵
57    std::cout << "M矩阵" << " ";
58    for (int i = 1; i <= n; i++){
59        std::cout << i << " ";
60    }
61    std::cout << std::endl;
62    for (int i = 0; i < n; i++)     {
63        std::cout << i + 1 << " ";
64        for (int j = 0; j < n; j++){
65            std::cout << M[i][j] << " ";
66        }
67        std::cout << std::endl;
68    }
69    std::cout << "S矩阵" << " ";
70    for (int i = 1; i <= n; i++){
71        std::cout << i << " ";
72    }
73    std::cout << std::endl;
74    for (int i = 0; i < n; i++){
75        std::cout << i + 1 << " ";
76        for (int j = 0; j < n; j++){
77            std::cout << S[i][j] << " ";
78        }
79        std::cout << std::endl;
80    }
81    std::cout << "矩阵连乘最少数乘加括号方式为:";
82    show(S, matrixlist, 0, n-1);                            //输出加括号方式
83    std::cout << std::endl;
84    system("pause");
85}

最终结果示意图:


以上是关于动态规划-矩阵连乘问题的主要内容,如果未能解决你的问题,请参考以下文章

矩阵连乘最优解---动态规划

动态规划 - 矩阵连乘问题

动态规划 - 矩阵连乘问题

矩阵连乘问题_动态规划

动态规划之矩阵连乘

动态规划-矩阵连乘问题