问题描述
给定
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[pi−1,pi]A0=A[p0,p1]
即第
i
i
i 个矩阵
A
i
A_i
Ai 的规模为
p
i
−
1
p_{i-1}
pi−1 行、
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}
pi−1pipi+1,两个矩阵相乘后变为规模为
[
p
i
−
1
,
p
i
+
1
]
[p_{i-1},p_{i+1}]
[pi−1,pi+1]
求完成此矩阵运算需要的最少乘法次数
问题分析
动态规划思想在于将原问题分解为若干个相关的子问题,先求解子问题,再根据子问题构建原问题的解
最优子结构性质
规定矩阵连乘
A
i
A
i
+
1
⋯
A
j
A_iA_{i+1}\cdots A_j
AiAi+1⋯Aj 表示为
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]+pi−1pkpj), i≤k≤j
自底向上的求解
m[i][j] | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 0 | ↘ \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 2、3、4…n−1 个矩阵连乘的最小乘法次数,再根据已有次数求出最优解
若要求出计算顺序,可将分段 k k k 记录在 s [ i ] [ j ] s[i][j] s[i][j] 中
s[i][j] | 1 | 2 | 3 | 4 | 5 |
---|---|---|---|---|---|
1 | 0 | ||||
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]
初学者多有不足,请大佬指正。