动态规划算法举例解析(最大收益和最小损失选择)

原创 2016年10月28日 21:14:49

在说动态规划的例子之前,先说明一下动态规划和分治算法的区别

虽然两者都是通过组合子问题的解来求解原问题但是分治方法将问题划分为互不相交的子问题,递归的求解子问题再将它们的解组合起来求出原问题的解。

而动态规划算法应用于子问题重叠的情况,即不同的子问题具有公共的子子问题,在这种情况下,分治算法会做许多不必要的工作,它会重复的求解这些子问题,尽管这些子问题都曾经计算过。而动态规划算法就聪明了很多,它对每个子子问题都只求解一次。将其解保存在一个数据结构中,从而遇到曾经计算过的子子问题并不是再计算而是从这个数据结构直接取结果即可。

学习动态规划算法,首先要了解最优子结构这个概念

如果一个问题的最优解包含其子问题的最优解,我们就称此问题具有最优子结构性质,一个问题如果可以应用动态规划算法,那么它必然具有最优子结构。在使用动态规划方法时,我们要利用子问题的最优解来构造原问题的最优解。

下面举一个例子,其实也是书上的例子

例子1,如何切割钢条,长度为n的钢条,可以选择切割成多段也可以选择不切割。这完全要依靠收益来看。下面这个表示长度为i的钢条所对应的价格表

长度i       1         2        3         4          5           6          7           8         9      10

价格pi      1        5       8         9          10          17       17         20        24     30

比如一个长度为4的钢条,它的切割情况就有

方案                    收益

(1,3)---------- 9

(2,2)-----------10

(3,1)-----------9

(1,1,2)---------7

(1,2,1)---------7

(2,1,1)---------7

(1,1,1,1)-------4

(4,0)-----------9

这些方案中将长度为4的钢条切割成(2,2)的收益最大,于是企业就会切割了卖。。。。

首先看这个问题满足使用DP的最优子结构的性质。

我们第一次选择切割钢条的问题,剩下的钢条则是与原问题相似的子问题。

其实原问题的最优解就是由第一次切割得到的两段钢条的最优切割方案组成的,符合最优子结构。这里为了尽可能减少子问题空间的大小,切割后剩下的钢条构成一个子问题。剩下的钢条可能还会被切割,也可能不被切割,这里我们不管,我们只要求得在当前切割方案下这个子问题的最优解,与当前切割方案组合最优则能得到原问题的最优切割方案。

对于第一步的切割,你假定已经知道第一部应该如何切割得到最优解了,并且你知道第一步切割会产生怎么的子问题。

可以利用剪切粘贴的思想来验证钢条切割问题用动态规划的思想来进行,假设子问题的解不是它自身的最优解,那么我们可以从原问题中把子问题的解减掉,将子问题一个更优的解放到原问题的最优解中,那么将形成一个比原问题最优解更优的方法,这与全问题最优解是矛盾的,所以如果要想达到原问题的最优解,必须子问题步步是最优的才是可行的。

将一个长度为4的钢条切割,仅仅使用了一个子问题,(长度为4-i钢条的切割),但i的值可能取0,1,2,3,就是第一次要切割的位置在长度为4的钢条上的位置,下面则是一棵递归调用树。可以看出要求长度4的钢条最优切割方案,我们需要求解长度为3、2的切割方案,对应的则是i=1,i=2,这是剩余各条需要切割的,节点为1和0的部分不需要切割,所以可以看到它的子树没有或者是0.


画框的部分代表如果不采取措施就会重复计算的部分。朴素的自顶向下的动态规划会造成重复计算的问题,时间复杂度高,加入了备忘机制的自顶向下的动态规划可以避免这一问题,将计算出的结果放在列表中,如果还需要这部分值则直接取即可。另外自底向上的动态规划可以更好的达到备忘机制的效果,想得到4的切割方案,先计算出3的方案,想得到2的切割方案,先得到1的方案,直到0,而0是初始设置,这里长度为0的钢条自然是没有收益的。用了就是先把小粒度的计算出来在得到大粒度的思想。下面针对这三种方案分别实现

package DP;

public class Cut_Rod {
//p为存放了不同长度对应的价格,而n则是要判断长度n的钢条最好的切割

