算法設計與分析——矩陣連乘(動態規划法、備忘錄法)


問題描述

        給定n個矩陣{A1,A2,…,An},其中,Ai與Ai+1是可乘的,(i=1,2 ,…,n-1)。用加括號的方法表示矩陣連乘的次序,不同的計算次序計算量(乘法次數)是不同的,找出一種加括號的方法,使得矩陣連乘的次數最小。
        通俗的來說就是: 一個 m * n 的矩陣與一個 n * p 的矩陣相乘,越需要進行 m * n * p 次乘法。矩陣的乘法雖不滿足交換律,但滿足結合律😀。我們可以通過對矩陣進行合適的結合,使得進行的乘法次數最少。

動態規划法

        將矩陣連乘積AiAi+1…Aj簡記為A[i:j] ,這里i≤j。考察計算A[i:j]的最優計算次序。設這個計算次序在矩陣Ak和 Ak+1之間將矩陣鏈斷開,i≤k<j。
        則其相應完全加括號方式為(AiAi+1…Ak)(Ak+1Ak+2…Aj)-> A[i:j]的計算量:A[i:k]的計算量加上A[k+1:j]的計算量,再加上A[i:k]和A[k+1:j]相乘的計算量

遞歸關系

        設計算A[i:j],1≤i≤j≤n,所需要的最少數乘次數m[i,j],則原問題的最優值為m[1,n]

k為斷開位置
m[i][j]實際是子問題最優解的解值,保存下來避免重復計算

        根據遞歸公式,對角線的值為0。其他值需要根據於斷開位置k的值來得到,k ∈ \in∈ [i,j),我們要遍歷所有k,就要訪問所求值的所有同一行左邊的值和同一列下方的值。因此,在代碼中我們可以使用自底向上、從左到右的計算順序來依次填充,最終得到右上角的值。

核心代碼

public static int matrixChain(int n,int[] p, int[][] m, int[][] s) {
	for (int i = 1; i <= n; i++)
		// 本身為0
		m[i][i] = 0;  // 初始化二維數組
	for (int r = 2; r <= n; r++) {
		for (int i = 1; i <= n - r + 1; i++) { 
			int j = i + r - 1;
			// 先以i進行划分
			m[i][j] = m[i + 1][j] + p[i - 1] * p[i] * p[j];  // 求出Ai到Aj的連乘
			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 (t < m[i][j]) {
					m[i][j] = t;
					s[i][j] = k;
				}
			}
		}
	}
	return m[1][n];
}
public static void traceback(int i, int j, int[][] s) {
	// 輸出A[i:j] 的最優計算次序
	if (i == j) {
		// 遞歸出口
		System.out.print("A"+i);
		return;
	} else {
		System.out.print("(");
		// 遞歸輸出左邊
		traceback(i, s[i][j], s);
		// 遞歸輸出右邊
		traceback(s[i][j] + 1, j, s);
		System.out.print(")");
	}
}

備忘錄法

  • 備忘錄方法是動態規划算法的變形。用表格保存子問題答案,避免重復計算。
  • 與動態規划不同的是:備忘錄方法的遞歸是自頂向下的,而動態規划是自底向上的。
  • 備忘錄方法為每個問題建立一個記錄項(如賦初值為0),初始化時,該記錄項存入一個特殊值,表示該問題尚未求解。
  • 備忘錄就是用來保存計算結果,在每次計算前查表,如果計算過,則直接取值,避免重復計算。

核心代碼

public static int memoMatrix(int i, int j, int[] p, int[][] m, int[][] s) {
		for (int x = 0; x < p.length; x++) {
            for (int y = 0; y < m[x].length; y++) {
                m[x][y] = -1;
            }
        }
        if (m[i][j] != -1)
            return m[i][j];
        if (i == j) {
            m[i][j] = 0;
            return 0;
        }
        int ans, minV = 9999999;
        //System.out.println("(" + i + "," + j + ")");//查看調用次序
        for (int k = i; k < j; k++) {
            ans = memoMatrix(k + 1, j, p, m, s) + memoMatrix(i, k, p, m, s) + p[i - 1] * p[k] * p[j];
            if (ans < minV) {
                minV = ans;
                s[i][j] = k;
            }
        }
        m[i][j] = minV;
        return minV;
    }

動態規划和備忘錄法的區別:

        備忘錄法是動態規划的變形。 與動態規划算法一樣, 備忘錄方法用表格保存已解決的子問題的答案。 在下次需要解此問題時,只要簡單的查看該子問題的解答, 而不必重新計算。

不同點

        與動態規划算法不同的是, 備忘錄方法色遞歸方式是自頂向下的, 而動態規划是自底向上的遞歸的。 因此備忘錄方法的控制結構與直接遞歸方法的控制結構相同, 區別在於備忘錄方法為每個解過的子問題建立了備忘錄以備需要時查看,避免了相同子問題的重復求解。

總結

        一般情況下,當一個問題的所有子問題都要至少解一次時,用動態規划算法比備用錄方法好,此時,動態規划算法沒有任何多余的計算。同時,對於許多問題,常可利用其規則的表格存取方式,減少動態規划算法的計算時間和空間需求。當子問題空間中的部分子問題可不必求解時,用備忘錄方法則較有利,因為從其控制結構可以看出,該方法只解那些確實需要求解的子問題。

運行示例

全部代碼:https://gitee.com/KSRsusu/arithmetic/tree/master/src/MatrixChain


免責聲明!

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



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