動態規劃之矩陣鏈乘法(第15章)

實驗室催促畢業論文進展,所以今天下午我得收拾一下,準備一下畢業論文需要弄的東西。 唉, 感覺自己就是太笨了。 ——題外話。

1.動態規劃的原理

[1] 什麼情況下使用動態規劃?
  適合應用動態規方法的求解最優化問題應該具備兩個要素:最優子結構和子問題重疊。使用動態規劃方法求解最優化問題的第一步就是刻畫最優解的結構。如果一個問題的最優解包含子問題的最優解,我們就成此類問題具有-最優解結構-性質。第二個條件子問題重疊,子問題的空間足夠的小,即問題的遞歸算法會反覆的求解相同的子問題,而不是一味的生成新的子問題。如果遞歸算法反覆求解相同的子問題,我們就稱最優化問題具有-重疊子問題-的性質。動態規劃算法通常利用重疊子問題的性質:對每個子問題求解一次,將解存入一個表中,當再次需要這個子問題的時候直接查表,每次查表的代價爲常量時間。
  

[2] 重構最優解和備忘機制 
  從實際的角度考慮我們通常將每個子問題所做的選擇存在一個表中,這樣就不需要根據代價值來重構這些信息。
  備忘機制:在遞歸的算法中加入備忘機制,可以讓自頂向下的遞歸算法達到和自底向上的動態規劃算法相似的效率。
  

2.矩陣鏈乘法概述

Description
給定n個矩陣{A1,A2,…,An},其中Ai與Ai+1是可乘的,i=1,2 ,…,n-1。如何確定計算矩陣連乘積的計算次序,使得依此次序計算矩陣連乘積需要的數乘次數最少。
Input
有N個矩陣連乘,用一行有n+1個數數組表示,表示是n個矩陣的行及第n個矩陣的列,它們之間用空格隔開.
Output
你的輸出應該有C行,即每組測試數據的輸出佔一行,它是計算出的矩陣最少連乘積次數,輸出最優全括號結構

3.矩陣鏈乘法算法分析與設計

矩陣鏈乘法問題動態規劃分析:
給定由n個矩陣構成的序列[A1,A2,…,An],對乘積A1A2…An,找到最小化乘法次數的加括號方法。
1)尋找最優子結構
  此問題最難的地方在於找到最優子結構。對乘積A1A2…An的任意加括號方法都會將序列在某個地方分成兩部分,也就是最後一次乘法計算的地方,我們將這個位置記爲k,也就是說首先計算A1…Ak和Ak+1…An,然後再將這兩部分的結果相乘。
最優子結構如下:假設A1A2…An的一個最優加括號把乘積在Ak和Ak+1間分開,則前綴子鏈A1…Ak的加括號方式必定爲A1…Ak的一個最優加括號,後綴子鏈同理。一開始並不知道k的確切位置,需要遍歷所有位置以保證找到合適的k來分割乘積。
2)構造遞歸解
  設m[i,j]爲矩陣鏈Ai…Aj的最優解的代價,則
┌ 0 如果i = j
m[i,j] = │
└ min(i≤k<j) {m[i,k] + m[k+1,j] + Ai.row*Ak.col*Aj.col} 如果i < j

3)構建輔助表,解決重疊子問題
  從第二步的遞歸式可以發現解的過程中會有很多重疊子問題,可以用一個n*n維的輔助表 m[n, n]來保存子問題的解,表中每個元素包含2個信息,分別是最優乘積代價及其分割位置k 。輔助表 m[n, n]可以由2種方法構造,一種是自底向上填表構建,該方法要求按照遞增的方式逐步填寫子問題的解,也就是先計算長度爲2的所有矩陣鏈的解,然後計算長度3的矩陣鏈,直到長度n;另一種是自頂向下填表的備忘錄法,該方法將表的每個元素初始化爲某特殊值(本問題中可以將最優乘積代價設置爲一極大值),以表示待計算,在遞歸的過程中逐個填入遇到的子問題的解。
  備忘錄法會比自底向上法慢一個常數因子,因爲前者有遞歸調用的代價,維護表格的開銷也稍大。不過如果動態規劃中有些的子問題是不需要的話,自頂向下可能會少計算一些子問題。
4)構建最優解
  雖然m[n, n]可以記錄矩陣鏈乘積所需的最小的標量乘法的運算次數,但是它並沒有直接支出如何進行這種最優代價的矩陣鏈乘法的計算完全加括號的方式。輔助表s[1…n-1,2….n]記錄了構造最優解所需的信息。每個表項s[i,j]記錄一個k值,指出AiAi+1......Aj 最優括號化方案的分割點應該在AkAk+1 之間。

核心算法設計:
算法僞代碼

4.矩陣鏈乘法Java實現

矩陣鏈乘法核心類

//矩陣鏈乘法核心類
package lbz.ch15.dp.ins2;

/**
 * @author LbZhang
 * @version 創建時間:2016年3月9日 上午10:29:42
 * @description 矩陣鏈乘法核心類
 */
public class MatrixChain {
    public static Pair matrixChainOrder(int[] p) {
        int n = p.length - 1;
        int i, j, L, k, q;
        int[][] m = new int[n + 1][n + 1];
        int[][] s = new int[n + 1][n + 1];
        for (i = 0; i <= n; i++) {
            m[i][i] = 0;
        }
        for (L = 2; L <= n; L++) { // /L是子矩陣鏈的長度
            for (i = 1; i <= n - L + 1; i++) { // /這個地方n也算是一個 eg 6-2+1=5 5,6
                                                // 就是一組
                j = i + L - 1; // /這個地方我們可以分析得 j-L+1=i 類似於上面一行的說明解釋
                m[i][j] = Integer.MAX_VALUE; // /預先設置m[i][j]等於無窮大
                for (k = i; k <= j - 1; k++) {
                    q = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j]; // /用於暫時存儲i-j子矩陣鏈中的求解的值
                    // ////由於全部加括號 那麼相互之間構成括號結合的兩項肯定是相互靠近在一起的兩項
                    if (q < m[i][j]) {
                        m[i][j] = q;
                        s[i][j] = k; // /分裂位置就是k加括號的位置
                    }
                }
            }
        }
        return Pair.make(m, s);
    }

    public static void printOptimalParens(int[][] s, int i, int j) {
        if (i == j) {
            System.out.printf("A%d", i);
        } else {
            System.out.print("(");
            printOptimalParens(s, i, s[i][j]);
            printOptimalParens(s, s[i][j] + 1, j);
            System.out.print(")");
        }
    }
}

矩陣鏈乘法輔助類

package lbz.ch15.dp.ins2;
/** 
 * @author LbZhang
 * @version 創建時間:2016年3月9日 上午10:36:20 
 * @description 矩陣鏈乘法輔助類
 */
public class Pair {
    public static int[][] mm ;
    public static int[][] ss;


    public static Pair make(int[][] m, int[][] s) {
        mm = m;
        ss = s;
        return null;
    }
    /**
     * 調用方法
     * @param args
     */
    public static void main(String[] args) {
        int[] p = {30,35,15,5,10,20,25};
        MatrixChain.matrixChainOrder(p);
        MatrixChain.printOptimalParens(Pair.ss, 1, 6);
    }

}

發佈了69 篇原創文章 · 獲贊 34 · 訪問量 13萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章