矩阵连乘

【问题描述】

给定 nn 个矩阵 {A1,A2,A3...An}\{A_1, A_2, A_3,... A_n\},其中,AiA_iAi+1 (i=1,2..n1)A_{i+1}\ (i=1, 2,.. n-1) 是可乘的。用加括号的方法表示矩阵连乘的次序,不同的计算次序计算量(乘法次数)是不同的,找出一种加括号的方法,使得矩阵连乘的计算量最小。

例如:

A1A_1M5×10M_{5×10} 的矩阵;A2A_2M10×100M_{10×100} 的矩阵;A3A_3M100×2M_{100×2} 的矩阵。

那么有两种加括号的方法:

  1. (A1A2)A3(A_1A_2)A_3
  2. A1(A2A3)A_1(A_2A_3)

11 种加括号方法运算量:5×10×100+5×100×2=60005× 10× 100+5× 100× 2=6000

22 种加括号方法运算量:10×100×2+5×10×2=210010× 100× 2+5× 10× 2=2100

可以看出,不同的加括号办法,矩阵乘法的运算次数可能有巨大的差别!

【问题分析】

1.前置知识:

(1)什么是矩阵可乘?

如果两个矩阵,第 11 个矩阵的列等于第 22 个矩阵的行时,那么这两个矩阵是可乘的。

(2)矩阵相乘后的结果是什么?

两个矩阵相乘的结果矩阵,其行、列分别等于第 11 个矩阵的行、第 22 个矩阵的列。

多个矩阵相乘的结果矩阵,其行、列分别等于第 11 个矩阵的行、最后 11 个矩阵的列。而且无论矩阵的计算次序如何都不影响它们的结果矩阵。

(3)两个矩阵相乘需要多少次乘法?

两个矩阵 A3×2A_{3×2}A2×4A_{2×4} 相乘执行乘法运算的次数为 3×2×43×2×4

因此,Am×nA_{m×n}An×kA_{n×k} 相乘执行乘法运算的次数为 mnkm*n*k

(4)穷举

如果穷举所有的加括号方法,那么加括号的所有方案是一个卡特兰数序列,其算法时间复杂度为 2n2^n,是指数阶,因此穷举的办法是很糟的。

2.动态规划

(1)分析最优解的结构特征

假设我们已经知道了在第 kk 个位置加括号会得到最优解,那么原问题就变成了两个子问题:(AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j})

原问题的最优解是否包含子问题的最优解呢?

