算法设计与分析——矩阵连乘(动态规划法、备忘录法)

问题描述

        给定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

posted @ 2021-03-24 17:21  苏洬  阅读(2081)  评论(0编辑  收藏  举报