	private static int[] array;
	private static int[] srray;
	public static int cut_Rod(int[]p,int n){
		//自顶向下朴素的动态规划
		if(n==0){
			return 0;
		}
		int q = Integer.MIN_VALUE;
		for(int i=1;i<=n;i++){
			q=max(q,p[i]+cut_Rod(p,n-i));
		}
		return q;
	}
	public  static int memoized_cut_road(int[]p,int n){
		//加入了备忘录的自顶向下的动态规划
		int[] array = new int[n+1];
		for(int i=0;i<array.length;i++){
			array[i] = Integer.MIN_VALUE;
		}
		return memorized_cut_road_autx(p,n,array);
	}
	public static int memorized_cut_road_autx(int[]p,int n,int[]array){
		
		if(array[n]>0){
			//说明已经更新完,可以直接取值
			return array[n];
			
		}
		int q;
		if(n==0){
			q = 0;
		}else{
			q = Integer.MIN_VALUE;
			for(int i=1;i<=n;i++){
				q = max(q,p[i]+memorized_cut_road_autx(p,n-i,array));
			}
		}
		return q;
		
	}
	public static int bottom_up_road(int[]p,int n){
//自底向上的动态规划算法,先求解出小问题,逐步向上求解
 
                int[] array = new int[n+1];
		array[0] = 0;
		int q = Integer.MIN_VALUE; 
		for(int j=1;j<=n;j++){
			q = Integer.MIN_VALUE;
			for(int i=1;i<=j;i++){
				q = max(q,p[i]+array[j-i]);
			}
			array[j] = q;
			
		}
		return array[n];
	}
	public static void extended_bottom_up_rod(int[]p,int n){
               //不仅求出切割的最大效益,并且保存切割方案,array保存的是子问题的解避免重复计算,而sarry保存的则是每步的最优切割方案
                array = new int[n+1];
		srray = new int[n+1];
		array[0]=0;
		int q = Integer.MIN_VALUE;
		for(int i=1;i<=n;i++){
			q = Integer.MIN_VALUE;
			for(int j=1;j<=i;j++){
				if(q<p[j]+array[i-j]){
					q = p[j]+array[i-j];
					srray[i] = j;
				}
			}
			array[i] = q;
		}
	}
	public  static void print_road_solution(int[]p,int n){
		extended_bottom_up_rod(p,n);
		/*for(int i=1;i<=n;i++){
			System.out.println(srray[i]);
		}
		System.out.print("-------");*/
		while(n>0){
			System.out.print(srray[n]);
			 n = n-srray[n];
		}
		
		
	}
	static int max(int n1,int n2){
		return n1>n2?n1:n2;
	}
	public static void main(String[] args) {
		int[] p={0,1,5,8,9,10,17,17,20,24,30};
		/*System.out.print(cut_Rod(p,4));*/
/*		System.out.print(memoized_cut_road(p,4));
		System.out.print(bottom_up_road(p,4));*/
		print_road_solution(p,9);

	}

}

例子2,矩阵链乘法,给定一个n个矩阵的序列<A1,A2.......An>,我们计算矩阵链乘积A1*A2*....An,用加括号的方式来明确计算次序,然后利用标准的矩阵相乘算法进行计算,使得计算量最小,这里的计算量用标量乘法的次数来表示作为计算的代价。

比如A1为10*100,A2为100*5,,A3为5*50,(A1*A2)*A3的计算量为10*100*5+10*5*50=7500,A1*(A2*A3)的计算量为10*100*50+100*5*50=75000,显然按照第一种计算顺序的计算代价更小一些,这里就是在这样的背景下提出了利用三种方式(自顶向下、加入备忘机制、自下向上)来分别计算的。

package DP;

public class Matrix_Multiply {


