动态规划解决矩阵连乘
问题描述:
给定
N
N
N个矩阵
{
A
1
,
A
2
,
A
3
…
…
A
n
}
\{A_1,A_2,A_3……A_n\}
{A1,A2,A3……An},其中
A
i
A_{i}
Ai 和
A
i
+
1
A_{i+1}
Ai+1 和可以相乘的,即:
A
i
A_i
Ai 的行数等于
A
i
+
1
A_{i+1}
Ai+1 的列数,如何。确定矩阵连乘的计算次序,使得安照此次序计算该矩阵连乘所需要的数乘次数最少。
两个矩阵相乘,它的数乘次数等于前一个矩阵的行数乘以后一个矩阵的列数,当这些矩阵相乘的次序不同时,它的数乘次数的有很大差异的。如下例题:
例:现有四个矩阵 A , B , C , D A,B,C,D A,B,C,D,他们的维数分别是: A = 50 ∗ 10 , B = 10 ∗ 40 , C = 40 ∗ 30 , D = 30 ∗ 5 A=50*10,B=10*40,C=40*30,D=30*5 A=50∗10,B=10∗40,C=40∗30,D=30∗5,则连乘 A , B , C , D A,B,C,D A,B,C,D共有五种相乘的次序,如:
- ( A ( ( B C ) D ) ) (A((BC)D)) (A((BC)D)),数乘次数为 16000
- ( ( A B ) ( C D ) ) ((AB)(CD)) ((AB)(CD)),数乘次数为 36000
- ( ( A ( B C ) ) D ) ((A(BC))D) ((A(BC))D),数乘次数为 34500
- ( A ( B ( C D ) ) ) (A(B(CD))) (A(B(CD))),数乘次数为 10500
- ( ( ( A B ) C ) D ) (((AB)C)D) (((AB)C)D),数乘次数为 87500
我们可以看到,连乘的次序不同,它的数乘次数大区别也是很大的,最少的 10500 次,最多的,87500 次,相差 8 倍之多。所以,寻找一个矩阵连乘的最优分隔方式就显得尤为重要。
解题思路:
这个问题可以使用枚举法进行解决,枚举每一个分隔方式的数乘次数,找到一个最小的即可,但是这样的算法的是时间复杂度就会很大,并不是一个合格的算法,为此,我们可以通过动态规划来解决该问题。
将矩阵连乘
A
i
∗
A
i
+
1
…
…
A
j
A_i*A_{i+1}……A_j
Ai∗Ai+1……Aj 简记为
A
[
i
:
j
]
A[i:j]
A[i:j]。
1. 😉设定一个二维数组
m
[
N
]
[
N
]
m[N][N]
m[N][N],表示当前矩阵的连乘次数。比如:
m
[
i
]
[
j
]
m[i][j]
m[i][j] 表示矩阵从
A
i
A_i
Ai 连乘到
A
j
A_j
Aj 的数乘值。
2. 😉再设定一个二维数组
S
[
N
]
[
N
]
S[N][N]
S[N][N],表示当前矩阵连乘时的具有最优解的分隔位置。比如
s
[
i
]
[
j
]
s[i][j]
s[i][j] 表示从矩阵从
A
i
A_i
Ai 连乘到
A
j
A_j
Aj 的数乘值最小时的分隔位置,可以理解为加括号的位置。
给出递归关系:
m
[
i
]
[
j
]
=
{
0
,
i==j
m
i
n
{
m
[
i
]
[
k
]
+
m
[
k
+
1
]
[
j
]
+
A
i
−
1
∗
A
k
∗
A
j
}
,
i<j
m[i][j] = \begin{cases} 0,& \text{i==j} \\ min\{m[i][k]+m[k+1][j]+A_{i-1}*A_k*A_j\}, & \text{i<j} \end{cases}
m[i][j]={0,min{m[i][k]+m[k+1][j]+Ai−1∗Ak∗Aj},i==ji<j
核心算法详解:
- 先将m数组的对角线初始化为0,因为 m [ i ] [ i ] m[i][i] m[i][i] 表示只有一个矩阵,那它的数乘次数当然是0了。
- 依次计算从第二个矩阵连乘到最后一个矩阵的最优解情况。
- 依次在 r − 1 r-1 r−1 个分隔位置中依次检测最优分隔点。
- 对于每个分隔点,变换一次分隔位置,再进行逐一测试,如果有更优的分隔点,就替换掉当前的分隔点。
矩阵连乘实例:
连乘矩阵 :
{
A
1
,
A
2
,
A
3
,
A
4
,
A
5
,
A
6
}
\{A_1,A_2,A_3,A_4,A_5,A_6\}
{A1,A2,A3,A4,A5,A6}
m数组:
s数组:
#include<iostream>
using namespace std;
const int N = 100;
int A[N];//矩阵规模
int m[N][N];//最优解
int s[N][N];
void MatrixChain(int n)
{
int r, i, j, k;
for (i = 0; i <= n; i++)//初始化对角线
{
m[i][i] = 0;
}
for (r = 2; r <= n; r++)//r个矩阵连乘
{
for (i = 1; i <= n - r + 1; i++)//r个矩阵的r-1个空隙中依次测试最优点
{
j = i + r - 1;
m[i][j] = m[i][i]+m[i + 1][j] + A[i - 1] * A[i] * A[j];
s[i][j] = i;
for (k = i + 1; k < j; k++)//变换分隔位置,逐一测试
{
int t = m[i][k] + m[k + 1][j] + A[i - 1] * A[k] * A[j];
if (t < m[i][j])//如果变换后的位置更优,则替换原来的分隔方法。
{
m[i][j] = t;
s[i][j] = k;
}
}
}
}
}
void print(int i, int j)
{
if (i == j)
{
cout << "A[" << i << "]";
return;
}
cout << "(";
print(i, s[i][j]);
print(s[i][j] + 1, j);//递归1到s[1][j]
cout << ")";
}
int main()
{
int n;//n个矩阵
cin >> n;
int i, j;
for (i = 0; i <= n; i++)
{
cin >> A[i];
}
MatrixChain(n);
cout << "最佳添加括号的方式为:";
print(1, n);
cout << "\n最小计算量的值为:" << m[1][n] << endl;
return 0;
}