动态规划——矩阵连乘问题
问题描述
\(\{A_1,A_2\dots A_n\}\)n个矩阵相乘,最少需要进行多少次乘法运算?
解答思路
划分
假设三个矩阵连乘,结果可能为
\[\begin {array}{c|c}
Result1&(A_1A_2)A_3\\
Result2&A_1(A_2A_3)
\end {array}
\]
四个矩阵连乘,结果可能为
\[\begin {array}{c|c}
Result1&((A_1A_2)A_3)A_4\\
Result2&(A_1(A_2A_3))A_4\\
Result3&A_1(A_2(A_3A_4))\\
Result4&(A_1A_2)(A_3A_4)
\end {array}
\]
显然,四个矩阵连乘中三个矩阵连乘组成的矩阵链的解法和三个矩阵连乘的解法一样。那么n个矩阵相乘,我们需要考虑一个合理的划分方式,划分所有的可能。
设n个矩阵\(A_1,A_2,A_3\dots A_n\)。不难发现,对于任意规模为n的最优矩阵连乘总是由规模小于n的最优矩阵连乘合成组成。
若规模为n,在作一次划分的情况下,则有n种可能(假设在第k-1到k矩阵中间作划分)
\[Posibility\ of\ \{A_1,A_2,A_3\dots A_n\}=
\begin {cases}
A_1(A_2A_3A_4\dots A_n)&k=1\tag 1\\
(A_1A_2)(A_3A_4\dots A_n)&k=2\\
(A_1A_2A_3)(A_4\dots A_n)&k=3\\
\dots\\
(A_1A_2A_3A_4\dots A_{n-1})A_n&k=n-1
\end {cases}
\]
其中规模小于n的矩阵链也可以用相同的方法求解,直到最小规模为1连乘次数为0。在求解子矩阵链相乘时只记录最优解,以此避免不必要的矩阵运算,我们找到了矩阵之间的递推关系了。
阶段、状态、状态转移方程、最优子结构
找到合理的划分方法后,我们可以套入动态规划的一般性方法论。
阶段
在这个问题中,阶段很明显地指向矩阵的规模大小。也就是说,规模大的矩阵链(下一阶段)是由规模较小的矩阵链组成(之前的阶段)的。
状态
显然这里的状态就是指,子矩阵\(A_i\)到子矩阵\(A_k\)的矩阵连乘链用\(i,k\)表示连乘开始矩阵和连乘结束矩阵。
状态转移方程
n表示问题规模即阶段,求解规模为N的矩阵要求解N个阶段的状态转移方程
\[state(i,i+n)=
\begin {cases}
0&i=i+n\\
min(state(i,i+n),state(i,k)+state(k,i+n)+r_i*c_k*c_{i+n})\Tiny(2\leqslant n\leqslant N,i\leqslant N-n,i\leqslant k\leqslant i+n)&其他
\end {cases}
\]
最优子结构
对于任意规模为n的最优矩阵连乘总是由规模小于n的最优矩阵连乘合成组成,直到最小规模为1连乘次数为0。即\(state(0,0)=0\)
测试样例
Sample Input | Sample Output |
---|---|
6 30 35 35 15 15 5 5 10 10 20 20 25 |
The Path: ((A1(A2A3))((A4A5)A6)) The Minimum: 15125 |
参考代码
点此展开代码
/*
6
30 35
35 15
15 5
5 10
10 20
20 25
*/
#include "iostream"
#include <cstring>
#include "iomanip"
const int N = 100;
using namespace std;
struct Matrix {
int r, c;
// int matrix[N][N];
} m[N];
void findMinMatrixChain(int p[][N+1], const int &l, const int &r) {
if (l == r) {
cout << 'A' << l+1;
return;
}
cout << "(";
findMinMatrixChain(p, l, p[l][r]);
findMinMatrixChain(p, p[l][r] + 1, r);
cout << ")";
}
int main() {
int dp[N][N];//第i到j个矩阵的最优解结构
for (auto &i: dp)
for (int &j: i) j = INT_MAX;//初始化
int n;
cin >> n;
int p[N + 1][N + 1] = {};//记录最优划分区间
for (int i = 0; i < n; i++) {
cin >> m[i].r >> m[i].c;//矩阵行列
/*for (int j = 0; j > m[i].r; j++)
for (int k = 0; k > m[i].c; k++) cin >> m[i].matrix[j][k];*/
}
for (int i = 0; i < n; i++) dp[i][i] = 0;//Optimal Substructure第i个到第i个矩阵之间没有矩阵
for (int i = 2; i <= n; i++) {//Stage阶段——问题规模(由小到大依次求解)[2,n]
for (int j = 0; j < n - i + 1; j++) {//规模为i的矩阵连乘j+i-1<=n-1
for (int k = j; k < j + i - 1; k++) {//State求解规模为2的不同划分的矩阵解,k表示划分的地方[j,j+i-2]
if (dp[j][k] == INT_MAX || dp[k + 1][j + i - 1] == INT_MAX) continue;//Decision决策
int dst = dp[j][k] + dp[k + 1][j + i - 1] + m[j].r * m[j + i - 1].c * m[k].c;
if (dst < dp[j][j + i - 1]) {
dp[j][j + i - 1] = dst;
p[j][j + i - 1] = k;
}
//Transition状态转移方程 dp[j][j + i - 1] = min(dp[j][j + i - 1],dst);
}
}
}
cout << "The Path:\n";
findMinMatrixChain(p, 0, n - 1);
//自底向上的
cout << "\nThe Minimum:\n" << dp[0][n - 1] << '\n';
/*for(int i=0;i<n;i++){
for(int j=0;j<n;j++)
cout<<setw(10)<<dp[i][j];
cout<<'\n';
}*/
return 0;
}
复杂度分析
时间复杂度
n个问题阶段,对于每个阶段有n-1个解每个解都有其子问题。设\(T(n)\)为求子问题的时间。
\[T(n)=
\begin {cases}
0&,n=1\\
\sum\limits_{i=1}^n(T(i)+T(n-i))&,n>1
\end {cases}
\]
求解递归式,该算法的时间复杂度大致为\(O(n^3)\)。
空间复杂度
对于规模为n的矩阵连乘,其存储解的空间的大小为\(\frac{n(n-1)}{2}\),其空间复杂度为\(O(n^3)\)。