	public static int[][]m;
	public static int[][]s;
	public static int recursive_Matrix_Chain(int[]p,int i,int j){
		if(i==j){
			return 0;
		}
		int q = Integer.MIN_VALUE;
		int[][] m = new int[j+1][j+1];
		m[i][j] = Integer.MAX_VALUE;
		for(int k=i;k<=j-1;k++){
			q = recursive_Matrix_Chain(p,i,k)+recursive_Matrix_Chain(p,k+1,j)+p[i-1]*p[k]*p[j];
			if(q<m[i][j])
				m[i][j] = q;
		}
		
		return m[i][j];
			
		
	}
	//加入了备忘机制d
	public static int memoized_martix_chain(int[] p){
		int n = p.length-1;
		int q = Integer.MIN_VALUE;
		int[][] m = new int[n+1][n+1];
		for(int i=1;i<=n;i++){
			for(int j=i;j<=n;j++){
				//可见是矩阵的上三角部分
				m[i][j] = Integer.MAX_VALUE;
			}
		}
		return lookup_Chain(m,p,1,n);
		
	}
	public static int lookup_Chain(int[][]m,int[]p,int i,int j){
		int q = Integer.MIN_VALUE;
		if(m[i][j]<Integer.MAX_VALUE){
			//只说明一种情况就是所求的值之前已经计算过,不需要重复计算,直接取值即可
			return m[i][j];
		}
		if(i==j){
			m[i][j] = 0;
		}
		else{
			for(int k=i;k<=j-1;k++){
			q = lookup_Chain(m,p,i,k)+lookup_Chain(m,p,k+1,j)+p[i-1]*p[k]*p[j];
			if(q<m[i][j]){
				m[i][j] = q;
			}
		}
		
	}
		return m[i][j];
		}
	public static void matrix_chain_Order(int[] p){
		int n = p.length-1;
		m = new int[n+1][n+1];
		s = new int[n+1][n+1];
		for(int i=1;i<=n;i++){
			m[i][i] = 0;
		}
		for(int l=2;l<=n;l++){
			for(int i=1;i<=n-l+1;i++){
				int j = i+l-1;
				m[i][j] = Integer.MAX_VALUE;
				for(int k=i;k<=j-1;k++){
					int q = m[i][k]+m[k+1][j]+p[i-1]*p[k]*p[j];
					if(q<m[i][j]){
						m[i][j] = q;
						s[i][j] = k;
					}
				}
			}
		}
	}
	public static void print_matrix(int[] p,int i,int j){
		matrix_chain_Order(p);
		print_optimal_parens(i,j);
		
	}
	public static void print_optimal_parens(int i,int j){
		if(i==j){
			System.out.print("A"+i);
		}else{
			System.out.print("(");
			print_optimal_parens(i,s[i][j]);
			print_optimal_parens(s[i][j]+1,j);
			System.out.print(")");
		}
	}
	
	public static void main(String[] args) {
	
		int[] p ={30,35,15,5,10,20,25};
		/*System.out.print(recursive_Matrix_Chain(p,1,6));*/
		/*print_matrix(p,1,6);*/
		System.out.print(memoized_martix_chain(p));

	}

}



版权声明:本文为博主原创文章,未经博主允许不得转载。

相关文章推荐

算法导论之求股票最大收益

int a[] = { 1, 23, 4, 3, 4, 3, 2, 46, 7, 5, 5 };上述数组可看做是一支几天连续的股票价格。 我们要做的是用最小的时间复杂度来求出最大化收益 ...

java 动态规划判断股票最大盈利问题

题目:Say you have an array for which the ith element is the price of a given stock on day i.If you wer...

MySql读写分离,及数据库高可用负载均衡的实现

今天主要讲一下关于mysql读写分离rw-splitting的实现方式,以及简单的mysql高可用(HA)和负载均衡(LB)。我会提到读写分离的原理,实现方式和一些注意点。然后就是介绍一下官方提供的m...

使用superobject中的TSuperObjectHelper序列化和反序列化对象

最近在研究把对象进行序列化和反序列化,看过网上很多办法, 如 1.TStream.ReadComponent和TStream.WriteComponent的方法;2.XML和对象的转换。3.Json...

利用动态规划解决实际问题之多次兑换获取最大外汇收益

动态规划问题之多次兑换货币获取最大收益实例

小米笔试-股票最大收益(动态规划)

题目: 风口之下,猪都能飞。当今中国股市牛市,真可谓“错过等七年”。 给你一个回顾历史的机会,已知一支股票连续n天的价格走势,以长度为n的整数数组表示,数组中第i个元素(prices[i])代表该股...

【动态规划】UVa 1331 最大面积最小三角形剖分

将一个多边形用它不相交的对角线将它分成若干个三角形,使得最大的三角形面积最小,求最大三角形的面积。如图是一个六边形的几种剖分: 思路:记由点u,u+1,…,v-1,v(u< v)组成的多边形为F(i...

算法训练 最大的算式(动态规划)

问题描述   题目很简单,给出N个数字,不改变它们的相对位置,在中间加入K个乘号和N-K-1个加号,(括号随便加)使最终结果尽量大。因为乘号和加号一共就是N-1个了,所以恰好每两个相邻数字之间都有一...
  • f_zyj
  • f_zyj
  • 2016年04月08日 20:47
  • 1760

算法篇-5-动态规划-01背包&流水作业调度&&整数线性规划&树的最大连通分支

本系列所有代码https://github.com/YIWANFENG/Algorithm-github 0-1背包 给定N中物品和一个背包。物品i的重量是Wi,其价值位Vi ,背包的容量为C。问应该...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:动态规划算法举例解析(最大收益和最小损失选择)
举报原因:
原因补充:

(最多只允许输入30个字)