动态规划(详解矩阵连乘 案例+Java代码实现)

动态规划

History does not occur again

算法总体思想

  • 与分治算法类似
  • 子问题往往不是互相独立的, (分治会重复计算)
  • 保存已解决的子问题的答案,需要时找出即可(空间换时间)

基本步骤

  • 找出最优解的性质并刻划其结构特征
  • 递归地定义最优值
  • 以自底向上的方式计算出最优值(递推)
  • 根据计算最优值时得到的信息构造最优解

矩阵连乘问题

问题描述

  • 给定n个矩阵{A1, A2,…, An}, 其中Ai 与 Ai+1 是可乘的, i = 1, 2, …, n-1
  • 如何确定连乘积的计算次序,使得依次次序计算矩阵连乘积所需要的数乘次数最少

分析

  • 矩阵乘法满足结合律
    ->矩阵乘法可以有不同的计算次序

  • 矩阵连乘的计算次序可以用加括号的方式来确定
    ->若矩阵连乘已完全加括号,则其计算次序完全确定

  • 完全加括号的矩阵连乘可递归定义为:

    1. 单个矩阵是完全加括号的;
    2. 矩阵连乘积A是完全加括号的,则A可表示为2个完全加
      括号的矩阵连乘积B和C的乘积并加括号,即 A=(BC)。

例,有四个矩阵A,B,C,D,它们的维数分别是: A=50×10,B=10×40, C=40×30, D=30×5
连乘积ABCD共有五种完全加括号的方式

(A((BC)D)) 16000      (A(B(CD))) 10500
((AB)(CD)) 36000      (((AB)C)D) 87500
((A(BC))D) 34500

解决方法

  • 穷举法:列举出所有可能的计算次序,并计算出每一种计算次序相应需要的数乘次数,从中找到一种数乘次数最少的计算次序。

    • 复杂性分析: 用p(n)表示n个矩阵链乘的穷举法计算成本,如果将n个矩阵从第k和k+1出隔开,对两个子序列再分别加扩号,则可以得到下面递归式:

    很明显,指数级增长,此方法不太可行

  • 动态规划

    • 将矩阵连乘积AiAi+1…Aj简记为A[i:j] ,这里i≤j。考察计算A[i:j]的最优计算次序。设这个计算次序在矩阵Ak和 Ak+1之间将矩阵链断开,i≤k<j,则其相应完全加括号方式为(AiAi+1…Ak)(Ak+1Ak+2…Aj)
      -> A[i:j]的计算量:A[i:k]的计算量加上A[k+1:j]的计算量,再加上A[i:k]和A[k+1:j]相乘的计算量

具体步骤

  • 分析最优解的结构

    • 特征:计算A[i:j]的最优次序所包含的计算矩阵子链A[i:k]和A[k+1:j]的次序也是最优的

  • 建立递归关系

    • 设计算A[i:j],1≤i≤j≤n,所需要的最少数乘次数m[i,j],则原问题的最优值为m[1,n]

    k为断开位置

    m[i][j]实际是子问题最优解的解值,保存下来避免重复计算

    • 在递归计算时,许多子问题被重复计算多次。这也是该问题可用动态规划算法求解的又一显著特征
  • 计算最优值

    • 根据递归公式,对角线的值为0。其他值需要根据于断开位置k的值来得到,k ∈ \in [i,j),我们要遍历所有k,就要访问所求值的所有同一行左边的值和同一列下方的值。因此,在代码中我们可以使用自底向上、从左到右的计算顺序来依次填充,最终得到右上角的值。
  • 构造最优解

    • 前面我们已经讲数据记录在了数组中,直接查表即可构造最优解

案例

  • 求矩阵链A1A2A3A4的最优运算次序。其中矩阵Ai的大小为pi-1×pi。其中P(0) = 5,P(1) = 7,P(2) = 4,P(3) = 3,P(4) = 5

Java代码实现

package MatrixChain;

public class Array {
	
