動態規划之多矩陣乘積


算法原理請參考《算法導論》,因為算法這東西千篇一律,關鍵還是實現和理解,這里只提幾個關鍵點,幫助大家理解。

1. 為什么需要動態規划?

比如矩陣A是p x q大小,矩陣B是q x r大小,很明顯,得到的矩陣C是p x r大小,其中花費的時間必定是p*q*r。這只是兩個矩陣,如果存在N個矩陣需要算其乘積呢?那么就需要用到動態規划了,比如A(p x q), B(q x r), C(r x l)這三個矩陣相乘。如果不規划,那么花費的時間是A*B=p*q*r,然后再乘以C,還需要額外花費p*r*l時間。但有可能B*C先乘,然后再乘以A,這樣花費的時間最少。以此推廣到N個矩陣相乘。這就是動態規划的原因!!!

2. 為了幫助大家理解《算法導論》中的算法,這里我給大家講解下數組p。比如A(a x b), B(b x c), C(c x d)這三個矩陣,那么p數組的內容是p[]={a,b,c,d},書上因為沒有采用C語言數組,所以下標從1開始,但是這里我給出下標從0開始的數組p,這樣矩陣A可以用p[0] x p[1] 表示,那么推廣到N個矩陣,矩陣A:i的維度是p[i] x p[i+1] (所以,書上給出的偽代碼需要做一些下標調整,包括算法)

下面是代碼部分

matric_free函數,內存回收

template <int p>
void matric_free(int **C) {
    for (int i = 0; i < p; i++)
        delete[] C[i];
    delete[] C;
}

matric_show函數,打印矩陣,默認值為-1時,表示未使用,則不打印。

template <int p, int q>
void matric_show(int **A) {
    for (int i = 0; i < p; i++) {
        for (int j = 0; j < q; j++)
            if (A[i][j] == -1)
                printf("%-04s", " ");
            else
                printf("%-04d ", A[i][j]);
        printf("\n");
    }
}

matric_multiply函數,這個是任意兩個矩陣相乘,從代碼也可以推出來,任意兩個矩陣相乘花費的時間是p*q*r

template <int p, int q, int r>
int **matric_multiply(int A[p][q], int B[q][r]) {
    int **C = new int *[p];
    for (int i = 0; i < r; i++)
        C[i] = new int[r];
    for (int i = 0; i<p; i++)
        for (int j = 0; j < r; j++) {
            C[i][j] = 0;
            for (int k = 0; k < q; k++)
                C[i][j] += A[i][k] * B[k][j];
        }
    return C;
}

matric_optimal函數,通過傳入數組p(構建原理,已經在上面的關鍵點中提到了),length參數代表數組p長度,實際上,數組p會多一個元素出來,所以真實構建的m和s是length-1,由於下標通通改用了C語言形式,所以代碼我做了調整

template <int length>
void matric_optimal(int *p, int ***m, int ***s) {
    int n = length - 1, q;
    if (n < 2) return;//至少兩個矩陣
    *m = new int *[n];
    *s = new int *[n];
    for (int i = 0; i < n; i++) {//生成m, s對應的二維數組
        (*m)[i] = new int[n];
        (*s)[i] = new int[n];
    }
    //初始化
    for (int i=0;i<n;i++)
        for (int j = 0; j < n; j++) 
            (*m)[i][j] = (*s)[i][j] = -1;//-1表示未使用
    for (int i = 0; i < n; i++)
        (*m)[i][i] = 0;
    for (int l = 1; l <= n; l++) //這里針對C語言數組,做了改變
        for (int i = 0; i < n - l + 1; i++)
        {
            int j = i + l - 1;
            for (int k = i; k <j; k++) {
                q = (*m)[i][k] + (*m)[k + 1][j] + p[i] * p[k+1] * p[j+1];//這里針對C語言數組做了改變,原因很簡單,因為數組下標不能取-1,所以需要修改
                if ((*m)[i][j]==-1 || q < (*m)[i][j]) {
                    (*m)[i][j] = q;
                    (*s)[i][j] = k;//保留k值,方便輸出
                }
            }
        }
}

另一個版本是遞歸版本,是recursive_matric_optimal函數。

int recursive_matric_optimal(int *p, int ***m, int ***s, int i, int j) {
    int q, n;
    if (i == j) return 0;
    if (*m == NULL && *s == NULL) {
        n = j - i + 1;
        if (n < 2) return -1;//至少兩個矩陣
        *m = new int *[n];
        *s = new int *[n];
        for (int i = 0; i < n; i++) {//生成m, s對應的二維數組
            (*m)[i] = new int[n];
            (*s)[i] = new int[n];
        }
        //初始化
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                (*m)[i][j] = (*s)[i][j] = -1;
    }
    for (int k = i; k < j; k++) {
        q = recursive_matric_optimal(p, m, s, i, k) + recursive_matric_optimal(p, m, s, k + 1, j) + p[i] * p[k + 1] * p[j + 1];
        if ((*m)[i][j] == -1 || q < (*m)[i][j]) {
            (*m)[i][j] = q;
            (*s)[i][j] = k;
        }
    }
    return (*m)[i][j];
}

matric_show_optimal函數,生成動態規划后的結果,直觀看得出哪個矩陣先乘,時間最短。

void matric_show_optimal(int **s, int i, int j) {
    if (i == j)
        printf("A:%d", i);
    else {
        printf("(");
        matric_show_optimal(s, i, s[i][j]);
        matric_show_optimal(s, s[i][j] + 1, j);
        printf(")");
    }
}

數據錄入

A0 30x35 
A1 35x15
A2 15x5 
A3 5x10 
A4 10x20
A5 20x25

轉換為數組p

int p[] = { 30,35,15,5,10,20,25 };//A(i)的維數表示p(i) x p(i+1)

結果圖

這個結果對應書上的結果圖(書上旋轉了),是一樣的

 

所有代碼均經過測試,結果正確!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM