java dp 动态规划之矩阵连乘问题

问题描述

给定n个矩阵{A1A2…An},其中Ai和Ai+1是可乘的,考察这n个矩阵的连乘积A1A2…An。由于矩阵的乘法满足结合律,故计算矩阵的连乘积有许多不同的计算次序,而不同的计算次序,所需要计算的连乘次数也是不同的,求解连乘次数最少的矩阵连乘最优次序。
举例说明矩阵结合方式对数乘次数的影响:
矩阵连乘积A1A2A3,3个矩阵的维数分别为10100,1005和550,连乘时加括号的方式有:
((A1
A2)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];
		
	}

}

输入和输出截图

在这里插入图片描述
这道题目的解析和解题是参考其他博主动态规划算法——矩阵连乘问题,以及自己的感悟写的,如果对你有帮助请给个赞哈

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值