关于矩阵加括号的问题

 

一、问题描述

给定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);
 }

}

点赞