开心的小明(java版) 动态规划

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qingmengwuhen1/article/details/82691567

浅谈DP算法(一)

                  ——如何用一维数组解决01背包问题

  DP算法(Dynamic Programming,俗称动态规划)是最经典算法之一.本笔记以耳熟能详的数塔问题为引子,深入讨论01背包的解决方法.

  首先,如下图所示,要求从顶层走到底层,若每一步只能走到相邻的结点,则经过的结点的数字之和最大是多少?

                                                                       

  这个问题,对于任意一个结点,直接选择数字大的子结点显然是不行的.以9为例,如果选择15,当前和24>21,但是15的两个子结点太小,24+6+18<21+10+18.由此可见,这样求出每阶段的部分最优解并不是全局最优解.另外,如果用蛮力算法,每条路径都遍历一次,那么层数为n时,路径总数呈指数级增长:

  显然这种方法的计算量太大,也不可取.那么此时用DP算法是行之有效的.具体思想为:从倒数第二层开始,一层一层向上遍历.倒数第二层第一个结点是2,如果路径经过2,那么肯定会选择数值较大左子结点19. 便用19+2=21代替原先的2. 同理18改为18+10=28,9改为19,5改为21. 这样倒数第二层就变成21 28 19 21四个结点,再将最后一层舍弃.这样一层层向上,直到第一层,选择第二层较大的那个结点加到9上面去,就得出了全局最优解.

 代码实现:如果数字塔为n层,开辟一个n*n的二维数组即可,非常简单,此处省略.

  对动态规划的思想有一个基本了解后,现总结出动态规划基本概念.不过在此之前,首先解释一下什么是多阶段决策问题,什么是状态.

  多阶段决策问题:一个问题可以分为若干个阶段,每个阶段都要做出决策;

  状态:每个阶段开始面临的自然状况或客观条件.在上例中每个阶段的状态就是到达当前结点的两个子结点的选择.

  动态规划是一个多阶段决策问题中,各个阶段采取的决策,依赖于当前状态,又引起状态的转移,一个决策序列就是在变化的状态中产生出来的,故有“动态”的含义,称这种解决多阶段决策最优化问题的方法为动态规划方法.

   适用DP算法的问题一般具备以下特点:

    (1)最优化原理(最优子结构性质)

    一个最优化策略的子策略也是最优的.举个例子就清楚了,如数字塔问题中,第二层到底层数值和最大的路径一定是从顶层到底层数值和最大的路径的子集;

    (2)无后向性(无后效性)

    通俗讲,某阶段状态一旦确定,就不受这个状态之后的决策的影响;

    (3)子问题重叠

    即子问题之间不独立.与前两个不同的是,这个特点不是必要的,如果不满足相比之下DP算法不具备优势.如果独立,分治算法策略将更简单方便.

  下面来看经典的01背包问题:

    把N个物品放入容量为V的背包里,第i个物品所需要的空间为need[i],同时它的价值为value[i],该如何放才能达到背包里的物品价值最大?

  分析:因为对于任何一个物品,都只有放或不放的选择,因而称之为01背包.用best(N,V)*表示N个物品放入容量为V的背包里的最大价值.则对于第N个物品,除非need[N]>V,都有放或不放两个选择:

  (1)如果将第N个物品放入背包中

best(N,V)=best(N-1,V-need[N])+value[N];  //即等于第N个物品的价值加上将N-1个物品放入容量为V-need[N]的背包里的最大价值;

  (2)如果不放

best(N,V)=best(N-1,V-need[N])+value[N];  //即等于第N个物品的价值加上将N-1个物品放入容量为V-need[N]的背包里的最大价值;

  这样我们重复上述步骤,直至N和V减小到0,可以得到对于其中任何一个物品i (1<=i<=N),当前状态背包剩余容量为j (0<=j<=V),都有

if(j<need[i])

    best(i,j)=best(i-1,j);

else

    best(i,j)=MAX{best(i-1,j-need[i])+value[i],best(i-1,j)};

  这就是从第i阶段到第i-1阶段的状态转移规律,程序员为了把逼格提高,起了一个术语——状态转移方程.

  而当i=0时**,只需设置一下边界值best(0,j)=0.这样,求解best(N,V)这个看起来很复杂又无从下手的问题,就变成了从i=0时的best(0,j)=0逐渐到i=N时的best(N,j).

  *best(N,V)只是物品、背包容量和价值三者之间的关系表示,千万不要纠结它为什么这么表示,到底什么意思,里面又是如何根据N,V来得到价值的;

  **本来i=0是没有意义的,因为是从第N个物品逐渐推导到第1个物品,设置i=0时的best(i,j)只是为了满足数学上的计算;

下面这题目是经典的DP算法背包问题的变种:

小明今天很开心,家里购置的新房就要领钥匙了,新房里有一间他自己专用的很宽敞的房间。更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”。今天一早小明就开始做预算,但是他想买的东西太多了,肯定会超过妈妈限定的N元。

于是,他把每件物品规定了一个重要度,分为5等:用整数1~5表示,第5等最重要。他还从因特网上查到了每件物品的价格(都是整数元)。他希望在不超过N元(可以等于N元)的前提下,使每件物品的价格与重要度的乘积的总和最大。

设第j件物品的价格为v[j],重要度为w[j],共选中了k件物品,编号依次为j1,j2,……,jk,则所求的总和为:

v[j1]*w[j1]+v[j2]*w[j2]+ …+v[jk]*w[jk]。(其中*为乘号)

请你帮助小明设计一个满足要求的购物单。

实现代码如下:

package huawei;

public class Demo {
	/*
	 * 功能:
	 * 
	 * 输入参数:a为二维数组,该二维数组第0行的两个数分别表示:总钱数<30000,和希望购买物品的个数<25;
	 * 该数组从第1行到第m行(1<=j<=m)中给出了编号为j的物品的基本数据,每行有2个非负整数,
	 * 表示该物品的价格(<=10000)和该物品的重要度(1~5)。
	 * 
	 * GetResult表示不超过总钱数的物品的价格与重要度乘积的总和的最大值(<100000000)。
	 * 
	 * 不需做入参检查,测试用例可以保证~
	 * 
	 * 例如:4000 8(第0行) 821 3 (第1行) 422 5 458 5 500 3 200 2 430 4 530 3 239 3
	 * 
	 * 则表示 总钱数为4000,希望购买物品个数为8个,因此从第1行到第8行表示编号为j的物品的价格及物品的重要度。
	 * 
	 * 
	 * 
	 * 
	 * 返回值:无
	 * 
	 * 温馨提示:根据题意可知,该二维数组只有两列,且行数为第0行的第二个元素数值+1;
	 */

	public int getResult(int[][] a) {
		// 在这里实现功能
		int money = a[0][0], numMax = a[0][1];
		int[][] max = new int[a.length][money + 1];
		for (int i = 1; i <= numMax; i++) {
			for (int j = 1; j <= money; j++) {
				if (j >= a[i][0]) {
					max[i][j] = Math.max(max[i - 1][j], max[i - 1][j - a[i][0]] + a[i][0] * a[i][1]);
				} else {
					max[i][j] = max[i - 1][j];
				}
			}
		}
		return max[numMax][money];
	}
}

猜你喜欢

转载自blog.csdn.net/qingmengwuhen1/article/details/82691567