0815------算法筆記----------矩陣連乘問題

1.矩陣連乘問題的定義ios

  1.1 給定 n 個矩陣的連乘積 A1A2...An,由於矩陣乘法知足結合律,因此計算矩陣的連乘積能夠有不一樣的計算次序(這個次序的組合數知足卡特蘭數),採用不一樣的計算次序計算的數乘次數也不相同。例如,A1A2A3,這三個矩陣的維數分別是10*100,100*5,和5*50,若先計算A1A2,總的計算次數爲10*100*5+10*5*50 = 7500,然而先計算A2A3,總的計算次數爲 100*5*50 + 10*100*50 = 75000,可見計算數乘次數相差10倍。這裏咱們用加括號的方式來表示矩陣的計算次序。每一種徹底加括號方式對應一種矩陣的計算次序;
算法

  1.2 什麼是徹底加括號的矩陣連乘積?徹底加括號的矩陣連乘積能夠遞歸的定義爲(見《計算機算法分析與設計》):數組

    a)單個矩陣是徹底加括號的;spa

    b)矩陣連乘積 A 是徹底加括號的,則 A 能夠表示爲 2 個徹底加括號的矩陣連乘積 B 和 C 的乘積並加括號,即 A =(BC)。設計

2.矩陣連乘問題的求解blog

  2.1 設矩陣連乘積A1A2...An,Ai的維數分別爲 p[i-1]*p[i],m[i][j]爲AiAi+1...Aj的最少數乘次數,它知足下述遞歸關係:遞歸

    a)m[i][j] = 0  , i == j;ci

    b)m[i][j] = min{m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j]},  i<= k < j;string

  2.2 根據上述遞歸式能夠寫出本問題的遞歸方法,這裏爲了求出最終的計算次序,須要用一個二維數組 s 保存每次斷開的位置。io

#include <iostream>
#include <string>
#include <vector>
#include <string.h>
#define MAX 100
using namespace std;

int  MatrixChain(int i, int j, int *p, int (*m)[MAX], int (*s)[MAX]);
void TraceBack(int i, int j, int (*s)[MAX]);


int main(int argc, const char *argv[])
{
    int p[MAX], m[MAX][MAX], s[MAX][MAX];
    int n;
    cin >> n;                   //矩陣的個數
    int i ;
    for(i = 0; i <= n; i++){    //相乘矩陣鏈的行列數
        cin >> p[i];
    }
    memset(m, -1, sizeof m);

    MatrixChain(1, n, p, m, s);
    TraceBack(1, n, s);

    cout << endl;
    return 0;
}

int  MatrixChain(int i, int j, int *p, int (*m)[MAX], int (*s)[MAX]){
    if(m[i][j] != -1)
        return m[i][j];

    if(i == j)
       return 0;
    else{
        int k, min = 1000000;
        for(k = i; k < j; k++){
                m[i][k] = MatrixChain(i, k, p, m, s);
                m[k+1][j] = MatrixChain(k+1, j, p,  m, s);

                int tmp = m[i][k] + m[k+1][j] + p[i-1] * p[k] * p[j];
                if(tmp  < min){
                    min = tmp;
                    s[i][j] = k;

                }
        }
        return min;
    }
}

void TraceBack(int i, int j, int (*s)[MAX]){
    if(i == j){
        cout << "A" << i;
        return;
    }

    cout << "(";
    TraceBack(i, s[i][j], s);
    cout << "*";
    TraceBack(s[i][j] + 1, j, s);
    cout << ")";
}

  2.3 矩陣連乘的非遞歸方法其實就是一個填充表格的過程,以6個矩陣的乘積爲例,矩陣的維度分別爲p[] = {30, 35, 15, 5, 10, 20, 25},以下圖所示,這裏先將 m[i][i] 設置爲 0, 以後再以正對角線方向根據上述遞歸式計算m[1][2],m[2][3]...m[5][6]等等,例如m[2][3] = m[2][2] + m[3][3] + p[1] * p[2] *p[3] = 0 + 0 + 30 * 35 * 15 = 15750,這裏只能以2分割, 再例如計算 m[2][5] ,此時要分別計算出 m[2][2] + m[3][5] + p[1] * p[2] * p[5], m[2][3] + m[4][5] + p[1] * p[3] * p[5], 以及 m[2][4] + m[5][5] + p[1] * p[4] * p[5],而後求其最小值即爲 m[2][5]。填充後的表格和源程序以下:

   1  2 3 4 5 6
1  0   15750 7875 9375 11875 15125
2   0 2625 4375 7125 10500
3     0 750 2500 5375
4       0 1000 3500
5         0 5000
6           0

 

 

    

 

 

 

 

 

#include <iostream>
#include <string>
#include <vector>
#define MAX 100
using namespace std;

void MatrixChain(int *p, int n, int (*m)[MAX], int (*s)[MAX]);
void TraceBack(int i, int j, int (*s)[MAX]);

int main(int argc, const char *argv[])
{
    int p[MAX], m[MAX][MAX], s[MAX][MAX];
    int n;
    cin >> n;                   //矩陣的個數
    int i ;
    for(i = 0; i <= n; i++){    //相乘矩陣鏈的行列數
        cin >> p[i];
    }

    MatrixChain(p, n, m, s);
    TraceBack(1, n, s);

    cout << endl;
    return 0;
}

void MatrixChain(int *p, int n, int (*m)[MAX], int (*s)[MAX]){
    int i;
    for(i = 1; i <= n; i++)
        m[i][i] = 0;

    int r;                                            //外層循環的次數
    for(r = 2; r <= n; r++){
        for(i = 1; i<= n - r + 1; i++){               //求解m[i][j]
            int j = i + r -1;

            int min = m[i][i] + m[i+1][j] + p[i-1] * p[i] * p[j];
            s[i][j] = i;

            int k;
            for(k = i + 1; k < j; k++){
                int tmp = m[i][k] + m[k+1][j] + p[i-1]*p[k]*p[j];
                if(tmp < min){
                    min = tmp;
                    s[i][j] = k;
                }
            }
            m[i][j] = min;
        }
    }
}

void TraceBack(int i, int j, int (*s)[MAX]){
    if(i == j){
        cout << "A" << i;
        return;
    }

    cout << "(";
    TraceBack(i, s[i][j], s);
    cout << "*";
    TraceBack(s[i][j] + 1, j, s);
    cout << ")";
}

  

3.小結

  3.1 和最長公共子序列問題相同,當咱們使用動態規劃來求解某一個問題時,這個問題通常都具備兩個明顯特徵,一是最優子結構性質,即問題的最優解包含了子問題的最優解,二是子問題重疊,即在遞歸求解時,有些子問題被重複計算。

  3.2 針對上述子問題重疊的性質,咱們使用了備忘錄方法,即在遞歸的過程當中將每一個子問題的結果保存在數組中,若是下次須要直接從數組中取出,從而避免了重複計算。

相關文章
相關標籤/搜索