最近看到使用动态规划法求解矩阵连乘最小乘法次数,网上的一些copy主,只是copy,也不改错。本文已将一些不正确的错误更改。
问题描述:
给定n个矩阵:A1,A2,…,An,其中Ai与Ai+1是可乘的,i=1,2…,n-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)。每一种完全加括号的方式对应于一个矩阵连乘积的计算次序,这决定着作乘积所需要的计算量。
看下面一个例子,计算三个矩阵连乘{A1,A2,A3};维数分别为10X100 , 100X5 , 5X50 按此顺序计算需要的次((A1A2)A3):10X100X5+10X5X50=7500次,按此顺序计算需要的次数(A1(A2A3)):100X5X50+10X100X50=75000次
所以问题是:如何确定运算顺序,可以使计算量达到最小化。
如果你还不明白是什么意思,在看下面这个例子:给定一个 n 的矩阵序列,我们希望计算它们的乘积:
A1·A2·A3····An
其中,Ai 是 矩阵(i行,i+1列)
注意,这里不是要计算乘积,而是希望找到一个明确的计算次序,使得这个矩阵连乘的乘法次数最少,并求这个最小的乘法次数(m(1, n),这个值表示第 1 个到第 n 个矩阵相乘的最小乘法次数)。下面举举几个例子:
当 n=1 时,m(1,1) = 0;
当 n=2 时,m(1,2) = a1 * a2 * a3;
当 n=3 时,有两种情况:
((A1 · A2) · A3),这乘法次数为:a1*a2*a3 + a1*a3*a4;
(A1 · (A2 · A3)),这乘法次数为:a2*a3*a4 + a1*a2*a4;
而,m(1,3) = min {a1*a2*a3 + a1*a3*a4,a2*a3*a4 + a1*a2*a4}
这里我解释下,肯定很多人有点懵,这里的A1你可以认为代表的是一个a1*a2的矩阵,A2是a2*a3的矩阵,A3是a3*a4的矩阵。
简单来说,这道题的目的,就是在计算矩阵连乘时,求出一种方案,使得计算所需的乘法次数最小,输出的结果是这个最小的乘法次数。
动态规划求解
用动态规划算法解此问题时,可依据其递归式以自底向上的方式进行计算。在计算过程中,保存以解决的子问题的答案,每个子问题只计算一次,而在后面用到时只需要简单查一下,避免了大量的重复计算,最后得到了多项式时间的算法。
主要计算公式如下:
先求m(i,k)和m(k+1,j)的最小乘法次数,最后在求求m(i,k)和m(k+1,j)所对应的矩阵的乘法次数
动态规划问题,有一个非常明显的特点就是重叠子问题,上面的解法之所以时间复杂度那么高,一个重要的原因就是在使用递归方法时,有很多重复的计算,比如对于 m(1,2) 这个值,其实 m(1,3), m(1,4), m(1,5)… 都会用到,如何防止重复计算将是这个问题优化的一个重点,容易想到的方法就是使用空间换时间,在计算的过程中,额外申请一个二维数组(n*n)保存 m(i,j) 这个值,避免重复计算。
这个二维数据,也类似一个表格,下面的问题就是怎么填充这个表格(这个解法在算法导论上叫做自底向上的表格填充法)
这里,我们举个例子,有一个表格如下,首先,我们有:
m(i,j)=0,(i=j)
所以表格对角线上的值均为0,因为是要求 i < j的,所以这个表格对角线下面的空格的值是不需要去填充的。
假设这里,我们要去求 m(1,6)(图中红色的空格),根据 这个公式,其实是需要下面几个空格的值(图中黄色空格的数值):
m(1,1), m(2,6);
m(1,2), m(3,6);
m(1,3), m(4,6);
m(1,4), m(5,6);
m(1,5), m(6,6);
这里是有一个规律的,那就是上面的值均在红线以下。而且最开始的对角线的值是有的,所以将对角线依次往右上方平移去计算,这样的话,在求 m(i,j) 时,其所需要的值都是已知的。
右上角的深红色的点,就是我们要求解的最优解。
这部分的代码实现如下:
// a[0] 不使用,使用的是,1到 n+1
public int countMatricsChain(int[] a, int n) {
int[][] m = new int[n + 1][n + 1];
for (int i = 1; i <= n; i++) {
m[i][i] = 0;
}
for (int l = 2; l <= n; l++) {
for (int i = 1; i < n - l + 1; 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] + a[i] * a[k + 1] * a[j + 1];
if (q < m[i][j]) {
m[i][j] = q;
}
}
}
}
return m[1][n];
}
案例
矩阵连乘问题:
求矩阵A1(5×3)、A2(3×4)、A3(4×7)、A4(7×2)、A5(2×3)、A6(3×6)连乘的最佳计算次序。
算法实现(java):
package practice;
/**
* array[i][j] 表示Ai...Aj的最佳计算次序所对应的相乘次数 即存放各子问题的最优值
* s[i][j]=k 表示Ai...Aj这(j-i+1)个矩阵中最优加括号方法为(Ai...Ak)(Ak+1...Aj),即存放了各子问题的最优决策
* p[i]表示Ai的行数,p[i+1]表示Ai的列数
*/
public class MatrixChain{
private int array[][];
private int p[];
private int s[][];
public MatrixChain(){
//六个矩阵有七个数
p=new int[]{5,3,4,7,2,3,6};
array=new int[6][6];
s=new int[6][6];
}
/**动态规划*/
public void martixChain(){
int n=array.length;
for(int i=0;i<n;i++){
array[i][i]=0;
}
for(int r=2;r<=n;r++){ //不同规模的子问题
for(int i=0;i<=n-r;i++){ //每一个规模为r的矩阵连乘序列的首矩阵Ai
int j=i+r-1; //每一个规模为r的矩阵连乘序列的尾矩阵Aj
// 决策为k=i的乘法次数
array[i][j]=array[i+1][j]+p[i]*p[i+1]*p[j+1];
s[i][j]=i;
for(int k=i+1;k<j;k++){ //对Ai...Aj的所有决策,求最优值,记录最优决策
int t=array[i][k]+array[k+1][j]+p[i]*p[k+1]*p[j];
if(t<array[i][j]){
array[i][j]=t;
s[i][j]=k;
}
}
}
}
}
/*
* 待求矩阵为:Ai...Aj
*/
public void traceBack(int i ,int j){
if(i<j){
traceBack(i,s[i][j]);
traceBack(s[i][j]+1,j);
if(i==s[i][j] && (s[i][j]+1)==j){
System.out.println("把A"+(i+1)+"到A"+(j+1)+"括起来");
}else if(i==s[i][j] && (s[i][j]+1)!=j){
System.out.println("把A"+((s[i][j]+1)+1)+"到A"+(j+1)+"括起来,在把A"+(i+1)+"到A"+(j+1)+"括起来");
}else if(i!=s[i][j] && (s[i][j]+1)==j){
System.out.println("把A"+(i+1)+"到A"+(s[i][j]+1)+"括起来,在把A"+(i+1)+"到A"+(j+1)+"括起来");
}else{
System.out.println("把A"+(i+1)+"到A"+(s[i][j]+1)+"括起来,在把A"+((s[i][j]+1)+1)+"到A"+(j+1)+"括起来,然后把A"+(i+1)+"到A"+(j+1)+"括起来");
}
}
}
public static void main(String[] args) {
MatrixChain m=new MatrixChain();
m.martixChain();
m.traceBack(0, 5);
}
}
输出结果:
把A3到A4括起来
把A3到A4括起来,在把A2到A4括起来
把A2到A4括起来,在把A1到A4括起来
把A1到A4括起来,在把A1到A5括起来
把A1到A5括起来,在把A1到A6括起来