动态规划算法

基本思想

子问题:动态规划同样是把大问题拆分为小问题,小问题继续拆,直到没得拆(极其容易获得结果)。但这些拆开的小问题之间不是互相独立的,也就是同一层次的子问题之间存在结果的传递利用。

  • 同一层次:即由同一个大问题拆分得到的多个小问题;
  • 互相独立:排序如[2,1,4,3,6,5]拆分为A:[2,1,4]和B:[3,6,5],子问题A和B的排序互不影响,各排各的;
  • 结果传递:如0-1背包,容量C里判断放入(a,b,c,d,e)中的哪些,拆成两部分(a)和(b,c,d,e),拿第二部分来看,选择放入谁必须先知道第一部分(a)是否放入了,因为它的结果直接影响背包所剩容量。

记录思想:上面的分析知道,子问题之间存在信息传递,所以我们要准备一个表,把遇到的子问题结果都记录下来,当别的问题用到其它子问题结果时,直接查表就可以了。

重要性质

最优子结构:某问题的最优解包含了其子问题的最优解。即大问题的最优解,可以通过所有子问题的最优解“合并”来获取。

重叠子问题:即某个子问题的继续拆分过程中,存在拆出来的子问题不是新问题(在其他的子问题上已经拆出来过),这样就存在了重复计算。也是为什么要用表记录的原因。

算法步骤

  1. 找出最优解的性质,并刻画其结构特征;——>什么样的解是最优解
  2. 递归地定义最优值;——>注意这里是递归的“定义”
  3. 以自底向上的方式计算出最优值;——>循环的方式从最小的子问题开始逐渐合并为所求是大问题
  4. 根据计算最优值时的信息,构造最优解;——>根据题目需要判断是否进行该步骤

例:矩阵连乘

问题描述:给定n个连乘矩阵(A1*A2*A3*...*An),求怎样加括号组合才能使整个的数乘次数最少。

分析:数乘次数只跟每个矩阵的维数有关,我们不关心具体数值,如A1:10×100、A2:100×5、A3:5×50。采用方式((A1A2)A3)得10*100*5+10*5*50=7500次,而方式(A1(A2A3))得10*5*50+10*100*50=75000次,10倍之差。

最优解结构:另A[i:j]表示连乘式\left ( A_{i}A_{i+1}...A_{j}} \right ),假设把该1~n的连乘矩阵链从某一处k切开,分别得到1~k子链和(k+1)~n子链,那么A[1:n]的计算量=A[1:k]的计算量+A[k+1:n]的计算量+两个子链结果相乘的计算量。利用反证法可以证明矩阵连乘具有最优子结构性质。A[1:n]的最优次序一定包含A[1:k]和A[k+1:n]的最优次序。

递归定义:以m[i][j]表示A[i:j]链的最少数乘次数,原问题就是求m[1][n]

m[i][j]=\left\{\begin{matrix} 0 &i=j \\ min{m[i][k]+m[k+1][j]+p_{i-1}p_{k}p_{j}} & i<j,i\leqslant k<j \end{matrix}\right.

自底向上的循环求解:见函数matrixChain

根据第三步中的信息构造最优解,也即得到最佳的加括号方式:见函数traceback

以A1:30×35、A2:35×15、A3:15×5、A4:5×10、A5:10×20、A6:20×25为例:

import java.util.Arrays;

/**
* @author sun
* 创建时间:2018年8月2日上午11:28:36
*/
public class 矩阵连成 {
	public static void main(String[] args){
		int[][] a = {{1,2},{3,4}};
		int[][] b = {{1,1},{1,1}};
		int[][] c = matrixMultiply(a,b);
		System.out.println(Arrays.toString(c[0])+"\n"+Arrays.toString(c[1]));
		int[] p = {30,35,15,5,10,20,25};
		int[][] m = new int[7][7];//为了对应字母A1,A2...舍去第0行,第0列
		int[][] s = new int[7][7];
		matrixChain(p,m,s);
		traceback(s,1,6);
	}
	//2个矩阵的乘积算法
	public static int[][] matrixMultiply(int[][] a,int[][] b){	
		int ra = a.length,ca = a[0].length;
		int rb = b.length,cb = b[0].length;
		int[][] c = new int[ra][cb];//c用来存放乘积结果
		if(ca!=rb)//A×B要求矩阵A的列数等于矩阵B的行数
			throw new IllegalArgumentException("矩阵不可乘");
		for(int i=0;i<ra;i++){
			for(int j=0;j<cb;j++){
				int sum = a[i][0]*b[0][j];
				for(int k=1;k<ca;k++){
					sum+=a[i][k]*b[k][j];
				}
				c[i][j] = sum;
			}
		}
		return c;
	}
	
	//递归求解
	public static void matrixChain(int []p,int[][] m,int[][] s){
		//p记录所有矩阵的列数,第一个包括行,m记录当前分段的最少乘次数,s记录断开位置
		int n=p.length-1;
		for(int i=0;i<n;i++) m[i][i]=0;//对角线为0,表示分段只有一个矩阵,无需计算
		for(int r=2;r<=n;r++){//分段的长度从2到总长n
			for(int i=1;i<=n-r+1;i++){//起始位置从1
				int j=i+r-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++){
					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;
					}
				}
			}
		}
	}
	
	//构造最优解
	public static void traceback(int[][] s,int i,int j){
		if(i==j)return;
		traceback(s,i,s[i][j]);
		traceback(s,s[i][j]+1,j);
		System.out.println("Multiply A"+i+","+s[i][j]+"and A"+(s[i][j]+1)+","+j);
	}
}

 参考

[1] 算法设计与分析(第2版) 王晓东

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值