动态规划:矩阵链乘法最优问题

矩阵链乘法是动态规划中的经典区间 DP 问题。它的重点并不是如何计算两个矩阵相乘,而是:给定一串矩阵相乘,如何加括号,才能让总计算代价最小?

如果矩阵 \(A\) 的大小是 \(a \times b\),矩阵 \(B\) 的大小是 \(b \times c\),那么 \(AB\) 的大小是 \(a \times c\),计算代价为:

\[a \times b \times c \]

这里的代价通常指的是标量乘法次数。现在我要计算多个矩阵连续相乘,假设有一串矩阵:

\[A_1 A_2 A_3 \cdots A_n \]

矩阵乘法满足结合律,因此我们可以用不同方式加括号。例如:\( (A_1A_2)A_3 \)\( A_1(A_2A_3) \)。这两种方式的结果相同,但计算代价可能不同。

假设有三个矩阵:

\[A_1: 10 \times 100 \]

\[A_2: 100 \times 5 \]

\[A_3: 5 \times 50 \]

方式一:\((A_1A_2)A_3\)

先计算 \(A_1A_2\)\( 10 \times 100 \times 5 = 5000 \);得到的矩阵大小是 \(10 \times 5\)

然后再乘 \(A_3\)\( 10 \times 5 \times 50 = 2500 \);所以总代价是:\( 5000 + 2500 = 7500 \)

方式二:\(A_1(A_2A_3)\)

先计算 \(A_2A_3\)\( 100 \times 5 \times 50 = 25000 \);得到的矩阵大小是 \(100 \times 50\)

然后再乘 \(A_1\)\( 10 \times 100 \times 50 = 50000 \);所以总代价是:\( 25000 + 50000 = 75000 \)

可以看到,两种加括号方式的计算结果一样,但计算代价差了十倍。

对于 \(n\) 个矩阵:

\[A_1, A_2, \dots, A_n \]

上一个矩阵的列数必须等于下一个矩阵的行数。可用一个数组 \(p\) 来表示它们的维度。

如果矩阵 \(A_i\) 的大小是 \( p_{i-1} \times p_i \),那么数组 \(p\) 的长度就是 \(n+1\)

例如:p = [10, 100, 5, 50] 表示:

A1: 10 × 100
A2: 100 × 5
A3: 5 × 50

动态规划

矩阵链乘法具有明显的最优子结构。假设我们要求 \(A_iA_{i+1}\cdots A_j\) 的最小计算代价。最后一次乘法一定是把这一段矩阵分成两部分:

\[(A_i \cdots A_k)(A_{k+1} \cdots A_j) \]

如果整个区间 \([i,j]\) 的加括号方式是最优的,那么左半部分 \([i,k]\) 和右半部分 \([k+1,j]\) 也必须分别是最优的。

\( dp[i][j] \) 表示计算矩阵链 \( A_iA_{i+1}\cdots A_j \) 所需要的最小代价。最终我们要求的答案就是:\( dp[1][n] \)

初始条件:如果区间里只有一个矩阵,那么不需要做任何乘法。因此:\( dp[i][i] = 0 \)

状态转移:对于区间 \([i,j]\),我们枚举最后一次乘法的分割点 \(k\)

左半部分的最小代价是:\( dp[i][k] \),右半部分的最小代价是:\( dp[k+1][j] \)

接下来需要把这两个结果矩阵相乘。由于 \(A_i \cdots A_k\) 的结果矩阵大小是:\( p_{i-1} \times p_k \),而 \(A_{k+1} \cdots A_j\) 的结果矩阵大小是:\( p_k \times p_j \)

所以最后一次乘法的代价是:\( p_{i-1} \times p_k \times p_j \)

因此状态转移方程为:

\[dp[i][j] = \min_{i \le k < j} {dp[i][k] + dp[k+1][j] + p_{i-1}p_kp_j} \]

填表顺序

这是典型的区间 dp,长区间的答案依赖短区间。所以应该按照区间长度从小到大进行计算,先枚举长度再枚举起点

代码实现

def matrix_chain_order(p):
    n = len(p) - 1

    dp = [[0] * (n + 1) for _ in range(n + 1)]

    for length in range(2, n + 1):
        for i in range(1, n - length + 2):
            j = i + length - 1
            dp[i][j] = float("inf")

            for k in range(i, j):
                cost = (
                    dp[i][k]
                    + dp[k + 1][j]
                    + p[i - 1] * p[k] * p[j]
                )

                dp[i][j] = min(dp[i][j], cost)

    return dp[1][n]

复杂度分析

一共有 \(O(n^2)\) 个状态,因为状态由区间左右端点 \(i\)\(j\) 决定。

对于每个状态 \(dp[i][j]\),需要枚举分割点 \(k\),最多需要 \(O(n)\) 次。

因此总时间复杂度是:

\[O(n^3) \]

动态规划表 \(dp\) 的大小是 \(n \times n\),因此空间复杂度是:

\[O(n^2) \]

posted @ 2026-04-26 17:16  Ofnoname  阅读(21)  评论(0)    收藏  举报