问题分析
矩阵连乘问题是经典的动态规划问题,其主要是n个矩阵进行矩阵乘法运算时,通过括号改变运算的先后顺序,减少运算次数,找到最佳划分方法,求解最少运算次数。
算法分析
矩阵连乘问题中动态规划可以帮助我们找到从每个矩阵到另一个矩阵的最小运算次数以及对应的划分,我们用dp[i][j]存储从矩阵A[i]到矩阵A[j]的运算次数。每当对从矩阵A[i]连乘到矩阵A[j]求解最小连乘括号划分时,我们引入A[i]到A[j]连乘序列中的一个矩阵A[k]。我们从第i+1个矩阵开始尝试,直到矩阵A[j],如果有更优解——得到更小的dp[i][j],那么就更新,并且将第k个矩阵视为一个分割界限进行记录,直到完成对dp[1][n]的更新。
①初始化
因为每个矩阵对自身不用进行运算,所以我们将dp[i][j]的对角线元素初始化为0
for(int i=1;i<matrixNum;i++){//初始化对角线元素为零
dp[i][i]=0;
}
②核心算法
1)引入变量length用来记录当前矩阵连乘的数目,我们从两个矩阵连乘开始寻找最优解,直到求出n个矩阵连乘的最优解。
2)引入变量j记录连乘矩阵的最后一个矩阵的下标。
3)引入二维数组decison[i][j]记录每轮矩阵A[i]连乘到矩阵A[j]的分割界限,每一轮都会不断更新,直到找到本轮最好的分割界限。
4)核心算法共三轮循环:
第一轮用来控制由小问题不断向大问题扩展——先求解两两矩阵相乘最优解,再进行连续三个矩阵相乘求最优解,最终求出n个矩阵连乘的最优解。
第二轮连乘起点矩阵遍历的控制。每轮循环需要先把分割界限设定在起点矩阵,对dp[i][j]进行初始化。
第三轮连乘矩阵A[i]到矩阵A[j]之间,中间矩阵A[k]的遍历控制。
注意 :个人认为最难理解的地方在于,每次对dp[i][j]的更新都要将分割界限k两侧并且运算后的整体矩阵单独拿出来进行相乘运算并且加和—— matrixIndex[i-1]*matrixIndex[i]*matrixIndex[j];。
int j=0;
for(int length=2;length<=matrixNum;++length){//length表示矩阵连乘的矩阵数目
for (int i=1;i<=matrixNum-length+1;++i){
j=i+length-1;//length-1表示本次矩阵连乘的结果加和次数,j记录连乘矩阵的最后一个矩阵的下标
dp[i][j] = dp[i+1][j]+matrixIndex[i-1]*matrixIndex[i]*matrixIndex[j];//先将分割界限定在i,即最后计算第一个矩阵与后面整体相乘
decision[i][j] = i;//记录分割界限
for (int k = i + 1; k < j; ++k){
int tmp = dp[i][k]+dp[k+1][j]+matrixIndex[i-1]*matrixIndex[k]*matrixIndex[j];//k作为分割界限,不断寻找更优的划分界限,使得该规模矩阵相乘计算次数尽可能小
if(dp[i][j]>tmp){//如果有更优解就更新
dp[i][j] = tmp;
decision[i][j] = k;//更新分割界限
}
}
}
}
③结果展示
完整代码
package com.algorithm.review;
import java.util.Scanner;
public class Matrix {
public int [] matrixIndex=new int[100];
public int [][] decision = new int[100][100];//分割界限记录
public int [][]dp = new int[100][100];
int matrixNum = 0;
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
Matrix matrix =new Matrix();
matrix.matrixNum=scanner.nextInt();
for (int i=0;i <= matrix.matrixNum;i++){
matrix.matrixIndex[i] = scanner.nextInt();
}
matrix.matrixChain();
System.out.println("括号划分:");
matrix.print(1,matrix.matrixNum);
System.out.println();
System.out.println();
System.out.println("最小连乘次数dp表:");
for (int i=1;i<= matrix.matrixNum;i++){
for (int k=1;k<=i;k++){
System.out.print("null ");
}
for (int k=i+1;k<=matrix.matrixNum;k++)
System.out.print(matrix.dp[i][k]+" ");
System.out.println();
}
System.out.println();
System.out.println("最小连乘次数:"+matrix.getMatrixResult());
}
public void matrixChain(){
for(int i=1;i<matrixNum;i++){//初始化对角线元素为零
dp[i][i]=0;
}
int j=0;
for(int length=2;length<=matrixNum;++length){//length表示矩阵连乘的矩阵数目
for (int i=1;i<=matrixNum-length+1;++i){
j=i+length-1;//length-1表示本次矩阵连乘的结果加和次数,j记录连乘矩阵的最后一个矩阵的下标
dp[i][j] = dp[i+1][j]+matrixIndex[i-1]*matrixIndex[i]*matrixIndex[j];//先将分割界限定在i,即最后计算第一个矩阵与后面整体相乘
decision[i][j] = i;//记录分割界限
for (int k = i + 1; k < j; ++k){
int tmp = dp[i][k]+dp[k+1][j]+matrixIndex[i-1]*matrixIndex[k]*matrixIndex[j];//k作为分割界限,不断寻找更优的划分界限,使得该规模矩阵相乘计算次数尽可能小
if(dp[i][j]>tmp){//如果有更优解就更新
dp[i][j] = tmp;
decision[i][j] = k;//更新分割界限
}
}
}
}
}
public void print(int i,int j){
if(i==j){
System.out.print("A["+i+"]");
return ;
}
System.out.print("(");
print(i,decision[i][j]);
print(decision[i][j]+1,j);
System.out.print(")");
}
public int getMatrixResult(){
return dp[1][matrixNum];
}
}