文章目录

  • 1.矩阵连乘问题描述
  • 分治法
  • 动态规划
  • 1.自顶向下的动态规划(备忘录法)
  • 2.自底向上的非递归动态规划

1.矩阵连乘问题描述

给定n个矩阵{A1,A2,…,An},其中Ai与Ai+1是可乘的,i=1,2…,n-1。如何确定计算矩阵连乘积的计算次序,使得依此次序计算矩阵连乘积需要的相乘次数最少 。

由于矩阵乘法满足结合律,所以可以有许多的不同的计算次序,然而不同的计算次序,相乘次数可能相差很大

具体例子:
有三个矩阵A1,A2,A3 大小分别为10* 100,100* 5,5*50,考虑**(A1(A2A3), (A1A2)A3 ** 的相乘次数

这里给出两个矩阵相乘所需数乘的公式

如下图

java矩阵拼接 java矩阵连乘_动态规划


可以得到

  • (A1(A2A3) 的次数为 7500
  • (A1A2)A3 的次数为 75000

由此可见不同的次序,结果差距可能非常大

那么我们该如何找到最佳乘积次序呢

先引入以下符号

  • n表示矩阵的个数
  • Ai表示第i个矩阵
  • A[i : j]表示矩阵连乘AiAi+1…Aj
  • pi表示Ai的列数
  • pi-1表示Ai的行数
  • s[i][j]表示矩阵连乘断开的位置断点k , 指在Ak和Ak+1之间断开
  • m[i][j]表示A[i : j]的最小乘次

由矩阵相乘的条件可知: 前一个矩阵的列数 = 后一个矩阵的行数 。因此, pi既是Ai的列数,也是Ai+1的行数。

如下图

java矩阵拼接 java矩阵连乘_动态规划_02


该问题有一个关键特征:计算矩阵链A[i :j]的最优计算方式,包含了子矩阵链A[i: k]和A[k+1 : j]的最优计算方式。

方法一*

分治法

对于每一个i,j区间,有一个变量k表示从第k个数组划分,k的范围是[i,j-1],枚举一个区间的所有划分情况然后求最小值就可以了,当然,由于我们是从上向下枚举的,比如划分A[1,4]为A[1:1],A[2,4]的情况,但是这两个子情况我们还不知道,所以就要再次递归,求解每一种子情况的答案

如图

java矩阵拼接 java矩阵连乘_java矩阵拼接_03

如果我们把A[i : j]拆分成A[i : k]与A[k+1 : j],那么A[i : k]与A[k+1 : j]相乘的次数为多少?

A[i : k]的结果为pi-1* pk的矩阵,A[k+1 : j]的结果为pk* pj的矩阵,则它们的相乘的计算量为pi-1* pk * pj .

那我们该如何计算m[i][j](A[i : j]的最小乘次)呢?

  • 如果 i=j ,则m[i][j]=0(只有一个矩阵)
  • 如果i<j,则用加括号的方式将乘积AiAi+1…Aj分裂为AiAi+1…Ak和Ak+1Ak+2…Aj,其中 i<=k<j ,则m[i ,j]等于子乘积Ai…k和Ak+1…j的最小开销再加上这两个结果矩阵相乘的开销

则可得递归公式

java矩阵拼接 java矩阵连乘_算法_04


k的位置有 j-i 种可能

private static int  RecurMatrixChain(int i, int j, int[] p, int[][] s) {
  // TODO Auto-generated method stub
  if(i==j) {
   return 0;
  }
  //取第一个断开位置时计算量为初始值
  int m=RecurMatrixChain(i, i, p,s)+RecurMatrixChain(i+1, j, p,s)+p[i-1]*
    p[i]*p[j];
  //计断点为i
  s[i][j]=i;
  for (int k = i+1; k < j; k++) {
   int temp=RecurMatrixChain(i, k, p,s)+RecurMatrixChain(k+1,j, p,s)+p[i-1]*
     p[k]*p[j];
   //取最小的计算量和当前的断点位置k
   if(temp<m) {
    m=temp;
    s[i][j]=k;
   }
   
  }
  return m;
 }

求得最小计算量后,如何把乘积次序表示出来呢,这里我们用一个TraceBack方法通过递归找到断点 并加括号

由于s[i : j]是Ai…Aj的最优分裂位置,通过这个位置s[i : j],我们考虑这两个子问题Ai…As[i: j]和As[i: j]+1…Aj,而s[i, s[ i: j] ]记录问题Ai…As[i: j]的最优分裂位置,s[s[i: j]+1, j]记录问题As[i: j]+1…Aj的最优分裂位置

s[1][6]的求解步骤根据s表如下图:

java矩阵拼接 java矩阵连乘_算法_05

private static void Traceback(int[][] s, int i, int j) {
  if(i==j) {
        System.out.print("A"+i);
  }
  else {
        System.out.print("(");
        Traceback(s, i, s[i][j]);
        Traceback(s,s[i][j]+1,j);
        System.out.print(")");
  }
 }

方法二

动态规划

1.首先看动态规划的基本步骤

  • 找出最优解的性质,刻画其特征结构
  • 递归的定义最优质(写出递归动态规划方程)
  • 自顶向下或者自底向上的方式求出最优值
  • 根据最优值求最优解

之前我们说过:

该问题有一个关键特征:计算矩阵链A[i :j]的最优计算方式,包含了子矩阵链A[i: k]和A[k+1 : j]的最优计算方式。

如果原问题的最优解,包含了其子问题的最优解,则我们称这种性质为最优子结构性质。若问题具有最优子结构性质,则可用动态规划算法求解。

1.自顶向下的动态规划(备忘录法)

java矩阵拼接 java矩阵连乘_算法_06

在递归的基础上,创建一个二维数组存已经计算过的值

private static int MatrixChain(int i, int j, int[][] m, int[][] s, int[] p) {
  //如果二维数组中已经存在我们要找的值则返回
  if (m[i][j] != 0) {
   return m[i][j];
  } else {
   if (i == j) {
    m[i][j] = 0;
   }
   if (j > i) {
    //取第一个断开位置时计算量为初始值
    m[i][j]=MatrixChain(i, i, m, s,p)+MatrixChain(i+1, j, m, s, p)+p[i-1]*
      p[i]*p[j];
    s[i][j]=i;
     for (int k = i+1; k < j; k++) {
       int t=MatrixChain(i, k, m, s,p)+MatrixChain(k+1, j, m, s, p)+p[i-1]*
       p[k]*p[j];
     if(t<m[i][j]) {
      m[i][j]=t;
      s[i][j]=k;
     }
    }
   }
  }
  return m[i][j];
 }

2.自底向上的非递归动态规划

自底向上的计算过程中,保存已解决的子问题的答案。每个子问题只计算一次,而在后面只需要简单查一下,从而避免大量的重复计算。

如图

java矩阵拼接 java矩阵连乘_动态规划_07


自底求出子问题,最后向上合并出我们想要解决的问题

为了正确实现自底向上方法,我们必须决定m表中哪些元素用于计算m[i,j],我们可以按照矩阵链长度增加的方式依次填充表格

如下图

java矩阵拼接 java矩阵连乘_算法_08


这上面其实还有一行m[1][1],m[2][2],m[3][3]…m[n][n],因为它们都等于0,所以并未标注

private static void MatrixChain(int m[][], int n, int s[][], int p[]) {
  for (int i = 1; i <= n; i++) {
   m[i][i] = 0;// 第一行的值均为0
  }
  // 依次从链长为[2:n]递增分别计算不同链长的矩阵连乘最优值
  for (int l = 2; l <= n; l++) {
   // 当计算链长为r时,i的取值范围
   for (int i = 1; i <= n - l + 1; i++) {
    // j与i的关系
    int j = i + l - 1;
    //先令m[i][j]最优值为矩阵链首元素划分下的值,后再进行对比
    m[i][j] = m[i][i] + m[i + 1][j] + p[i - 1] * p[i] * p[j];
    s[i][j] = i;
    //计算k∈[i+1:j-1]递增划分下最优值,并和之前因保存的进行比较
    for (int k = i + 1; k < j; k++) {
     int temp = m[i][k] + m[k + 1][j] + p[i - 1] * p[k] * p[j];
     if (temp < m[i][j]) {
      m[i][j] = temp;
      s[i][j] = k;
     }
    }
   }
}
  System.out.println(m[1][n]);
 }