矩阵链乘法的Java实现

题目:给定n格矩阵序列 A1,A2,A3,…,An,我们需要计算它们的乘积。
我们把这个矩阵序列称之为矩阵链。

根据我们的线性代数的知识,两个矩阵要可以相乘,则必须满足相容性。

何为相容性?矩阵A和矩阵B相乘,那么A的列数量必须和B的行数量是相等的。

使用具体的例子解释一下:A是pq的矩阵,B是qr的矩阵,那么乘积C一定是p*r的矩阵。

矩阵相乘的伪代码如下:

MATRIX_MUTIPLY(A,B)
if A.columns != B.rows
	return "incompatible dimensions"
else
	C=A.rows X B.columns
	for i = 1 to A.rows
		for j = 1 to B.columns
			c(ij) = 0
				for k = 1 to A.columns
					c(ij) = c(ij) + a(ik)*b(kj)	
return C

根据上面的算法,我们可以知道矩阵相乘主要的耗时操作都在乘法次数上面,A.rows * A.columns * B.columns(注意A.columns = B.rows)这三个数字的乘积。

我们现在需要考虑到的问题在于,当多个矩阵连乘的时候,先算那两个,再算那两个,…,最后算那两个可以使我们进行的乘法运算次数达到最少。如果对于这一点优疑问的话,举个例子简单说明一下。

三个矩阵A,B,C,假设规模分别为10X100, 100X5, 5X50。如果先按照((AB)C),需要的乘法次数为:101005 + 10550 = 7500;如果按照(A(BC)),需要的乘法次数为:100550 + 1010050 = 75000。可见第一种结合方法的计算速度是第二种的10倍。

到现在为止,我们只是为了说明矩阵链乘法的核心问题在如何结合矩阵。但是,怎么用算法实现结合矩阵就是接下来的重点了。

现在有一点很明显,如果只有两个矩阵,那么就只有一种结合方案。

我们现在不妨做一个假设:m[i,j]表示矩阵Ai到Aj之间需要的乘法次数的最小值,这里i < j且中间不会有断点。假如 i=1,j=4,则表示A1A2A3*A4.

我们现在还做一个假设:在 i 和 j 之间的最佳分割点已经知道,它的值就是k,满足,i <= k < j。

现在我们还有一个已知条件,也是唯一的一个条件:矩阵Ai的规模为Pi-1 X Pi,有一个序列 P = [P0,P1,P2,…,Pn]

基于上面的两个假设,下面的等式是明显成立的:

m[i,j] = m[i,k] + m[k+1,j] + P[i-1]*P[k]*P[j]

这个等式将会是我们整个算法的核心,把整个问题一分为二,然后剩下的问题一按同样的手法一分为二,一直这样分割,直到这个问题遇到分割的终止条件,只剩下两个相乘。

在实现算法之前,我需要在假设几个数组来存储计算的结果。
m[n][n]和s[n][n],分别存储最小乘法次数和最优分割点k值。

可以理解为m[i][j]里面存放的是矩阵Ai到Aj之间连乘所需要的最少的乘法次数。

s[i][j]里面存放着矩阵Ai到Aj之间最佳分割点k,也就是说k是 i 到 j 之间的某个数值。

    /**
	 * @param p :矩阵链的连续矩阵的长度
	 * @return 一个集合,里面包裹了两个整型数组。分别是代价和
	 *         最有分割点k     
	 */
   public static HashMap<Integer, int[][]>  	 matrix_chain_order(int p[]) {
// 作为结果容器返回
HashMap<Integer, int[][]> map = new HashMap<Integer, int[][]>();
		int n = p.length - 1;
		int m[][] = new int[n + 1][n + 1];// 保存结果
		int s[][] = new int[n + 1][n + 1];// 保存K值

		for (int i = 0; i < n + 1; i++)
			m[i][i] = 0;// 长度为1的链的最小计算代价

        // l表示矩阵链的长度
		for (int l = 2; l < n + 1; l++) {
			for (int i = 1; i < n - l + 2; i++) {
				int j = i + l - 1;
				// 把可能有值的数组全部初始化为整数最大值
				m[i][j] = Integer.MAX_VALUE;
				for (int k = i; k < j; k++) {
					int q = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
					if (q < m[i][j]) {// 如果计算得到的q值比初始化的MAX小,表示此值有效,则保存起来
						m[i][j] = q;
						s[i][j] = k;
					}
				}
			}
		}
		map.put(1, m);
		map.put(2, s);
		return map;
	}

之前我们对最佳分割点k是假设已经知道的,但是实际上我们是不知道,那么请看第17行代码,在这个循环里面,我们会把从 i 到 j 之间的每一个数值都进行遍历计算,然后进行对比,把较小的值保存起来。于是,这样的假设具有一定的巧妙性。后期的具体操作会把这样的假设处理成一个确实存在的情况。【这里需要好好琢磨】

这段代码里面,由于我们需要返回两个二维数组,java语言并没有这样的特性,但是HashMap却提供了存储对象的能力。
这个方法已经封装了所有需要的算法,接下来是要测试这歌函数:

int p[] = { 30, 35, 15, 5, 10, 20, 25 };
int p1[] = { 5, 10, 3, 12, 5, 50, 6 };
HashMap<Integer, int[][]> map = matrix_chain_order(p1);
		// 乘法代价表格
		int m[][] = map.get(1);
		// 具体分割最优k值表格
		int s[][] = map.get(2);
		for (int i = 0; i < m.length; i++) {
			for (int j = 0; j < m[i].length; j++) {
				if (i < j && i > 0) {
					System.out.println("m[" + i + "]" + "[" + j + "]=" + m[i][j]);
				}
			}
		}

打印输出了所有的乘法次数代价。

到这里,我们依然没有找到如何能够手工操作去结合矩阵序列,但是我们找到了每两个序列之间的代价二维表格。

我们举个例子,帮助理解下面的代码:
根据之前的假设和推理,s[1][n]里面存储的是A1到An之间的最优分割点,暂时记为:s[1][n]=k。也就是说,A1到An最后一次最优矩阵乘法是(A1…Ak)*(Ak+1…An)。同样的,s[1][k]和s[k+1][n]分别是前半段和后半段的最优分割点,还是一样的往下面一只分割,直到s的两个下标值相等,就会推出递归。

实际代码如下:

   /**
	 * @param s
	 *            :存储最优k值的二维数组(表格)
	 * @param i
	 *            :矩阵链的起点
	 * @param j
	 *            :矩阵链的终点
	 */
	public static void print_optional_parens(int s[][], int i, int j) {
		if (i == j) {// 函数递归跳出的条件是起点和终点重合
			System.out.print("A" + i);
		} else {
			System.out.print("(");
			// 注意:s[i][j]记录者从i到j之间最优分割点k的值,一定满足:i<=k<j
			print_optional_parens(s, i, s[i][j]);// 这句话打印出i..s[i][j]之间的最后一次矩阵乘法运算
			print_optional_parens(s, s[i][j] + 1, j);// 这句话打印出s[i][j]+1..j之间的最后一次矩阵乘法运算
			System.out.print(")");
		}
	}

测试调用如下:

//表示A1到A6之间的具体结合方法
print_optional_parens(s, 1, 6);

到现在为止,所有的核心问题都已经解决完毕,但是还有一个问题就是矩阵链乘法的最后结果计算,可以根据最开始的伪代码进行计算。在此就不做java代码展示了。

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值