矩陣連乘

【問題描述】

給定 nn 個矩陣 {A1,A2,A3...An}\{A_1, A_2, A_3,... A_n\},其中,AiA_iAi+1 (i=1,2..n1)A_{i+1}\ (i=1, 2,.. n-1) 是可乘的。用加括號的方法表示矩陣連乘的次序,不同的計算次序計算量(乘法次數)是不同的,找出一種加括號的方法,使得矩陣連乘的計算量最小。

例如:

A1A_1M5×10M_{5×10} 的矩陣;A2A_2M10×100M_{10×100} 的矩陣;A3A_3M100×2M_{100×2} 的矩陣。

那麼有兩種加括號的方法:

  1. (A1A2)A3(A_1A_2)A_3
  2. A1(A2A3)A_1(A_2A_3)

11 種加括號方法運算量:5×10×100+5×100×2=60005× 10× 100+5× 100× 2=6000

22 種加括號方法運算量:10×100×2+5×10×2=210010× 100× 2+5× 10× 2=2100

可以看出,不同的加括號辦法,矩陣乘法的運算次數可能有巨大的差別!

【問題分析】

1.前置知識:

(1)什麼是矩陣可乘?

如果兩個矩陣,第 11 個矩陣的列等於第 22 個矩陣的行時,那麼這兩個矩陣是可乘的。

(2)矩陣相乘後的結果是什麼?

兩個矩陣相乘的結果矩陣,其行、列分別等於第 11 個矩陣的行、第 22 個矩陣的列。

多個矩陣相乘的結果矩陣,其行、列分別等於第 11 個矩陣的行、最後 11 個矩陣的列。而且無論矩陣的計算次序如何都不影響它們的結果矩陣。

(3)兩個矩陣相乘需要多少次乘法?

兩個矩陣 A3×2A_{3×2}A2×4A_{2×4} 相乘執行乘法運算的次數爲 3×2×43×2×4

因此,Am×nA_{m×n}An×kA_{n×k} 相乘執行乘法運算的次數爲 mnkm*n*k

(4)窮舉

如果窮舉所有的加括號方法,那麼加括號的所有方案是一個卡特蘭數序列,其算法時間複雜度爲 2n2^n,是指數階,因此窮舉的辦法是很糟的。

2.動態規劃

(1)分析最優解的結構特徵

假設我們已經知道了在第 kk 個位置加括號會得到最優解,那麼原問題就變成了兩個子問題:(AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j})

原問題的最優解是否包含子問題的最優解呢?

