【問題描述】
給定 個矩陣 ,其中, 和 是可乘的。用加括號的方法表示矩陣連乘的次序,不同的計算次序計算量(乘法次數)是不同的,找出一種加括號的方法,使得矩陣連乘的計算量最小。
例如:
是 的矩陣; 是 的矩陣; 是 的矩陣。
那麼有兩種加括號的方法:
- ;
第 種加括號方法運算量:
第 種加括號方法運算量:
可以看出,不同的加括號辦法,矩陣乘法的運算次數可能有巨大的差別!
【問題分析】
1.前置知識:
(1)什麼是矩陣可乘?
如果兩個矩陣,第 個矩陣的列等於第 個矩陣的行時,那麼這兩個矩陣是可乘的。
(2)矩陣相乘後的結果是什麼?
兩個矩陣相乘的結果矩陣,其行、列分別等於第 個矩陣的行、第 個矩陣的列。
多個矩陣相乘的結果矩陣,其行、列分別等於第 個矩陣的行、最後 個矩陣的列。而且無論矩陣的計算次序如何都不影響它們的結果矩陣。
(3)兩個矩陣相乘需要多少次乘法?
兩個矩陣 、 相乘執行乘法運算的次數爲 。
因此,、 相乘執行乘法運算的次數爲 。
(4)窮舉
如果窮舉所有的加括號方法,那麼加括號的所有方案是一個卡特蘭數序列,其算法時間複雜度爲 ,是指數階,因此窮舉的辦法是很糟的。
2.動態規劃
(1)分析最優解的結構特徵
假設我們已經知道了在第 個位置加括號會得到最優解,那麼原問題就變成了兩個子問題:,。
原問題的最優解是否包含子問題的最優解呢?
假設 的乘法次數是 , 的乘法次數是 , 的乘法次數是 , 和 的結果矩陣相乘的乘法次數是 ,那麼 。
而無論兩個子問題 、 的計算次序如何,都不影響它們結果矩陣,兩個結果矩陣相乘的乘法次數 不變。
因此,我們只需要證明:如果 是最優的,則 和 一定是最優的(即原問題的最優解包含子問題的最優解)。
反證法:如果 不是最優的, 存在一個最優解 ,,那麼,,所以 不是最優的,這與假設 是最優的矛盾,因此,如果 是最優的,則 一定是最優的。同理,可證 也是最優的。因此,如果 是最優的,則 和 一定是最優的。
因此,矩陣連乘問題具有最優子結構性質。
(2)建立最優值遞歸式
用二維數組 表示 矩陣連乘的最優值,那麼兩個子問題 、 對應的最優值分別是 、。剩下的只需要考查 和 的結果矩陣相乘的乘法次數了。
設矩陣 的行數爲 ,列數爲 ,,且矩陣是可乘的,即相鄰矩陣前一個矩陣的列等於下一個矩陣的行 。 的結果是一個 矩陣, 的結果是一個 矩陣,,兩個結果矩陣相乘的乘法次數是 。
用一維數組 來記錄矩陣的行和列,第 個矩陣的行數存儲在數組的第 位置,列數存儲在數組的第 位置,那麼 對應的數組元素相乘爲 ,遞歸式爲:
(3)自底向上計算並記錄最優值
初始化:採用一維數組 來記錄矩陣的行和列,,,其中
先求 個矩陣相乘的最優值,再求 個矩陣相乘的最優值,直到 個矩陣連乘的最優值。
- 按照遞歸關係式計算 個矩陣 、 相乘時的最優值,,並將其存入 ,同時將最優策略記入 ,。
- 按照遞歸關係式計算 個矩陣相乘 、、 相乘時的最優值,,並將其存入 ,同時將最優策略記入 ,。
- 以此類推,直到求出 個矩陣相乘的最優值 。
(4)構造最優解
上面得到的最優值只是矩陣連乘的最小的乘法次數,並不知道加括號的次序,需要從記錄表中還原加括號次序,構造出最優解。
用二維數組 來存放各個子問題的最優決策(加括號的位置)。
根據最優決策信息數組 遞歸構造最優解。 表示 最優解的加括號位置,即 ,我們再遞歸構造兩個子問題 、 的最優解加括號位置,一直遞歸到子問題只包含一個矩陣爲止。
【算法實現】
#include <cstdio>
#include <cstring>
#include <iostream>
using namespace std;
const int msize = 100;
int p[msize];
int m[msize][msize];
int s[msize][msize];
int n;
void matrixchain()
{
memset(m, 0, sizeof(m));
memset(s, 0, sizeof(s));
for(int len = 2; len <= n; len++) // 不同規模的子問題
{
for(int l = 1; l + len - 1 <= n; l++)
{
int r = l + len - 1;
m[l][r] = m[l+1][r] + p[l-1] * p[l] * p[r]; // 決策爲k=l的乘法次數
s[l][r] = l; // 子問題的最優策略是l;
for(int k = l + 1; k < r; k++) // 對從l到r的所有決策,求最優值,記錄最優策略
{
int t = m[l][k] + m[k+1][r] + p[l-1] * p[k] * p[r];
if(t < m[l][r])
{
m[l][r] = t;
s[l][r] = 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);
cout << ")";
}
int main()
{
cout << "請輸入矩陣的個數 n:";
cin >> n;
cout << "請依次輸入每個矩陣的行數和最後一個矩陣的列數:";
for(int i = 0; i <= n; i++) cin >> p[i];
matrixchain();
print(1, n);
cout << endl;
/*
for(int i = 1; i <= n; i++) // 用於測試
{
for(int j = i; j <= n; j++) cout << m[i][j] << " ";
cout << endl;
}
for(int i = 1; i <= n; i++)
{
for(int j = i; j <= n; j++) cout << s[i][j] << " ";
cout << endl;
}
cout << endl;
*/
cout << "最小計算量的值爲 " << m[1][n] << endl;
}
樣例圖解如下:
初始化:
計算 個矩陣相乘的最優值
計算 個矩陣相乘的最優值
計算 個矩陣相乘的最優值
計算 個矩陣相乘的最優值
構造最優解
【複雜度分析】
(1)時間複雜度:由程序可以得出,語句 ,它是算法的基本語句,在 層 for 循環中嵌套。最壞情況下,該語句的執行次數爲 ,print() 函數算法的時間主要取決於遞歸,時間複雜度爲 。故該程序的時間複雜度爲 。
(2)空間複雜度:該程序的輸入數據的數組爲 ,輔助變量爲 、、、、、、,空間複雜度取決於輔助空間,因此空間複雜度爲 。