一、问题描述
给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2,…,n-1。要算出这n个矩阵的连乘积A1A2…An。由于矩阵乘法满足结合律,故计算矩阵的连乘积可以有许多不同的计算次序。这种计算次序可以用加括号的方式来确定。若一个矩阵连乘积的计算次序完全确定,也就是说该连乘积已完全加括号,则可以依此次序反复调用2个矩阵相乘的标准算法计算出矩阵连乘积。完全加括号的矩阵连乘积可递归地定义为
(1)单个矩阵是完全加括号的;
(2)矩阵连乘积A是完全加括号的,则A可表示为2个完全加括号的矩阵连乘积B和C的乘积并加括号,即A=(BC)。
例如,矩阵连乘积A1A2A3A4有5种不同的完全加括号的方式:(A1(A2(A3A4))),(A1((A2A3)A4)),((A1A2)(A3A4)),((A1(A2A3))A4),(((A1A2)A3)A4)。每一种完全加括号的方式对应于一个矩阵连乘积的计算次序,这决定着作乘积所需要的计算量。若A是一个p×q矩阵,B是一个q×r矩阵,则计算其乘积C=AB的标准算法中,需要进行pqr次数乘。
为了说明在计算矩阵连乘积时,加括号方式对整个计算量的影响,先考察3个矩阵{A1,A2,A3}连乘的情况。设这三个矩阵的维数分别为10×100,100×5,5×50。加括号的方式只有两种:((A1A2)A3),(A1(A2A3)),第一种方式需要的数乘次数为10×100×5+10×5×50=7500,第二种方式需要的数乘次数为100×5×50+10×100×50=75000。第二种加括号方式的计算量时第一种方式计算量的10倍。由此可见,在计算矩阵连乘积时,加括号方式,即计算次序对计算量有很大的影响。于是,自然提出矩阵连乘积的最优计算次序问题,即对于给定的相继n个矩阵{A1,A2,…,An}(其中矩阵Ai的维数为pi-1×pi,i=1,2,…,n),如何确定计算矩阵连乘积A1A2…An的计算次序(完全加括号方式),使得依此次序计算矩阵连乘积需要的数乘次数最少。
穷举搜索法的计算量太大,它不是一个有效的算法,本实验采用动态规划算法解矩阵连乘积的最优计算次序问题。
二、算法思路
动态规划算法的基本思想是将待求解问题分成若干个子问题,先求解子问题,然后从这些子问题的解得到原问题的解。与分治法不同的是,动态规划法经分解得到的子问题往往不是相互独立的,前一子问题的解为后一子问题的解提供有用的信息,可以用一个表来记录所有已解决的子问题的答案,不管该子问题以后是否被用到,只要它被计算过,就将其结果填入表中。具体递归式子的推导请参考算法导论。
三、程序
package dynamic_progranmming;
/*
* 矩阵连乘加括号的问题
*/
/**
* @author wangzhendong
*0
*/
public class MatrixMut {
private int[][] f,s;//保存矩阵的边长信息,如果有n个矩阵,就会有n*n个元素
private int num;//连乘矩阵的数目
private int[] p;//保存矩阵的维数,第i个矩阵矩阵的维数是p[i]p[i+1]
/*
* 构造函数
*/
public MatrixMut(int[] p,int num) {
// TODO Auto-generated constructor stub
/*
* 矩阵的数目
*/
this.num=num;
/*
* 存储矩阵维数的数组
*/
this.p=p;
/*
* 存放结果的数组
*/
f=new int[num][num];
/*
* 存放分割方式的数组
*/
s=new int[num][num];
/*
* 初始化每个值
*/
for(int i=0;i<num;i++)
{
for(int j=0;j<num;j++)
{
f[i][j]=100000000;
}
}
}
public int MatrixChain()
{
if(p.length-1!=num)
{
return -1;
}
else
{
/*
* 当只有一个矩阵的时候,不需要代价
*/
for(int g=0;g<num;g++)
{
f[g][g]=0;
}
/*
* 每个处理的矩阵个数
*/
for(int l=2;l<=num;l++)
{
/*
* 起始矩阵的编号
*/
for(int i=0;i<=num-l;i++)
{
/*
* 结束矩阵的编号
*/
int j=i+l-1;
int sum=0;
/*
* 分割
*/
for(int k=i+1;k<=j;k++)
{
sum=f[i][k-1]+f[k][j]+p[i]*p[k]*p[j];
if(sum<f[i][j])
{
f[i][j]=sum;
s[i][j]=k-1;
}
}
}
}
}
return f[0][num-1];
}
/*
* 使用递归方法解决问题
*/
public int MatrixChain2(int i,int j)
{
/*
* 如果只有一个矩阵
*/
if(i==j)
{
return 0;
}
else
{ int sum=10000000;
int q=0;
/*
* 在每个矩阵处分开
*/
for(int k=i+1;k<=j;k++)
{
/*
* 递归计算每个分割的情况下的代价
*/
q=MatrixChain2(i,k-1)+MatrixChain2(k,j)+p[i]*p[k]*p[j];
if(sum>q)
{
sum=q;
/*
* 记录分割的地方
*/
s[i][j]=k-1;
}
}
return sum;
}
}
/*
* 使用递归来输出结果
*/
public void print(int i,int j)
{
/*
* 递归结束条件,只有一个
*/
if(i==j)
{
System.out.print(“A”);
System.out.print(i);
}
else
{
/*
* 递归输出
*/
System.out.print(“(“);
print(i,s[i][j]);
print(s[i][j]+1,j);
System.out.print(“)”);
}
}
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] p={3,5,4,6,7,12,17,23,12};
MatrixMut mu=new MatrixMut(p,8);
System.out.println(mu.MatrixChain());
mu.print(0, 7);
System.out.println(mu.MatrixChain2(0,7));
mu.print(0, 7);
}
}