假設 AiAi+1...AjA_iA_{i+1}...A_{j} 的乘法次數是 cc(AiAi+1...Ak)(A_iA_{i+1}...A_{k}) 的乘法次數是 aa(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的乘法次數是 bb(AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的結果矩陣相乘的乘法次數是 dd,那麼 c=a+b+dc=a+b+d

而無論兩個子問題 (AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的計算次序如何,都不影響它們結果矩陣,兩個結果矩陣相乘的乘法次數 dd 不變。

因此,我們只需要證明:如果 cc 是最優的,則 aabb 一定是最優的(即原問題的最優解包含子問題的最優解)。

反證法:如果 aa 不是最優的,(AiAi+1...Ak)(A_iA_{i+1}...A_{k}) 存在一個最優解 aa'a<aa'<a,那麼,a+b+d<ca'+b+d<c,所以 cc 不是最優的,這與假設 cc 是最優的矛盾,因此,如果 cc 是最優的,則 aa 一定是最優的。同理,可證 bb 也是最優的。因此,如果 cc 是最優的,則 aabb 一定是最優的。

因此,矩陣連乘問題具有最優子結構性質。

(2)建立最優值遞歸式

用二維數組 m[i][j]m[i][j] 表示 AiAi+1...AjA_iA_{i+1}...A_{j} 矩陣連乘的最優值,那麼兩個子問題 (AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 對應的最優值分別是 m[i[k]m[i[k]m[k+1][j]m[k+1][j]。剩下的只需要考查 (AiAi+1...Ak)(A_iA_{i+1}...A_{k})(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的結果矩陣相乘的乘法次數了。

設矩陣 AmA_m 的行數爲 pmp_m,列數爲 qmq_mm=i,i+1,...jm=i, i+1,...j,且矩陣是可乘的,即相鄰矩陣前一個矩陣的列等於下一個矩陣的行 (qm=pm+1)(q_m= p_{m+1})(AiAi+1...Ak)(A_iA_{i+1}...A_{k}) 的結果是一個 pi×qkp_i×q_k 矩陣,(Ak+1Ak+2...Aj)(A_{k+1}A_{k+2}...A_{j}) 的結果是一個 pk+1×qjp_{k+1}×q_j 矩陣,qk=pk+1q_k= p_{k+1},兩個結果矩陣相乘的乘法次數是 pi×pk+1qjp_i×p_{k+1}*q_j

用一維數組 p[ ]p[\ ] 來記錄矩陣的行和列,第 ii 個矩陣的行數存儲在數組的第 i1i-1 位置,列數存儲在數組的第 ii 位置,那麼 pipk+1qjp_i*p_{k+1}*q_j 對應的數組元素相乘爲 p[i1]p[k]p[j]p[i-1]*p[k]* p[j],遞歸式爲:

m[i][j]={0,i=j,min{m[i][k]+m[k+1][j]+p[i1]p[k]p[j]},i<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<j \end{cases}

(3)自底向上計算並記錄最優值

初始化:採用一維數組 p[ ]p[\ ] 來記錄矩陣的行和列,m[i][i]=0m[i][i]=0s[i][i]=0s[i][i]=0,其中 i=1,2,3,...ni=1,2,3, ... n

先求 22 個矩陣相乘的最優值,再求 33 個矩陣相乘的最優值,直到 nn 個矩陣連乘的最優值。

  • 按照遞歸關係式計算 22 個矩陣 AiA_iAi+1A_{i+1} 相乘時的最優值,j=i+1j=i+1,並將其存入 m[i][j]m[i][j],同時將最優策略記入 s[i][j]s[i][j]i=1,2,3,...n1i=1, 2, 3,...n-1
  • 按照遞歸關係式計算 33 個矩陣相乘 AiA_iAi+1A_{i+1}Ai+2A_{i+2} 相乘時的最優值,j=i+2j=i+2,並將其存入 m[i][j]m[i][j],同時將最優策略記入 s[i][j]s[i][j]i=1,2,3,...n2i=1, 2, 3, ... n-2
  • 以此類推,直到求出 nn 個矩陣相乘的最優值 m[1][n]m[1][n]

(4)構造最優解

上面得到的最優值只是矩陣連乘的最小的乘法次數,並不知道加括號的次序,需要從記錄表中還原加括號次序,構造出最優解。

用二維數組 s[ ][ ]s[\ ][\ ] 來存放各個子問題的最優決策(加括號的位置)。

根據最優決策信息數組 s[ ][ ]s[\ ][\ ] 遞歸構造最優解。s[1][n]s[1][n] 表示 A1A2...AnA_1A_2...A_n 最優解的加括號位置,即 (A1A2...As[1][n])(As[1][n]+1...An)(A_1A_2...A_{s[1][n]})(A_{s[1][n]+1}...A_n),我們再遞歸構造兩個子問題 (A1A2...As[1][n])(A_1A_2...A_{s[1][n]})(As[1][n]+1...An)(A_{s[1][n]+1}...A_n) 的最優解加括號位置,一直遞歸到子問題只包含一個矩陣爲止。

【算法實現】

#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;
}

樣例圖解如下:

初始化:

計算 22 個矩陣相乘的最優值

計算 33 個矩陣相乘的最優值

計算 44 個矩陣相乘的最優值

計算 55 個矩陣相乘的最優值

構造最優解

【複雜度分析】

(1)時間複雜度:由程序可以得出,語句 t=m[l][k]+m[k+1][r]+p[l1]p[k]p[r]t= m[l][k] + m[k+1][r] +p[l-1]*p[k]*p[r],它是算法的基本語句,在 33 層 for 循環中嵌套。最壞情況下,該語句的執行次數爲 O(n3)O(n^3),print() 函數算法的時間主要取決於遞歸,時間複雜度爲 O(n)O(n)。故該程序的時間複雜度爲 O(n3)O(n^3)

(2)空間複雜度:該程序的輸入數據的數組爲 p[ ]p[\ ],輔助變量爲 iijjrrttkkm[ ][ ]m[\ ][\ ]s[ ][ ]s[\ ][\ ],空間複雜度取決於輔助空間,因此空間複雜度爲 O(n2)O(n^2)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章