参考链接:
动态规划——矩阵连乘问题算法及实现详解(附完整代码)-CSDN博客动态规划之——矩阵连乘(全网最详细博文,看这一篇就够了!)_矩阵连乘问题-CSDN博客
1.1矩阵连乘
1.1.1什么是矩阵连乘问题?
找出一种使得做矩阵连乘时相乘次数最小的一个计算次序。
矩阵连乘可以被递归成如上的形式。
在这里举一个简单的例子我们要计算矩阵ABC的乘积,可以(AB)C,A(BC)两种计算次序,它们计算所得的结果相同,但相乘的次数不同。
A(5x4) B(4x3) C(3x6)
(AB)C的相乘次数为:150次
A(BC)的相乘次数为:192次
但是面对实际问题中,肯定不可能出现三个矩阵相乘,让我们求一个最优的计算次序。那么面对这个问题,应该怎么解决呢?
1.1.2矩阵连乘问题
问题描述:
矩阵相乘,有一系列的整数,矩阵的规模为,现计算出矩阵连乘所需要的计算次数最少是多少?
方案一:我们很容易可以想到,穷举所有的计算次序,然后得出它们的计算次数,找出最小的那个即可。
是不是感觉我在发癫,与其穷举所有的计算次序再计算出所有的计算次数,还不如随便找一个计算次序算得了。(暴力算法的时间复杂度为)
做事当然不能老是靠蛮力干,当然有巧妙的方法去求解它啦。(并不针对沸羊羊)
1.1.3动态规划解决矩阵连乘
1.寻找最优子结构
在寻找计算次数最少的计算次序的过程,实际上就是给这一系列矩阵添加括号的过程。
现有矩阵相乘,我们怎么样添加括号才能使得它的计算次数最少即获得最优解呢?
我们从计算的最后一步考虑,不管是多少矩阵连乘,最后都会变成两个新的矩阵(B*C)相乘。
那如果说,经过前面的一番猛虎操作,在获得B、C的过程中,各自的计算次数都是最少的,那么B*C再次运算得到的计算次数也就是最少的。就是一个最优子结构。(另一部分也是。)
那这个时候,我们就想了,如果B和C都能像上述所言,同样的被拆分成两个部分相乘,并且它们的各个部分计算次数都是最少的,那么环环相扣,是不是就能套出来最优解了。
答案是肯定的。
但又出现了新的问题,刚刚我们是假设已经知道了将连乘序列从那个部分拆分成子序列,但实际不知道,那么寻找括号的添加点即k的取值就是我们这个问题的重点。
2.最优子结构的递推式
定义为计算的最少计算次数,所以这个最小计算次数的问题可以用下列式子递归表出:
递推式解释:(当时老师讲这部分的时候,我的疑惑是为什么要加上p那一部分。)
是整个连乘序列的子序列,我们可以将它看做成一个独立的连乘序列,再次寻找添加括号的位置,那么就会再次将这个连乘序列划分为两个子序列(即最优子结构),通过这种不断递归的方式,我们就可以得到矩阵连乘问题的最优解。
当时,不发生乘法运算,所以计算次数为0.
当时,的最少计算次数为将该连乘序列自k处拆分的两个子序列的最少计算次数的和()加上生成的两个临时矩阵(两个子序列连乘之后得到的新的矩阵)相乘的计算次数()。
对于k值的选取,这个时候就需要我们用蛮力干活——穷举。我们只需要穷举次,就可以得到k的取值。
那么,现在所有的问题就都解决啦。
3.使用“自底向上”的方式获得最优解
当我们使用时,我们首先要计算出和的值。对于被分割的子序列长度都应该小于等于,因此我们的算法以矩阵链长度增长的顺序计算。
关于这句话的解释:假设我们需要计算,我们就需要要获得和、和、和的值。那要获得的值,就又要获得和、和的值。要获得的值,又需要获得和的值。
我们来看这张图,我们需要得到的值,但是不能直接得到,而是从最底部开始(把这张图翻转一下) 先得到的值,通过递归得到的值。
这就是使用自底向上得到最优解。
4.使用“动态规划“算法的基本要素
存在递归关系,我的理解是,全局最优解需要拆分的部分得到局部的最优解。局部的最优解又可以继续拆分,还是需要更小部分的最优解。它们之间存在一个依赖关系。也就是说,从最开始我们就得把路选对,获得局部的最优解,之后才能获得全局的最优解。
(偏个题,大家不要把这种寻求全局最优解的方法映射到我们的人生中,我总是时常会悔恨之前所做的决定不够好,选择的路径不够正确,但是要记得,我们当时所做的决定已经是在以往的那个空间那个时刻最优的了,虽然可能现在看来很蠢,但是不要责备自己,继续向前走就好啦。)
通过上述,其实我们也能了解最优子结构的性质:当前最优解包含着其他子问题的最优解。
而最优子结构是使用动态规划算法求解的前提。
5.例子
假设我们现在有4个矩阵组成的一个矩阵链,=5,=4,=6,=2,=7。
计算结果如上,过程不再赘述,套公式比大小得最值。
6.代码
//没用递归
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];
}
}
#include<iostream>
using namespace std;
#define N 7
//计算最优值
void MatrixChain(int *p,int n,int m[][N],int s[][N]){
for(int i=1;i<=n;i++){ //矩阵链中只有一个矩阵时,次数为0
m[i][i]=0;
}
for(int r=2;r<=n;r++){
for(int i=1;i<=n-r+1;i++){
int j=i+r-1; //矩阵链的末尾矩阵,注意r-1,因为矩阵链为2时,实际是往右+1
m[i][j]=m[i+1][j]+p[i-1]*p[i]*p[j];
s[i][j]=i;
for(int k=i+1;k < j;k++){ //这里面将断链点从i+1开始,可以断链的点直到j-1为止
int t = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
if(t<m[i][j]){
m[i][j] = t;
s[i][j] = k;
}
}
}
}
}
//构造最优解
void Traceback(int i,int j,int s[][N]){
if(i==j) //回归条件
{
cout<<"A"<<i;
}
else //按照最佳断点一分为二,接着继续递归
{
cout<<"(";
Traceback(i,s[i][j],s);
Traceback(s[i][j]+1,j,s);
cout<<")";
}
}
int main(){
int p[N]={30,35,15,5,10,20,25};
int m[N][N],s[N][N];
MatrixChain(p,N-1,m,s); //N-1因为只有六个矩阵
cout<<"矩阵的最佳乘积方式为: ";
Traceback(1,6,s);
return 0;
}