矩阵链相乘的最少乘法次数(动态规划解法)
矩阵链相乘问题是动态规划的经典应用场景。当多个矩阵相乘时,不同的相乘顺序会导致乘法运算的总次数差异巨大。我们的目标是找到一种相乘顺序,使得总的乘法次数最少。
问题分析
假设有矩阵序列 A1,A2,…,An,其中 Ai 的维度为 pi−1×pi。矩阵相乘满足结合律,但不同的结合方式会影响乘法运算的总次数。例如,三个矩阵 A(10×30)、B(30×5)、C(5×60),按 (AB)C 计算的乘法次数为 10×30×5+10×5×60=4500,按 A(BC) 计算的乘法次数为 30×5×60+10×30×60=27000,显然前者更优。
矩阵相乘的基本逻辑
当我们有一组维度数据,比如 74 16 58
,它能构成两个矩阵:矩阵 a 的维度是 [74,16](对应数据的第 i 和 i+1 个元素),矩阵 b 的维度是 [16,58](对应数据的第 i+1 和 i+2 个元素)。
矩阵 a 和 b 相乘时,乘法次数的计算方式为 74×58×16。相乘之后,会得到一个新的矩阵,其维度为 [74,58],后续可继续与其他矩阵相乘。
问题核心:寻找最少乘法次数
我们的目标是,对于由 n 个矩阵组成的链,找到一种相乘顺序,使得总的乘法次数最少。
以包含 6 个维度数据(对应 5 个矩阵 a b c d e)的情况为例:
- 两个矩阵相乘时,只有 1 种组合情况(如 ab)。
- 三个矩阵相乘时,有 2 种组合情况(如 a(bc) 或 (ab)c)。
- 四个矩阵相乘时,有 3 种组合情况(如 a(bcd)、(ab)(cd) 或 (abc)d)。
- 以此类推,n 个矩阵相乘时,会有 n−1 种组合情况。
可以发现,不管有多少个矩阵相乘,每种组合情况都是将所有矩阵分成两部分。这一特性表明,矩阵链相乘问题具有最优子结构,非常适合用动态规划来解决。
动态规划的思路推导
以 4 个矩阵(对应 5 个维度数据 74 16 58 58 88
)的情况为例,这 4 个矩阵分别是 a[74,16]、b[16,58]、c[58,58]、d[58,88]。
1. 设定断点分割矩阵链
我们可以设定一个断点 k(范围是 1 到 n),表示在第 k 个矩阵后将整个矩阵链隔开。
比如,当断点 k=2 时,矩阵链被分成两部分:ab 和 cd。
2. 子问题的最少乘法次数计算
- 计算 ab 部分的最少乘法次数:ab 相乘的次数为 74×58×16,得到新矩阵维度为 [74,58],这部分的最少次数用 dp[1][2] 表示(dp[i][j] 表示从第 i 个矩阵到第 j 个矩阵相乘的最少次数)。
- 计算 cd 部分的最少乘法次数:cd 相乘的次数为 58×88×58,得到新矩阵维度为 [58,88],这部分的最少次数用 dp[3][4] 表示。
3. 整体最少乘法次数的计算
此时,整个矩阵链 abcd 的最少乘法次数 dp[1][4] 就等于 ab 部分的最少次数 dp[1][2] 加上 cd 部分的最少次数 dp[3][4],再加上将这两部分结果矩阵相乘的次数 74×88×58(即 i×(j+1)×(k+1),其中 i=1,j=4,k=2)。
再比如,当断点 k=1 时,矩阵链被分成 a 和 bcd 两部分:
- a 是单个矩阵,无需计算乘法次数。
- 对于 bcd 部分,又存在多种分割情况(如 b(cd) 或 (bc)d),需要先求出 bcd 部分的最少乘法次数 dp[2][4](取这些分割情况中的最小值)。
- 最后,整个矩阵链 abcd 的最少乘法次数 dp[1][4] 等于 bcd 部分的最少次数 dp[2][4] 加上将 a 与 bcd 结果矩阵相乘的次数 i×(j+1)×(k+1)(其中 i=1,j=4,k=1)。
四、核心公式与递推过程
通过对各种分割情况的分析,我们可以总结出状态转移公式:
dp[i][j] = min{dp[i][k] + dp[k+1][j] + nums[i]×nums[j+1]×nums[k+1]}
其中,nums 是存储矩阵维度的数组,dp[i][j] 表示从第 i 个矩阵到第 j 个矩阵相乘的最少次数。
递推过程是:
- 先计算长度为 2 的矩阵链的最少乘法次数(如 dp[1][2]、dp[2][3] 等)。
- 基于长度为 2 的结果,计算长度为 3 的矩阵链的最少乘法次数(如 dp[1][3]、dp[2][4] 等)。
- 以此类推,逐步计算更长的矩阵链,每次都取当前所有可能分割情况中的最少乘法次数,直到得到整个矩阵链(从第 1 个到第 n 个矩阵)的最少乘法次数 dp[1][n]。
代码实现
#include <iostream>
#include <vector>
#include <algorithm>
#include <climits> // 用于INT_MAX的定义
using namespace std;// 计算矩阵链相乘的最少乘法次数
// nums:存储矩阵维度的数组,nums[1..n]有效,对应n-1个矩阵
// n:维度数组的长度(矩阵数量为n-1)
void matrixChainMinMultiplications(vector<int>& nums, int n) {// dp[i][j]表示第i个矩阵到第j个矩阵相乘的最少乘法次数vector<vector<int>> dp(n, vector<int>(n, 0));// 遍历矩阵链的长度,从2开始(至少需要2个矩阵才能相乘)for (int length = 2; length <= n - 1; ++length) {// 遍历所有可能的起始矩阵ifor (int i = 1; i + length - 1 <= n - 1; ++i) {int j = i + length - 1; // 计算对应的结束矩阵jdp[i][j] = INT_MAX; // 初始化为最大整数,用于后续取最小值// 遍历所有可能的分割点k,寻找最优分割for (int k = i; k < j; ++k) {// 状态转移:当前分割的乘法次数 = 左半部分最少次数 + 右半部分最少次数 + 当前分割的乘法次数dp[i][j] = min(dp[i][j], dp[i][k] + dp[k + 1][j] + nums[i] * nums[j + 1] * nums[k + 1]);}}}// 输出从第1个矩阵到第n-1个矩阵相乘的最少乘法次数cout << dp[1][n - 1] << endl;
}int main() {int n;// 多组数据输入,直到输入结束while (cin >> n) {vector<int> nums(n + 1); // 维度数组,索引1到n有效for (int i = 1; i <= n; ++i) {cin >> nums[i];}matrixChainMinMultiplications(nums, n);}return 0;
}
代码注释说明
matrixChainMinMultiplications
函数:核心动态规划逻辑,计算矩阵链相乘的最少乘法次数。- 首先初始化
dp
数组,dp[i][j]
表示第 i 到第 j 个矩阵相乘的最少次数。 - 外层循环按矩阵链长度
length
递增遍历,确保先计算短链,再利用短链结果计算长链。 - 中层循环遍历所有可能的起始矩阵
i
,并计算对应的结束矩阵j
。 - 内层循环遍历分割点
k
,通过状态转移方程更新dp[i][j]
为最小值。
- 首先初始化
main
函数:处理多组输入数据,读取矩阵维度数组并调用核心函数计算结果。示例运行
输入:
6 10 1 50 50 20 5
输出:
33000
这种动态规划方法的时间复杂度为 O(n3),空间复杂度为 O(n2),能够高效解决矩阵链相乘的最少乘法次数问题。