動態規劃---矩陣鏈連乘

問題描述

給定若干個矩陣,尋找最優的相乘次序,使得乘法運算的次數最少,並輸出對應的最少運算次數。好比現有三個矩陣ABC,維數分別爲\(A:2\times10\)\(B:10\times2\)\(C:2\times10\) 。雖然(AB)C=A(BC) 結果是相等的,即與相乘次序沒有關係。可是(AB)C乘法運算的次數爲\(2\times10\times2+2\times2\times10=80\),而A(BC)爲\(10\times2\times10+2\times10\times10=400\),顯然(AB)C的運算次數更少,即效率更高。ios

思路

求解的關鍵在於如何將問題分解爲若干子問題。咱們想象在各個矩陣之間能夠放上隔板,那麼只要先分別求解左和右的最少乘法次數,再將隔板左右的兩部分相乘,就能夠獲得當前分隔方法的最優解,最後經過比較各類分隔方法,就能獲得當前長度的最優解,以下圖中四種分隔中咱們取最少的次數,即爲長度5(5個連續矩陣)的最優解。因爲分隔後的長度一定小於當前長度,如此處的子問題長度一定小於5,而子問題已經在以前的迭代過程求得,無需重複計算。經過下一部分的迭代過程展現能有更直觀的理解。算法

迭代過程

給定5個矩陣: ABCDE ,維數分別爲\(A:30\times35\) , \(B:35\times15\), \(C:15\times5\) , \(D:5\times10\), \(E:10\times20\) 。從矩陣鏈長度爲1開始求解,而後求解長度爲2的,最終到達5。dp[]數組(詳見算法實現)的更新以下圖所示。

爲了更直觀的理解,下圖和上面是等效的。(符號說明:m[a,b]表明從序號爲a到序號爲b的矩陣鏈所需的最少乘法次數,特別地,m[a,a]表明a號矩陣自己,很明顯m[a,a]=0。)
數組

算法實現

arr[]數組用於記錄矩陣鏈信息,其中n號矩陣對應的維數是arr[n-1]*arr[n]。動態規劃的核心算法利用3個for循環,最外層控制矩陣鏈長度,下一層控制起始點,再下一層控制隔板的位置。最後左右合併的時候注意下標的選擇,以下圖所示。
es5

#include <iostream>
#include <limits>
#include <cstring>
using namespace std;
int dp[51][51]; //從1開始,不使用0

// output: Minimum number of multiplications is 11875


/*功能:尋找矩陣鏈最優的運算次數
返回值:相對應的運算次數 */
int MinMatrixMul(int arr[], int num){
    int start, mid, end;
    int times; //記錄當前的最優解,即最少的運算次數

    //dp[i][i]=0 已經在初始化的時候完成
    //dp[i][j] 表明i號到j號矩陣鏈的運算次數
    for (int length = 2; length < num; ++length) {

        for (start = 1; start <= num-length; ++start) {
            end = start + length - 1;
            dp[start][end] = INT_MAX;

            for (mid = start; mid < end; ++mid) {
                times = dp[start][mid] + dp[mid+1][end] + arr[start-1]*arr[mid]*arr[end];

                //若是找到當前長度下更優的解,馬上更新dp數組
                if (times < dp[start][end]) {
                    dp[start][end] = times;
                }
            }
        }
    }
    return dp[1][num-1];
}

int main() {
    int num;
    int arr[] = {30, 35, 15, 5, 10, 20};
    memset(dp, 0, sizeof(dp));
    num = sizeof(arr)/ sizeof(int);

    cout << "Minimum number of multiplications is "<< MinMatrixMul(arr, num) << endl;

}

拓展:嘗試寫出計算順序,即打印括號

思路:增長bracket數組,bracket[start][end]記錄從start到end當中的mid,即加括號的地方。若start==end說明只有一個矩陣,直接輸出。令name(使用引用)從'A'開始,一旦輸出一個,便執行++name。spa

#include <iostream>
#include <limits>
#include <cstring>
using namespace std;
int dp[51][51]; //從1開始,不使用0
int bracket[51][51]; //記錄括號添加的位置

/*output:
 * Minimum number of multiplications is 11875
 * Order: ((A(BC))(DE))
 * */

/*功能:打印括號
 *返回值:無 */
void printBrackets(int start, int end, char &name){
    if (start == end) {
        cout << name;
        ++name;
        return;
    }
    cout << "(";
    printBrackets(start, bracket[start][end], name);
    printBrackets(bracket[start][end]+1, end, name);
    cout << ")";
}


/*功能:尋找矩陣鏈最優的運算次數
 *返回值:相對應的運算次數 */
int MinMatrixMul(int arr[], int num){
    int start, mid, end;
    int times; //記錄當前的最優解,即最少的運算次數

    //dp[i][i]=0 已經在初始化的時候完成
    //dp[i][j] 表明i號到j號矩陣鏈的運算次數
    for (int length = 2; length < num; ++length) {

        for (start = 1; start <= num-length; ++start) {
            end = start + length - 1;
            dp[start][end] = INT_MAX;

            for (mid = start; mid < end; ++mid) {
                times = dp[start][mid] + dp[mid+1][end] + arr[start-1]*arr[mid]*arr[end];

                //若是找到當前長度下更優的解,馬上更新dp數組
                if (times < dp[start][end]) {
                    dp[start][end] = times;

                    //更新添加括號的位置
                    bracket[start][end] = mid;
                }
            }
        }
    }
    return dp[1][num-1];
}

int main() {
    int num;
    char name = 'A';
    int arr[] = {30, 35, 15, 5, 10, 20};
    memset(dp, 0, sizeof(dp));
    num = sizeof(arr)/ sizeof(int);

    cout << "Minimum number of multiplications is "<< MinMatrixMul(arr, num) << endl;
    cout << "Order: " ;
    printBrackets(1, num-1, name);

}

參考資料

http://www.geeksforgeeks.org/dynamic-programming-set-8-matrix-chain-multiplication/code

http://www.geeksforgeeks.org/printing-brackets-matrix-chain-multiplication-problem/blog

相關文章
相關標籤/搜索