矩阵链乘
- 两个矩阵A(p, q),B(q, r)相乘,p,q分为A矩阵的行列数,q,r分为B矩阵的行列数。
- 伪代码实现如下
let C be a new Matrix(p, r)
for i = 1 to p
for j = 1 to r
C[i][j] = 0;
for k = 1 to q
C[i][j] += A[i][j] * B[i][j];
return C
中间计算过程中执行的乘法标量数为 W=p * q * r,将此记为其运算代价W;
矩阵链乘问题最优解的计算来源于矩阵运算里的乘法结合律,AxBxC = Ax(BxC) = (AxBxC),虽然计算结果一样,但是这里存在两种计算过程,那就是先让哪两个矩阵先相乘,不妨举例说明:矩阵A是4x5, 矩阵B是5x7, 矩阵C是7x3,若先AxB再和C相乘,那么中间运算代价W1=4x5x7+4x7x3=224;若先BxC再左乘A,那么W2=5x7x3+4x5x3=165,对比可知,第二种方法可以减少计算量,即为矩阵链A~C的最优解,记为(Ax(BxC))。
>
- n个矩阵链乘,那么中间任意的两个矩阵必须满足前矩阵的列数必须等于后矩阵的行数,那么需要输入的信息就仅仅是n个矩阵的行列数,实际上是n+1个数字了,存入一维数组,带入计算,大大简化问题。
- 采用动态规划的思想,其最优括号化方案,为比较所有的中间的分割点,及基于此的左右两部分子矩阵链的局部最优解,并比较大小,得出这所有方案中的最优解,中间有子问题的重叠,所以这里采用自下向上的解法,先找出少部分矩阵链的最优解,并保存,再不断扩大,直至最后的最长矩阵链的最优解。
代码实现
package useful;
import java.io.PrintStream;
import java.util.Arrays;
import java.util.Random;
/**
* n个矩阵链乘问题,根据矩阵相乘的结合律,如何简化计算使得该计算过程拥有最少的乘法标量
* @author Gastby
*
*/
public class MultiplyMatrix {
public static void main(String[] args) {
Random r = new Random();
int[] arr = {30, 35, 15, 5, 10, 20, 25};
int len = arr.length - 1;
int[][] m = new int[len][len];
int[][] s = new int[len][len];
MatrixOrder(arr, s, m); //主算法,自下而上求算所有情况下(i<j && i,j都属于0~(len-1))的最优链乘解
PrintStream ps = System.out;
ps.println(Arrays.toString(arr));
StringBuilder str = new StringBuilder();
for(int i=0; i<m.length; i++) {
str.append(Arrays.toString(m[i]) + "\n");
}
ps.println("[" + str + "]"); //打印m二维矩阵,是一个上三角阵,因为j>i
ps.println(m[0][len-1]); // 打印出所有矩阵在一起,从第一个到最后一个链乘的最少乘法标量数
/*回溯构建最佳解并打印出来*/
printOptimalParens(s, 0, len-1);
}
/**
* 构造最优解
* @param s 用来存放每一组(i, j)代表的矩阵链(i<j)之间的最优分割点
* @param i 从第i个矩阵
* @param j 到第j个矩阵
*/
private static void printOptimalParens(int[][] s, int i, int j) {
if(i == j) {
System.out.print("A" + (i + 1));
} else {
System.out.print("(");
printOptimalParens(s, i, s[i][j]);
printOptimalParens(s, s[i][j]+1, j);
System.out.print(")");
}
}
/**
* 从底向上的求算最佳链乘计算步骤数,并存入m矩阵,内部包含初始化,主要实现算法
* @param arr 链乘矩阵系列存储行列数组,n个矩阵则arr长度为n+1,多一个参数
* @param s 用来存储第i到j之间最优分割点的二维数组
* @param m 存储第i到j个矩阵之间链乘最少乘法标量数
*/
private static void MatrixOrder(int[] arr, int[][] s, int[][] m) {
int n = m.length;
for(int i=0; i<n; i++) {
m[i][i] = 0;
}
for(int l=2; l<=n; l++) {
for(int i=0; i<n-l+1; i++) {
int j = i+l-1;
m[i][j] = Integer.MAX_VALUE;
int q;
for(int k=i; k<j; k++) {
q = m[i][k] + m[k+1][j] + arr[i] * arr[k+1] * arr[j+1];
if(q < m[i][j]) {
m[i][j] = q;
s[i][j] = k; //能用来记录从第i到第j个矩阵链乘里,中间首先挑哪一个数k(i<=k<j)划分用来划分最合适
}
}
}
}
}
}
- 其中运用了动态规划的思想,采用自下向上的解法,求出局部最优解,再计算总的最优解,进而计算整个矩阵链的答案,并回溯,重构出解法等,下附上答案。