	/**
	 * 求解最优值
	 * @param p: 矩阵维数信息数组
	 * @param m: 存放最优值数组, 上三角形式
	 * @param s: 存放分割位置下标的数组
	 * @return 返回最优值
	 **/
	public static int matrixChain(int[] p, int[][] m, int[][] s) {
		int n = p.length - 1;
		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;
				// 先以i进行划分
				m[i][j] = m[i + 1][j] + p[i - 1] * p[i] * p[j];  // 求出Ai到Aj的连乘
				s[i][j] = i;  // 记录划分位置
				for (int k = i + 1; k < j; k++) {
					// 寻找是否有可优化的分割点
					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;
					}
				}
			}
		}
		return m[1][n];
	}
	
	/**
	 * 输出 A[i:j] 的最优计算次序
	 * @param i、j: 连乘矩阵下标
	 * @param s: 存放分割位置下标的数组
	 **/
	public static void traceback(int i, int j, int[][] s) {
		// 输出A[i:j] 的最优计算次序
		if (i == j) {
			// 递归出口
			System.out.print("A"+i);
			return;
		} else {
			System.out.print("(");
			// 递归输出左边
			traceback(i, s[i][j], s);
			// 递归输出右边
			traceback(s[i][j] + 1, j, s);
			System.out.print(")");
		}
	}
	
	public static void main(String[] args) {
		int[] p = new int[]{5, 7, 4, 3, 5};
		int[][] m = new int[p.length][p.length];
		int[][] s = new int[p.length][p.length];
		System.out.println("最优值为: "+matrixChain(p, m, s));
		traceback(1, p.length-1, s);
	}
}
最优值为: 264
((A1(A2A3))A4)
  • 46
    点赞
  • 158
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 4
    评论
Java动态代理是一种通过在运行时期间生成代理对象来实现对目标对象进行代理的技术。它可以在不修改目标对象的情况下,为目标对象提供额外的功能。 Java动态代理实现的核心是利用了Java的反射机制和动态生成类的技术。在动态代理中,我们需要定义一个代理类和一个实现了InvocationHandler接口的处理器类。 代理类是在运行时动态生成的类,它是目标对象的代理,它实现了与目标对象相同的接口,并且在方法调用时会通过InvocationHandler接口的实现类来处理方法的调用。InvocationHandler接口中只有一个方法invoke(Object proxy, Method method, Object[] args),这个方法就是用来处理方法调用的。 下面是一个简单的Java动态代理的示例代码: ``` import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class DynamicProxy implements InvocationHandler { private Object target; public DynamicProxy(Object target) { this.target = target; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("before method"); Object result = method.invoke(target, args); System.out.println("after method"); return result; } public static void main(String[] args) { RealObject realObject = new RealObject(); DynamicProxy dynamicProxy = new DynamicProxy(realObject); Interface proxyObject = (Interface) Proxy.newProxyInstance( Interface.class.getClassLoader(), new Class[] { Interface.class }, dynamicProxy); proxyObject.doSomething(); } } interface Interface { void doSomething(); } class RealObject implements Interface { public void doSomething() { System.out.println("RealObject doSomething"); } } ``` 在这个示例中,我们定义了一个DynamicProxy类作为代理处理器,它实现了InvocationHandler接口。在DynamicProxy类中,我们定义了一个Object类型的target属性,它表示目标对象。 在DynamicProxy类的invoke方法中,我们先输出了一句话“before method”,然后通过反射机制调用目标对象的方法,最后输出了一句话“after method”。 在DynamicProxy类的main方法中,我们首先创建了一个RealObject对象作为目标对象,然后创建了一个DynamicProxy对象,并将RealObject对象作为参数传递给DynamicProxy对象的构造方法。接着,我们通过Proxy.newProxyInstance方法动态生成了一个代理对象,并将DynamicProxy对象作为参数传递给它。最后,我们调用代理对象的doSomething方法。 当我们运行这个程序时,它会输出以下内容: ``` before method RealObject doSomething after method ``` 这表明,在代理对象调用doSomething方法时,它会先调用DynamicProxy类的invoke方法,在invoke方法中,我们将先输出一句话“before method”,然后调用目标对象的方法,最后输出一句话“after method”。 Java动态代理的优点是可以在运行时期间动态生成代理对象,不需要预先定义代理类,这样可以大大减少代码量。同时,Java动态代理也具有很好的灵活性,可以对不同的目标对象生成不同的代理对象,实现不同的处理逻辑。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

若尘

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值