1. 程式人生 > 實用技巧 >動態規劃之矩陣連乘問題

動態規劃之矩陣連乘問題

動態規劃之矩陣連乘問題


1. 問題描述

​ 給定\(n\)個矩陣\({A_1, A_2,\ldots,A_n}\),其中\(A_i\)\(A_{i+1}\)是可乘的\((i = 1, 2,\ldots, n - 1)\),矩陣\(A_i\)的維數為\(p_{i-1}*p_i, i=1, 2,\ldots,n\)。考察這\(n\)個矩陣的連乘積\(A_1A_2\ldots A_n\)。要求通過新增括號使得矩陣連乘時,數乘次數最少。例如連乘積\(A_1A_2A_3A_4\)可以有以下5中完全加括號方式:\((A_1(A_2(A_3A_4)))\)\((A_1((A_2A_3)A_4))\)\(((A_1A_2)(A_3A_4))\)

\(((A_1(A_2A_3))A_4))\)\((((A_1A_2)A_3)A_4)\)

2.問題分析

2.1 分析最優解的結構

​ 將矩陣連乘\({A_i, A_{i+1},\ldots,A_j}\)簡記為\(A[i:j]\)。為考察計算\(A[1:n]\)的計算次序,設這個計算次序在矩陣\(A_k(1\le k<n)\)\(A_{k+1}\)之間將矩陣鏈斷開,則其相應的完全加括號方式為\(((A_1,\ldots, A_k)(A_{k+1}, A_n))\)。以此次序,先計算\(A[1:k]\)\(A[k+1:n]\),再計算結果相乘,得到總得計算順序為:\(A[1:k]\)

的計算量加上\(A[k+1:n]\)的計算量,再加上\(A[1:k]\)\(A[k+1:n]\)相乘的計算量。

2.2建立遞推關係

​ 對於矩陣連乘積的最優計算次序問題,設計算\(A[i:j](1\le i\le j\le n)\),所需的最少數乘次數為\(m[i][j]\),則原問題的最優值為\(m[1][n]\)
​ 當\(i = j\)時,\(A[i:j]=A_i\)為單一矩陣,無須計算,因此\(m[i][i] = 0 (i = 1, 2,\ldots, n)\)
​ 當\(i < j\)時,利用最優子結構心知來計算\(m[i][j]\)。若\(A[i:j]\)的最優次序在 \(A_k(i\le k<j)\)

\(A_{k+1}\)之間斷開,則\(m[i][j] = m[i][k] + m[k+1][j] + p_{i-1}p_kp_j\)
​ 從而,\(m[i][j]\)可以遞迴定義為:

\[m[i][j]= \begin{cases} 0 &\text{i=j} \\ \underset{i \le k <j}{min}\{m[i][k] + m[k+1][j]+p_{i-1}p_kp_j\} &\text i<j\\ \end{cases} \]

2.3計算最優值

​ 動態規劃演算法MatrixChain中,輸入引數\(p_0,p_1,\ldots,p_n\)儲存於陣列\(p\)中。除了輸入最優值陣列\(m\),演算法還輸出記錄最優斷開位置的陣列\(S\)

void MatrixChain(int *p, int n, int **m, int **s) {
    // 求解規模為1的子問題,即一個矩陣相乘的數乘次數
    for (int i = 1;i <= n;i++)
        m[i][i] = 0;
    // 規模控制,控制規模從2到n個矩陣相乘的數乘次數
    for (int r = 2;r <= n;r++) {
        // 規模為r的子問題個數。i是對應的子問題的首矩陣的編號,j是以i開始的規模為r的子問題的結果矩陣編號
        for (int i = 1;i <= n - r + 1;i++) {
            int j = i + r - 1;
            // 求解在i出斷開時,矩陣數乘次數。s[i][j]記錄斷開位置
            m[i][j] = m[i + 1][j] + p[i - 1] * p [i] * p[j];
            s[i][j] = i;
            // 控制不同斷開方式下,最優值求解
            for (int k = i + 1;k < j;k++) {
                int t = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
                // 將新的最優解賦值
                if (m[i][j] > t) {
                    m[i][j] = t;
                    s[i][j] = k;
                }
            }
        }
    }
}

​ 例題分析:
​ 要計算矩陣連乘積\(A_1A_2A_3A_4A_5A_6\),其中各矩陣的維數分別如下表:

2.4 構造最優解

​ 演算法MatrixChain記錄了構造最優解所需的全部資訊。\(S[i][j]\)中的數表明,計算矩陣鏈\(A[i:j]\)的最佳方式是在矩陣\(A_k\)\(A_{k+1}\)之間斷開,即最優加括號方式為\((A[i:k])(A[k+1:j])\)。而\(A[1:s[1][n]]\)的最優加括號方式為\((A[1:s[1][n]])(A[s[1][n]+1:n])\)。算了,直接看程式碼吧......

void Traceback(int i, int j, int **s) {
    if (i == j)
        return ;
    Traceback(i, s[i][j], s);
    Traceback(s[i][j] + 1, j, s);
    cout << "Multiply A" << i << ", " << s[i][j];
    cout << " and A" << (s[i][j] + 1) << ", " << j << endl;
}