假设 AiAi+1...AjA_iA_{i+1}...A_{j} 的乘法次数是 cc(AiAi+1...Ak)(A_iA_{i+1}...A_{k}) 的乘法次数是 aa(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的乘法次数是 bb(AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的结果矩阵相乘的乘法次数是 dd,那么 c=a+b+dc=a+b+d

而无论两个子问题 (AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的计算次序如何,都不影响它们结果矩阵,两个结果矩阵相乘的乘法次数 dd 不变。

因此,我们只需要证明:如果 cc 是最优的,则 aabb 一定是最优的(即原问题的最优解包含子问题的最优解)。

反证法:如果 aa 不是最优的,(AiAi+1...Ak)(A_iA_{i+1}...A_{k}) 存在一个最优解 aa'a<aa'<a,那么,a+b+d<ca'+b+d<c,所以 cc 不是最优的,这与假设 cc 是最优的矛盾,因此,如果 cc 是最优的,则 aa 一定是最优的。同理,可证 bb 也是最优的。因此,如果 cc 是最优的,则 aabb 一定是最优的。

因此,矩阵连乘问题具有最优子结构性质。

(2)建立最优值递归式

用二维数组 m[i][j]m[i][j] 表示 AiAi+1...AjA_iA_{i+1}...A_{j} 矩阵连乘的最优值,那么两个子问题 (AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 对应的最优值分别是 m[i[k]m[i[k]m[k+1][j]m[k+1][j]。剩下的只需要考查 (AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的结果矩阵相乘的乘法次数了。

设矩阵 AmA_m 的行数为 pmp_m,列数为 qmq_mm=i,i+1,...jm=i, i+1,...j,且矩阵是可乘的,即相邻矩阵前一个矩阵的列等于下一个矩阵的行 (qm=pm+1)(q_m= p_{m+1})(AiAi+1...Ak)(A_iA_{i+1}...A_{k}) 的结果是一个 pi×qkp_i×q_k 矩阵,(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的结果是一个 pk+1×qjp_{k+1}×q_j 矩阵,qk=pk+1q_k= p_{k+1},两个结果矩阵相乘的乘法次数是 pi×pk+1qjp_i×p_{k+1}*q_j

用一维数组 p[ ]p[\ ] 来记录矩阵的行和列,第 ii 个矩阵的行数存储在数组的第 i1i-1 位置,列数存储在数组的第 ii 位置,那么 pipk+1qjp_i*p_{k+1}*q_j 对应的数组元素相乘为 p[i1]p[k]p[j]p[i-1]*p[k]* p[j],递归式为:

m[i][j]={0,i=j,min{m[i][k]+m[k+1][j]+p[i1]p[k]p[j]},i<j m[i][j]= \begin{cases} 0, & i=j,\\ min\{m[i][k]+m[k+1][j]+ p[i-1]*p[k]*p[j]\}, & i<j \end{cases}

(3)自底向上计算并记录最优值

初始化:采用一维数组 p[ ]p[\ ] 来记录矩阵的行和列,m[i][i]=0m[i][i]=0s[i][i]=0s[i][i]=0,其中 i=1,2,3,...ni=1,2,3, ... n

先求 22 个矩阵相乘的最优值,再求 33 个矩阵相乘的最优值,直到 nn 个矩阵连乘的最优值。

  • 按照递归关系式计算 22 个矩阵 AiA_iAi+1A_{i+1} 相乘时的最优值,j=i+1j=i+1,并将其存入 m[i][j]m[i][j],同时将最优策略记入 s[i][j]s[i][j]i=1,2,3,...n1i=1, 2, 3,...n-1
  • 按照递归关系式计算 33 个矩阵相乘 AiA_iAi+1A_{i+1}Ai+2A_{i+2} 相乘时的最优值,j=i+2j=i+2,并将其存入 m[i][j]m[i][j],同时将最优策略记入 s[i][j]s[i][j]i=1,2,3,...n2i=1, 2, 3, ... n-2
  • 以此类推,直到求出 nn 个矩阵相乘的最优值 m[1][n]m[1][n]

(4)构造最优解

上面得到的最优值只是矩阵连乘的最小的乘法次数,并不知道加括号的次序,需要从记录表中还原加括号次序,构造出最优解。

用二维数组 s[ ][ ]s[\ ][\ ] 来存放各个子问题的最优决策(加括号的位置)。

根据最优决策信息数组 s[ ][ ]s[\ ][\ ] 递归构造最优解。s[1][n]s[1][n] 表示 A1A2...AnA_1A_2...A_n 最优解的加括号位置,即 (A1A2...As[1][n])(As[1][n]+1...An)(A_1A_2...A_{s[1][n]})(A_{s[1][n]+1}...A_n),我们再递归构造两个子问题 (A1A2...As[1][n])(A_1A_2...A_{s[1][n]})(As[1][n]+1...An)(A_{s[1][n]+1}...A_n) 的最优解加括号位置,一直递归到子问题只包含一个矩阵为止。

【算法实现】

#include <cstdio>
#include <cstring>
#include <iostream>

using namespace std;

const int msize = 100;

int p[msize];
int m[msize][msize];
int s[msize][msize];
int n;

void matrixchain()
{
    memset(m, 0, sizeof(m));
    memset(s, 0, sizeof(s));
    for(int len = 2; len <= n; len++)                   // 不同规模的子问题
    {
        for(int l = 1; l + len - 1 <= n; l++)
        {
            int r = l + len - 1;
            
            m[l][r] = m[l+1][r] + p[l-1] * p[l] * p[r];	// 决策为k=l的乘法次数
            s[l][r] = l;                               	// 子问题的最优策略是l;
            
            for(int k = l + 1; k < r; k++)          	// 对从l到r的所有决策,求最优值,记录最优策略
            {
                int t = m[l][k] + m[k+1][r] + p[l-1] * p[k] * p[r];
                if(t < m[l][r])
                {
                    m[l][r] = t;
                    s[l][r] = k;
                }
            }
        }
    }
}

void print(int i, int j)
{
    if(i == j)
    {
       cout << "A[" << i << "]";
       return ;
    }
    cout << "(";
    print(i, s[i][j]);
    print(s[i][j] + 1, j);
    cout << ")";
}

int main()
{
    cout << "请输入矩阵的个数 n:";
    cin >> n;
    
    cout << "请依次输入每个矩阵的行数和最后一个矩阵的列数:";
    for(int i = 0; i <= n; i++) cin >> p[i];
    
    matrixchain();
    
    print(1, n);
    cout << endl;
    
    /*
    for(int i = 1; i <= n; i++)             // 用于测试
    {
        for(int j = i; j <= n; j++) cout << m[i][j] << "  ";
        cout << endl;
    }
    for(int i = 1; i <= n; i++)
    {
        for(int j = i; j <= n; j++) cout << s[i][j] << "  ";
        cout << endl;
    }
    cout << endl;
    */
    
    cout << "最小计算量的值为 " << m[1][n] << endl;
}

样例图解如下:

初始化:

计算 22 个矩阵相乘的最优值

计算 33 个矩阵相乘的最优值

计算 44 个矩阵相乘的最优值

计算 55 个矩阵相乘的最优值

构造最优解

【复杂度分析】

(1)时间复杂度:由程序可以得出,语句 t=m[l][k]+m[k+1][r]+p[l1]p[k]p[r]t= m[l][k] + m[k+1][r] +p[l-1]*p[k]*p[r],它是算法的基本语句,在 33 层 for 循环中嵌套。最坏情况下,该语句的执行次数为 O(n3)O(n^3),print() 函数算法的时间主要取决于递归,时间复杂度为 O(n)O(n)。故该程序的时间复杂度为 O(n3)O(n^3)

(2)空间复杂度:该程序的输入数据的数组为 p[ ]p[\ ],辅助变量为 iijjrrttkkm[ ][ ]m[\ ][\ ]s[ ][ ]s[\ ][\ ],空间复杂度取决于辅助空间,因此空间复杂度为 O(n2)O(n^2)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章