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

原创 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));

	}

}



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

动态规划算法经典案例

动态规划的关键点: 1、最优化原理,也就是最有子结构性质。这指的是一个最优化策略具有这样的性质,无论过去状态和决策如何,对前面的决策所形成的状态而言,余下的决策必须构成最优策略,简单来说就是一个最优化...
  • uestclr
  • uestclr
  • 2016年02月29日 20:14
  • 21500

求最大利益的算法

题目:外汇交易可以通过兑换不国家的货币以赚取汇率差,比如1美元兑换100日元时购入1000美元,然后汇率变动到1美元兑换108美元时卖出,这时的收益是(108-100)*1000=8000日元 现在...
  • qiumei1101381170
  • qiumei1101381170
  • 2016年11月29日 18:55
  • 269

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

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

动态规划-股票买卖总结

转载自:http://liangjiabin.com/blog/2015/04/leetcode-best-time-to-buy-and-sell-stock.html Best Time to ...
  • u014609111
  • u014609111
  • 2016年12月07日 17:45
  • 1174

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

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

贪心算法 Problem N 1013 求最大盈利

STL Problem N  Problem ID:1013 简单题意:MS公司每5个月公布一次这5个月的利润,也就是说这一年中共公布了8次(5月一次、6月一次····12月一次),且这8次恰...
  • q1169917
  • q1169917
  • 2016年03月28日 16:36
  • 615

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

题目:Say you have an array for which the ith element is the price of a given stock on day i.If you wer...
  • diu_brother
  • diu_brother
  • 2016年04月23日 16:07
  • 1384

【算法之动态规划(四)】动态规划笔试例题详解

转自:http://www.cnblogs.com/kkgreen/archive/2011/06/26/2090702.html 终于来到了算法设计思想中最有趣的这部分,在去年的g...
  • cangchen
  • cangchen
  • 2015年04月14日 17:32
  • 5324

几个经典的动态规划的算法

列举几个动态规划经典的算法
  • jpbj_zb
  • jpbj_zb
  • 2016年05月05日 09:25
  • 5076

动态规划算法理论阐述及一个小实例

动态规划问题:用来解决最优化问题 基本概念:将一个问题,分解成多个阶段来解决,每一个阶段的决策都依赖于当前的状态,决策过后状态又发生了转移,这种多阶段来解决最优化问题的过程就是动态规划。 基本思想...
  • leo115
  • leo115
  • 2012年10月16日 13:13
  • 8166
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:动态规划算法举例解析(最大收益和最小损失选择)
举报原因:
原因补充:

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