问题描述:
给定由n个要相乘的矩阵构成的序列(链)<A1,A2,...,An>,要计算乘积A1A2...An,可以将两个矩阵相乘的标准算法作为一个子程序,通过加括号确定计算的顺序(对同一矩阵链,不同的计算顺序所需要的计算次数大不相同)。
目标问题:给定n个矩阵构成的矩阵链<A1,A2,...,An>,其中,i=1,2,...,n,矩阵Ai的维数为pi-1×pi,对乘积A1A2...An以一种最小计算次数加全部括号。
穷尽搜索:
令P(n)表示一串n个矩阵可能的加全部方案数。当n=1时,只有一种方案。当n>1时,一个矩阵链乘积变成两个子矩阵链乘积的乘积,分裂的地方可能发生在第k个矩阵和第k+1个矩阵之间,k=2,3,...,n-1。那么,P(n)=∑(k=2,3,...,n-1)P(k)*P(n-k)。这是关于n的指数形式,因此穷尽搜索不是好的策略。
动态规划:
1、寻找最优子结构
寻找最优子结构,利用这一子结构,根据子问题的最优解构造原问题的一个最优解。对于矩阵链相乘的问题,用Ai..j表示A1A2...An的求积结果,其中i≤j。对于某个k(1<k<j),首先计算Ai..k和Ak+1..j,然后计算它们相乘最终得到Ai..j。计算Ai..j的代价就是计算Ai..k和Ak+1..j的代价之和,再加上两者相乘的代价。
最优子结构如下:
假设AiAi+1...Aj的最优加括号方式在k和k+1处分开,则对前缀子链AiAi+1...Ak的加括号必须是一个最优加括号,为什么呢?如果存在有对AiAi+1...Ak有一个更优的加括号,那么会产生一个对AiAi+1...Aj的更优加括号,这与假设矛盾。同理,后缀子链Ak+1Ak+2...Aj也必须是一个最优加括号。
现在利用最优子结构,根据子问题的最优解构造原问题的最优解。即保证寻找一个最优的位置分割乘积。
2、递归求解
假设m[i,j]表示计算AiAi+1...Aj最优解的花费代价,1≤i≤j≤n。整个问题,计算A1..n的代价为m[1,n]。如果i=j,m[i,j]=0;当i<j时,假设最优加括号是将AiAi+1...Aj分裂成AiAi+1...Ak和Ak+1Ak+2...Aj。则m[i,j]等于计算Ai..k和Ak+1..j的最小代价,再加上这二者相乘的代价。即:
m[i,j]=m[i,k]+m[k+1,j]+pi-1pkpj (i<k<j)
上述公式假设已经知道k值,实际上,只知道k有j-i个值,分别是k=i,i+1,i+2...,j-1,k是其中最佳的值,需要通过如下方式确定k值:
m[i,j]=min(i≤k<j){m[i,j]=m[i,k]+m[k+1,j]+pi-1pkpj } (i<j)
各个m[i,j]的值给出了子问题的最优解的代价。为了跟踪如何构造一个最优解,定义s[i,j],让其记录AiAi+1...Aj最优解分裂处的k值。
3、计算最优解的代价
可轻易根据上述递归公式写出计算m[1,n]的递归算法。但是,它和穷尽搜索类似,具有指数时间。
可以观察到,原问题只有较少的子问题:每一对满足i<j的(i,j)对应一个子问题,总共不超过n2个。一个递归算法在递归树的不同分支中可能会多次遇到同一个子问题,子问题重叠是判断是否能够采用动态规划方法的第二个标志(第一个标志是最优子结构)。
对于矩阵链相乘问题不使用递归方式,而是使用自底向上的表格法计算最优解的代价。如下代码计算最优解的代价,其中Ai的维数为pi-1×pi,i=1,2,....,n。输入序列p=<p0,p1,p2,...,pn>,使用一个辅助表m[1...n,1...n]保存m[i,j]的值,使用s[1...n,1...n]记录取得最优m[i,j]时分裂处的k值。s用来构造最优解。
1 public void Matrix_Chain_Order() 2 { 3 for (int i = 1; i <= n; i++) { 4 m[i][i] = 0; 5 } 6 for (int l = 2; l <= n; l++) 7 { 8 for (int i = 1; i <= n-l+1; i++) 9 { 10 int j = i+l-1; 11 m[i][j] = 2147483647; 12 for (int k = i; k <= j-1; k++) 13 { 14 int q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; 15 if(q<m[i][j]) 16 { 17 m[i][j] = q; 18 s[i][j] = k; 19 } 20 } 21 } 22 } 23 }
4、构造最优解
利用保存在表格s[1..n,1..n]内的、经过计算的信息来构造一个最优解并不难。在每一个表项s[i,j]中记录了AiAi+1..Aj在Ak与Ak+1之间取得最优解的k值。计算A1..n最优解时,最终相乘的顺序就是A1..s[1,n]As[1,n]+1..n。s[1,s[1.n]]决定A1..s[1,n]最优解处的值,s[s[1,n]+1,n]则决定As[1,n]+1..n最优解处的值。下面程序递归的计算出一个矩阵链相乘的最优顺序。
1 public void Print_Optimal_Parens(int i,int j) 2 { 3 if(i==j) 4 { 5 System.out.print("A"+i); 6 } 7 else 8 { 9 System.out.print("("); 10 Print_Optimal_Parens(i, s[i][j]); 11 Print_Optimal_Parens(s[i][j]+1, j); 12 System.out.print(")"); 13 } 14 }
程序实现:
/* * To change this template, choose Tools | Templates * and open the template in the editor. */ package agrith; /** * * @author ning */ public class Matrix_Chain { private int [] p; private int [][]s; private int [][] m; private int n; public int getN() { return n; } public void setN(int n) { this.n = n; } public int[] getP() { return p; } public void setP(int[] p) { this.p = p; } public int[][] getS() { return s; } public void setS(int[][] s) { this.s = s; } public int getM(int i,int j) { return m[i][j]; } public void setM(int[][] m) { this.m = m; } public void Matrix_Chain_Order() { 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] = 2147483647; for (int k = i; k <= j-1; k++) { int q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j]; if(q<m[i][j]) { m[i][j] = q; s[i][j] = k; } } } } } public void Print_Optimal_Parens(int i,int j) { if(i==j) { System.out.print("A"+i); } else { System.out.print("("); Print_Optimal_Parens(i, s[i][j]); Print_Optimal_Parens(s[i][j]+1, j); System.out.print(")"); } } } matrix_Chain.Matrix_Chain_Order(); System.out.println("最优解花费的代价:"+matrix_Chain.getM(1, n-1)); System.out.println("最优解的顺序:"); matrix_Chain.Print_Optimal_Parens(1, n-1); } }
程序结果:
最优解花费的代价:15125
最优解的顺序:
((A1(A2A3))((A4A5)A6))