问题描述
给定n个矩阵{A1A2…An},其中Ai和Ai+1是可乘的,考察这n个矩阵的连乘积A1A2…An。由于矩阵的乘法满足结合律,故计算矩阵的连乘积有许多不同的计算次序,而不同的计算次序,所需要计算的连乘次数也是不同的,求解连乘次数最少的矩阵连乘最优次序。
举例说明矩阵结合方式对数乘次数的影响:
矩阵连乘积A1A2A3,3个矩阵的维数分别为10100,1005和550,连乘时加括号的方式有:
((A1A2)A3) 数乘次数:101005+10550=7500
(A1(A2A3)) 数乘次数:100550+10100*50=75000
由此可见,不同的加括号方式计算次数差距竟然如此之大!所以要想快速解决问题就需要找到最优次序。
问题分析
我们记A[i:j] 是矩阵Ai到Aj之间的连乘,我们需要计算A[i:j]的最优数乘次数,可以将A[i:j] 拆分成A[i:k] 和A[k+1,j] 两部分 然后分别计算这两部分的最优数乘次数,再将次两部分来构成A[i,j]的最优数乘次数,而A[i,k]和A[k+1,j]这两部分的结果就需要继续划分,求更小部分的最优数乘次数,所以是类似递归问题
每一个最优数乘次数的公式如下:
A[i:j]的数乘次数 = A[i:k]的数乘次数 + A[k+1:j]的数乘次数 + d[i-1]d[k]d[j];
这里需要解释下d 数组的意思 :d数组是存放矩阵的维度的 ,举例说明:当我们输出5个矩阵时,各自的维度分别为 35 510 108 82 2*4 ,按照一般的思想是输入这10个数字,但是我们会发现遇到的题目是 输入 3 5 10 8 2 4 ,这样的,就是前一个矩阵的列和后一个矩阵的行是一样的,才可以相乘嘛,所以我们使用一个d数组来接收存放这些数据,大伙可能会疑惑以上公式的d[i-1]这个地方,因为我们是从0下标开始存放的,然后A[i:j] 这里的i和j是表示第几个矩阵的,从1开始,举个栗子,比如i==1,是不是就指的第一个矩阵,那d[i-1] 是不是就是d[0] = 3,即第一个矩阵的行,是不难理解的
建立最优值的递归关系
对于矩阵连乘积的最优计算次序问题,设计算A[i:j]所需的最少数乘次数为dp[i][j],则原问题的最优值为dp[1][n].
当i=j时,A[i:j]为一个矩阵,无需计算,因此dp[i][i] = 0, i = 1,2,3…n
当i<j时,可利用最优子结构的性质来计算dp[i][j],在计算A[i:j]时,将矩阵连乘分成两个子链,A[i:k]和A[k+1:j],其中i <= k < j,因为其子链也是最优次序,所以:
dp[i][j] = dp[i][k] + dp[k+1][j] + d[i-1]d[k]d[j];
计算最优值
这里你是否有一个疑惑,就是k究竟要取多少,我们可以把这个题目想象成一条长线,我们每一次分割(选择的断点都是最优的),每一个分隔就相当于给两部分各自加了括号,优先计算, 而第一次分隔的最优位置我们记为b1,b1是由我们的第二次分隔的最优位置决定的,即b2决定b1,那b2又是由第三次分割的最优位置决定的,所以是一层套一层,所以我们需要从底层算起,所以这个时候就需要我们来控制子问题的规模了,我们使用一个r变量来控制,请先看下图:
迭代的执行过程,R控制子问题的规模,直至达原问题
当R=2时,迭代计算出:
dp[1:2]=dp[1:1]+dp[2:2}+d[0]*d[1]*d[2];
dp[2:3]=dp[2:2]+dp[3:3]+d[1]*d[2]*d[3];
dp[3:4]=dp[3:3]+dp[4][4]+d[2]*d[3]*d[4];
dp[4:5]=dp[4:4]+dp[5][5]+d[3]*d[4]*d[5];
dp[5:6]=dp[5][5]+dp[6][6]+d[4]*d[5]*d[6];
当R=3时,迭代计算出:
dp[1:3]=min(dp[1:1]+dp[2:3]+d[0]*d[1]*d[3],dp[1:2]+dp[3:3]+d[0]*d[2]*d[3]);
dp[2:4]=min(dp[2:2]+dp[3:4]+d[1]*d[2]*d[4],dp[2:3]+dp[4:4]+d[1]*d[3]*d[4]);
......
dp[4:6]=min(dp[4:4]+dp[5:6]+d[3]*d[4]*d[6],dp[4:5]+dp[6:6]+d[3]*d[5]*d[6]);
......
我个人的理解是这样的:比如规模R=3时,一个小部分为三个矩阵,当我们进行第一个部分的最优值dp[1][3]的计算时,断点的取值可以为1或2,当断点为1时,需要计算dp[1][1] + dp[2][3] +d[0]*d[1]*d[3]
而这里的dp[1][1] 是已经初始化好的, dp[2][3] 则在R=1时进行计算了,所以一层套一层,可以理解为下一层计算的结果给上一层使用
代码
import java.util.Scanner;
public class Main {
static int n;//矩阵的个数
static int[] d;//存放连乘矩阵的维度 dimension
static int[][] dp;//存放连乘的最优值 dp[i][j]:矩阵i,矩阵i+1,.....矩阵j的最优乘积次数
static int[][] b;//存放最优值dp[i][j]对应的最优断开位置 break
public static void main(String[] args) {
//初始化参数
init();
//获取结果
getResult();
//打印乘积顺序
System.out.println("乘积顺序:");
print(1,n);
}
private static void print(int i,int j) {
if(i==j) {
System.out.print("A["+i+"]");
return;
}
System.out.print("(");
print(i,b[i][j]);
print(b[i][j]+1,j);
System.out.print(")");
}
private static void getResult() {
//每个矩阵与自己没有连乘次数,为0
for (int i = 1; i <= n; i++) {
dp[i][i] = 0;
}
//控制子问题的规模,从小规模到大规模,则至原问题
//使得计算不会重复,大规模可以利用已计算好的小规模的结果继续计算
for (int r = 2; r <= n; r++) {
for (int i = 1; i <= n-r+1; i++) {
int j = i+r-1;//i和j是规模中的每个小部分的范围
//初始化最优值,此时断点是i,即小范围(i,j)的第一个矩阵
dp[i][j] = dp[i+1][j] + d[i-1]*d[i]*d[j];
b[i][j] = i;
//断点的范围是从小范围(i,j)中的i+1开始
for (int k = i+1; k < j; k++) {
int temp = dp[i][k] + dp[k+1][j] + d[i-1]*d[k]*d[j];
//如果该断点对应的连乘次数更优,则替代
if(temp<dp[i][j]){
dp[i][j] = temp;
b[i][j] = k;
}
}
}
}
System.out.println("最优乘积次数为:"+dp[1][n]);
}
private static void init() {
Scanner sc = new Scanner(System.in);
n = sc.nextInt();
d = new int[n+1];
for (int i = 0; i <= n; i++) {
d[i] = sc.nextInt();
}
dp = new int[n+1][n+1];
b = new int[n+1][n+1];
}
}
输入和输出截图
这道题目的解析和解题是参考其他博主动态规划算法——矩阵连乘问题,以及自己的感悟写的,如果对你有帮助请给个赞哈