0%

Matrix Chain Multiplication

矩陣鏈乘積演算法

Matrix Chain Multiplication or Matrix Chain Ordering Problem

純量乘法數

假設
M是一個xy的矩陣
N是一個y
z的矩陣
MN需要xyz次純量乘法計算(xz個元素,每個元素執行y次乘法)

矩陣乘法先後次序會影響次數

定義 矩陣鏈乘積問題

一個含n個矩陣的矩陣鏈乘積: M1M2…Mn
找出一種完全括號(Fully Parenthesized)方式
使用最少純量乘法求出乘積

完全括號:單一矩陣或兩個完全括號矩陣鏈乘積相乘並加括號
EX: X、Y、(XY)

窮舉失敗: 窮舉所有不同的完全括號方式

長度為n的矩陣鏈乘積不同的完全括號方式的總數P(n):

P(n)為卡塔蘭數(Catalan number)=Cn-1 =O(2n)
因需要太多計算所以無法暴力解
=> 使用動態規劃演算法

動態規劃: 遞迴關係

動態規劃: 建構表格

長度為n的矩陣鏈乘積,
表格m[1~n, 1~n],
m[i,j]=Mi~Mj所需的最小純量相乘數
目標求得m[1,n]=M1~Mn的相乘數

輔助表格s[1~n, 1~n] 記錄分割位置k
s[i,j]紀錄哪一個分割位置k造就了m[i,j]的最小純量相乘數

動態規劃: 填表


邊界條件為i=j

11,22,…,nn, //d=j-i=0 都填0
12,23,…,(n-1)n, // d=j-i=1
13,24,(n-2)n, // d=j-i=2
…,
1n // d=j-i=n-1
的方式填表
(由底部而上的方式)

演算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Algorithm 矩陣鏈乘積演算法
Input: 矩陣鏈乘積 e(1)~e(n)
Output: 所需最小乘法數 m[1,n]
for i = 1 to n
m[i,i] = 0
for d = 1 to n-1 do // 1~n-1, d=j-i, 每一個橫排
for i = 1 to n-d // 1~n-d // 橫排中的格子
j = i+d // j-i=d
m[i,j] = 無限大
for k = i to j-1 // i~j-1, 每一格子可能的結果, Mi~Mj可能的分割, 看以哪一個k分割最好
tmp = m[i,k] + m[k+1,j] + e(i-1)e(k)e(j)
if tmp < m[i,j] // 找到更小的方法
m[i,j] = tmp
s[i,j] = k
return m and s

時間複雜度

三層for每層都是O(n)
=> O(n3)

印出最佳完全括號矩陣鏈乘積演算法

1
2
3
4
5
6
7
8
Algorithm PRINT_OPTIMAL_PARENS(s,i,j)
if i=j
print "M" + i
else
print "("
PRINT_OPTIMAL_PARENS(s,i,s[i,j]) // 印出Mi~Mk
PRINT_OPTIMAL_PARENS(s,s[i,j]+1,j) // 印出M(k+1)~Mj
print ")"

ex: (M1(M2M3))

實際範例


C++ 參考

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
// 矩陣鏈乘積演算法 MartixChainOrder
#include <iostream>
using namespace std;
void PRINT_OPTIMAL_PARENS(int** s, int i, int j) {
if (i == j)
{
cout << "A" << i;
}
else
{
cout << "(";
PRINT_OPTIMAL_PARENS(s, i, s[i][j]);
PRINT_OPTIMAL_PARENS(s, s[i][j] + 1, j);
cout << ")";
}
}
void MatrixChainOrder(int* e, int n) {
int **m;
m = new int*[n + 1]; // 0~n, use 1~n
for (int i = 0; i <= n; ++i) {
m[i] = new int[n + 1];
}
// 給印用的
int **OPTIMAL_PARENS;
OPTIMAL_PARENS = new int*[n + 1];
for (int i = 0; i <= n; ++i) {
OPTIMAL_PARENS[i] = new int[n + 1];
}
// 開始演算法
// 1~n, 對角線都0
for (int i = 1; i <= n; i++)
{
m[i][i] = 0;
}
// d=1~n-1, 每一個橫排
for (int d = 1; d <= n-1; d++)
{
// 1~n-d, 橫排中的格子
for (int i = 1; i <= n-d; i++)
{
int j = i + d;
m[i][j] = 2147483647;
// i~j-1, 計算每個格子的最小值
for (int k = i; k <= j-1; k++)
{
int tmp = m[i][k] + m[k + 1][j] + e[i-1] * e[k] * e[j];
if (tmp < m[i][j])
{
m[i][j] = tmp;
OPTIMAL_PARENS[i][j] = k;
}
}
}
}
PRINT_OPTIMAL_PARENS(OPTIMAL_PARENS, 1, n);
cout << endl;
cout << m[1][n]; // ans
}
int main()
{
int num = 6; // 幾個矩陣
//cin >> num;
int *row_col = new int[num + 1]; // 幾個矩陣+1個數字要輸入
int test_data[7] = { 30, 35, 15, 5, 10, 20, 25 };
for (int i = 0; i < num + 1; i++)
{
row_col[i] = test_data[i];
//cin >> row_col[i];
}
MatrixChainOrder(row_col, num);
system("pause");
return 0;
}