矩阵连乘积问题的一种动态规划算法

问题描述

给定 n n n 个矩阵 A 1 , A 2 , … , A n A_1, A_2, \dots, A_n A1,A2,,An 其中 A i A_i Ai A i + 1 A_{i+1} Ai+1 可以相乘,即用 p i , i ∈ [ 0 , n ] p_i, i \in [0, n] pi,i[0,n] 表示矩阵的规模(行数 × \times × 列数)时可以表示为
A i = A [ p i − 1 , p i ] A 0 = A [ p 0 , p 1 ] A_i = A[p_{i-1},p_i]\\ A_0 = A[p_0, p_1] Ai=A[pi1,pi]A0=A[p0,p1]
即第 i i i 个矩阵 A i A_i Ai 的规模为 p i − 1 p_{i-1} pi1 行、 p i p_i pi 列,其中第一个矩阵 A 1 A_1 A1 的规模为 p 0 p_0 p0 行、 p 1 p_1 p1列。那么,相邻两个矩阵 A i , A i + 1 A_i,A_{i+1} Ai,Ai+1 可以相乘,且要做的乘法次数为 p i − 1 p i p i + 1 p_{i-1} p_{i} p_{i+1} pi1pipi+1,两个矩阵相乘后变为规模为 [ p i − 1 , p i + 1 ] [p_{i-1},p_{i+1}] [pi1,pi+1]
求完成此矩阵运算需要的最少乘法次数

问题分析

动态规划思想在于将原问题分解为若干个相关的子问题,先求解子问题,再根据子问题构建原问题的解

最优子结构性质

规定矩阵连乘 A i A i + 1 ⋯ A j A_iA_{i+1}\cdots A_j AiAi+1Aj 表示为 A [ i : j ] A[i:j] A[i:j]
不同顺序计算的矩阵连乘所需的乘法次数是不同的
A 1 [ 3 , 4 ]   A 2 [ 4 , 7 ]   A 3 [ 7 , 9 ] ( A 1 A 2 ) A 3 = 3 × 4 × 7 + 3 × 7 × 9 = 273 A 1 ( A 2 A 3 ) = 4 × 7 × 9 + 3 × 4 × 9 = 360 A_1[3,4]\ A_2[4,7] \ A_3[7,9]\\ (A_1A_2)A_3 =3\times 4 \times 7 + 3 \times 7 \times 9 = 273\\ A_1(A_2A_3) = 4 \times 7 \times 9 + 3 \times 4 \times 9 = 360 A1[3,4] A2[4,7] A3[7,9](A1A2)A3=3×4×7+3×7×9=273A1(A2A3)=4×7×9+3×4×9=360
那么问题转换为确定矩阵计算的顺序,即通过某种顺序计算矩阵连乘,所需的乘法计算总数是最少的。
假设 n n n 个矩阵相乘的最小乘法次数为 r e s n res_{n} resn 是通过 A [ 1 : k ] A[1:k] A[1:k] A [ k + 1 , n ] A[k+1,n] A[k+1,n] 的次序计算的,
若有一种计算 A [ 1 : k ] A[1:k] A[1:k] 的运算次序得到的乘法次数更少,则用此顺序替换原顺序计算 A [ 1 : k ] A[1:k] A[1:k] 再计算 A [ 1 : n ] A[1:n] A[1:n] 得到的乘法计算总数小于 r e s n res_n resn 即与假设矛盾。
反之,若 A [ i : n ] A[i:n] A[i:n] 的最优解是通过 A [ 1 : k ] A[1:k] A[1:k] A [ k + 1 , n ] A[k+1,n] A[k+1,n] 计算得来的,那么计算 A [ 1 : k ] A[1:k] A[1:k] A [ k + 1 : n ] A[k+1:n] A[k+1:n] 所用的乘法次数也是最少的,即计算次序也是最优解
即可以证明此问题满足最优子结构性质,即原问题的最优解包含其子问题的最优解。

递推关系

m [ i ] [ j ] m[i][j] m[i][j] 表示矩阵连乘 A [ i : j ] A[i:j] A[i:j] 的最小乘法次数,有
m [ i ] [ j ] = { 0 ,   i = j m i n ( m [ i ] [ k ] + m [ k + 1 ] [ j ] + p i − 1 p k p j ) ,   i ≤ k ≤ 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 \le k \le j \end{cases} m[i][j]={0, i=jmin(m[i][k]+m[k+1][j]+pi1pkpj), ikj

自底向上的求解

m[i][j]12345
10 ↘ \searrow ↘ \searrow ↘ \searrow r e s n res_n resn
2\0 ↘ \searrow ↘ \searrow ↘ \searrow
3\\0 ↘ \searrow ↘ \searrow
4\\\0 ↘ \searrow
5\\\\0

每行依次求出相邻 2 、 3 、 4 … n − 1 2、3、4 \dots n-1 234n1 个矩阵连乘的最小乘法次数,再根据已有次数求出最优解

若要求出计算顺序,可将分段 k k k 记录在 s [ i ] [ j ] s[i][j] s[i][j]

s[i][j]12345
10
2\0
3\\0
4\\\0
5\\\\0

代码实现

//矩阵连乘积问题
#include <iostream>

using namespace std;

const int N = 1e4+4;
int p[N+1];//各矩阵大小
int m[N][N];//最小乘法次数
int s[N][N];//分割位置,即对i到j的矩阵如何划分
int trace[N];//记录分割次序

void matrixChain(int n)
{
    for (int i = 1; i <= n; i++) m[i][i] = 0;
    //一个矩阵连乘需要的乘法次数为0

    for (int len = 2; len <= n; len++)//len个相邻矩阵连乘(2,3-->n)
    {
        for (int i = 1; i <= n - len + 1; i++)
        {
            int j = i + len - 1;//确定长度为len的矩阵范围
            //先默认计算 k = i 时有最少乘法,再遍历更新
            m[i][j] = m[i + 1][j] + p[i - 1] * p[i] * p[j];
            s[i][j] = i;
            for (int k = i + 1; k < j; k++)
            //j<=n, k<n
            //n=n-len+1时最大,此时j=n,而k取[i,n-1]
            {
                int tmp = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
                if (tmp < m[i][j])//出现更好的顺序则更新
                {
                    m[i][j] = tmp;
                    s[i][j] = k;
                }
            }
        }
    }
}

void traceBack(int i, int j)
{
    if (i == j) return;
    traceBack(i, s[i][j]);
    traceBack(s[i][j] + 1, j);

    cout << "A_[" << i << ":" << s[i][j] << "]\t";
    cout << "A_[" << s[i][j] + 1 << ":" << j << "]" << endl;
}
int main()
{
    int n;
    cin >> n;
    for (int i = 0; i <= n; i++) cin >> p[i];

    matrixChain(n);

    cout << "最小乘法次数为" << m[1][n] << endl;
    
    traceBack(1, n);

    return 0;
}

样例

3
3 4 7 9
最小乘法次数为273
A_[1:1] A_[2:2]
A_[1:2] A_[3:3]

初学者多有不足,请大佬指正。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值