动态规划-钢条切割(java)
来源:互联网 发布:java对象的引用 编辑:程序博客网 时间:2024/06/06 10:53
数据结构与算法系列源代码:https://github.com/ThinerZQ/AllAlgorithmInJava
本文源代码:https://github.com/ThinerZQ/AllAlgorithmInJava/blob/master/src/main/java/com/zq/algorithm/dynamicprogrammin/SteelBar.java
如果代码链接失效了,麻烦评论给我。
动态规划与分治法相似,都是通过组合子问题的解来求解原问题。
分治法将问题划分为不互相交子问题,递归的求解子问题,再将他们组合起来,求出原问题的解。
与之相反,动态规划应用于子问题重叠的情况,即不同的子问题具有公共的 子子问题。这种情况下分治法会重复的求解那些公共的子子问题。而动态规划算法对每个子子问题只求解一次,将其存放在某一个表格中,无需每次求解一个子子问题时都重新计算,避免了不必要的计算工作,特别是当问题规模比较大的时候,在时间上有显著的区别
动态规划用来求解最优化问题。这类问题可以有很多可行的解,每个解都有一个值,我们希望需找具有最优值(最大或最小)的解。当然可能同时存在多个最优解(同时最大,或同时最小),动态规划只要求找到其中一个就好了。
这里我们用算法导论里面的钢条切割为例子。
切钢条:假如Serling公司出售一段长度为 i 英寸的钢条的价格为 pi( i =1,2,3,4…单位问美元)。钢条的长度为整英寸。
下表是一个价格表。
假设Serling公司进了一批长度为10的钢条,那么怎么切割才能使利益最大呢,长度为 9 , 8 呢?
对于上述价格表样例,我们可以观察出所有最优收益值Ri及对应的最优解方案:
更一般地,对于Rn(n >= 1),我们可以用更短的钢条的最优切割收益来描述它:
Rn = max(Pn, R1 + Rn-1, R2 + Rn-2,…,Rn-1 + R1)
首先将钢条切割为长度为i和n - i两段,接着求解这两段的最优切割收益Ri和Rn - i(每种方案的最优收益为两段的最优收益之和),由于无法预知哪种方案会获得最优收益,我们必须考察所有可能的i,选取其中收益最大者。如果直接出售原钢条会获得最大收益,我们当然可以选择不做任何切割。
注意到,为了求解规模为n的原问题,我们先求解形式完全一样,但是规模更小的子问题。当完成首次切割之后,我们将两段钢条看成两个同等的钢条切割问题实例,通过组合两个问题的最优解,并在所有可能的两段切割方案中选择组合收益最大值,构成原问题的最优解,我们称这样的问题满足最优子结构性质:问题的最优解是由相关子问题的最优解组和而成的,这些子问题可以独立求解。
分析到这里,假设现在出售10英寸的钢条,应该怎么切割呢?为了方便分析,我们使用钢条长度=4来分析问题
1、解法:
1.1 递归法:
/** * 递归方法,时间复杂度为O(2的N次方),因为考察了 2的N-1次方种可能 * @param p,钢条的价格数组, * @param n,钢条的长度,这里的划分是以 1 为单位 * @return 最大收益 */ public int cut_rod(int[] p,int n){ //递归出口,n=0,不用切割了。 if ( n==0){ System.out.println("调用子问题规模:0"); return 0; } // q 是最大值,初始值设为为一个负值, int q=-1; //对于每一次递归调用,都会求1..n之间的最优质,然后返回给上一层 for (int i=1;i<=n;i++){ //当前长度为 n 的切割收益的最大值,是当前的 q .和p[i]+cut_rod(p,n-i)中的最大值,循环中时不断改变q值的, System.out.println("调用子问题规模:"+n); q=max(q,p[i]+cut_rod(p,n-i)); if (i==n){ System.out.println("子问题规模为 "+n+" 的最优值 = "+q); } } System.out.println("回到第:"+(n+1)+"层"); System.out.println(); return q; } public int max(int a,int b){ return a>b?a:b; }
1.1.1 分析:
上面代码的递归中,始终会重复执行太多相同的操作,例如cut_rod(p,4)会递归调用cut_rod(p,3),cut_rod(p,2),cut_rod(p,1),cut_rod(p,0),当调用cut_rod(p,3) 的时候,又会递归调用cut_rod(p,2),…cut_rod(p,1)……..。
1.1.2 程序输出结果:
1.1.3 程序递归分析
………..如此多的重复递归是没有必要的,这也是动态规划所要处理的问题。
怎么避免重复调用呢??
动态规划的做法是,将每一次求得的cut_rod(p,i)的最优值保存在一个表(数组)里面,每次需要使用的时候,不用再递归调用了,直接使用就好了。
1.2 动态规划——>带备忘的自顶向下法:
/** * 动态规划方法 * 带备忘的自顶向下法 * @param p,钢条的价格数组, * @param n,钢条的长度,这里的划分是以 1 为单位 * @return 最大收益 */ public int memoized_cut_rod(int[] p,int n){ //一个数组,用r[i] 来保存 钢条长度为 i 的时候的最优值,初始值赋为 -1.一个负值就行。 int[] r= new int[n+1]; for (int i=0;i<r.length;i++){ r[i]=-1; } //调用递归的那个方法,返回长度为 n的最优值。 return memoized_cut_aux(p,n,r); } /** * * @param p,钢条的价格数组, * @param n,钢条的长度,这里的划分是以 1 为单位 * @param r 保存中间值的数组 * @return 最大收益 */ public int memoized_cut_aux(int[] p,int n,int[] r){ //递归出口,如果r[n] >0,表明,长度为 n 的钢条的最优值已经存在了。不用递归了,直接返回这个最优值,这里必须是r[n]>=0,因为r[0]是等于0的, if (r[n]>=0){ System.out.println(); System.out.print(" ------直接返回r[" + n + "] = " + r[n] ); return r[n]; } //设置零时变量 q 最为最大值 int q=-1; //刚进入递归的时候,刚开始一路调用下来,必然是从这个口出去。 if (n==0){ q=0; System.out.print(" 调用 n ="+q + " 第一次保存r[0]的值:" + q); }else { //递归调用,求解最大值。 System.out.println(" 调用 n ="+n); for (int i=1;i<=n;i++){ q = max(q,p[i]+memoized_cut_aux(p,n-i,r)); System.out.print(" 开始回溯到n="+n); if (i==n){ System.out.println(); } // System.out.println(); } } System.out.println(); //将每一次求的长度为 n 的最优值保存在数组 r 里面 r[n]=q; //返回最大值 if (n==r.length-1){ System.out.println("程序结束,返回r["+n+"]="+r[n]); } return q; }
1.2.1 分析:
上面使用自顶向下的方法,求解问题,就像深度优先搜索二叉树一样。具体分析见下图
1.2.2 程序输出结果:
1.2.3 程序递归分析:
1.3 动态规划——>自底向上法:
/** * 动态规划,自底向上求解。 * @param p,钢条的价格数组, * @param n,钢条的长度,这里的划分是以 1 为单位 * @return 最大收益 */ public int bottomUpCutRod(int[] p,int n){ //一个数组,用r[i] 来保存 钢条长度为 i 的时候的最优值,初始值赋为 0. int[] r= new int[n+1]; for (int i=0;i<r.length;i++){ r[i]=0; } //循环,外层依次求解 1....n的最优值 for (int j=1;j<=n;j++){ int q=-1; //内层,依次在 1 .. j 中求出最大值, //例如 // 当 j =1 的时候,q=max(q,p[1]+r[0]) .求的r[1]的最优值 // 当 j =2 的时候,q=max(q,p[1]+r[1]),然后再是 q=max(q,p[2]+r[0]) ,求的r[2]的最优值 // ... 以此类推 for (int i=1;i<=j;i++){ q=max(q,p[i]+r[j-i]); } //记录 j 的最优值 r[j]=q; } //最终返回 n 的最优值 return r[n]; }
1.3.1 分析:
这个方法是动态规划最佳方法,具体见后面的总结
1.3.2 程序结果:
1.3.3 程序分析:
自底向上的方法,不必进行递归调用,而是直接访问数组元素r[j-i]来获得规模为j-i的子问题的解。同时也将规模为j的解存入r[j]。就像上图一样,r[0]的解是0,r[1]的解依靠r[1] ,r[2]的解依靠r[0]和r[1]…..r[4]的解依靠r[0]和r[1],r[2],和r[3].
上面只是求出了,钢条长度为 i 的最优值,那么怎么切割呢?下面砸门来看看
1.4 求解切割方案
直接上代码,extended_button_up_cut_rod
函数和自底向上求解最优值的函数是一样的,不同点就是加入了一个保存切割方案的数组s,每次找到最优值的时候,记录切割方案。
/** * 求解最优值和组合方案 * @param p 价格表 * @param n 钢条长度 * @param r 最优值数组, * @param s 切割方案数组 */ public void extended_button_up_cut_rod(int[] p,int n,int[] r,int[] s){ //循环,外层依次求解 1....n的最优值 for (int j=1;j<=n;j++){ int q=-1; //内层,依次在 1 .. j 中求出最大值, //例如 // 当 j =1 的时候,q=max(q,p[1]+r[0]) .求的r[1]的最优值 // 当 j =2 的时候,q=max(q,p[1]+r[1]),然后再是 q=max(q,p[2]+r[0]) ,求的r[2]的最优值 // ... 以此类推 for (int i=1;i<=j;i++){ if (q<p[i]+r[j-i]){ q=p[i]+r[j-i]; //记录长度为 j 的钢条 第一下开始切割的位置 i . s[j]=i; } } //记录 j 的最优值 r[j]=q; } } /** * 输出最优值和切割方案的函数 * @param p 价格表 * @param n 钢条长度 */ public void print_cut_rod_solution(int[] p,int n){ //一个数组,用r[i] 来保存 钢条长度为 i 的时候的最优值,初始值赋为 0. int[] r= new int[n+1]; for (int i=0;i<r.length;i++){ r[i]=0; } int[] s = new int[n+1]; for (int i=0;i<r.length;i++){ s[i]=0; } //调用求最优值和方案的函数 extended_button_up_cut_rod(p,n,r,s); System.out.print("n="+n+" 的最优值为:"+r[n]+" , 切割方案为:"); //当 n>0 的时候,表明还有长度需要切割,哪怕做0切割 while (n>0){ //输出,组合方案 System.out.print(s[n] + "+"); //改变 n 的值,n=s[n]表示 已经切割下了s[n]那么长,剩下的要怎么切割 n=n-s[n]; } }
1.4.1 程序结果:
可以参考上面给出的表格中的数据
1.4总结:
第一种直接的自顶向下的递归方法,没有考虑子问题重叠问题,时间复杂度为指数级问题规模稍微大一点,比如(n=30),时间复杂度就不能忍受了。
第二种自上而下的带备忘录递归方法,考虑了子问题重叠问题,利用空间来保存求得的结果,时间复杂度为o(n^2),效果较好。
第三种自下而上的方法,很自然的考虑了子问题重叠问题,时间复杂度为o(n^2),没有频繁的递归调用的开销,这种方法具有更下的系数。更好。和第二种方法空间复杂度一样都是O(n)
- 动态规划-钢条切割(java)
- 钢条切割-动态规划
- 切割钢条【动态规划】
- 动态规划-钢条切割
- 动态规划-钢条切割
- 动态规划-钢条切割
- 动态规划 钢条切割
- 钢条切割-动态规划
- 【动态规划】钢条切割
- 动态规划--钢条切割
- 动态规划----切割钢条
- 动态规划-钢条切割
- 动态规划 钢条切割
- 钢条切割--动态规划
- 动态规划(二)--钢条切割
- 算法导论--动态规划(钢条切割)
- 钢条切割(动态规划法)
- 动态规划之切割钢条
- WebGL实现HTML5的贪吃蛇3D游戏
- Application.DoEvents()的作用
- Spring定时任务的几种实现
- JQuery中$.ajax()方法参数详解
- 英国真的没有电商吗?
- 动态规划-钢条切割(java)
- oracle 11g体系结构
- AFNetworking2.0源码解析<二>
- 一种android定时器写法
- opencv MAT
- js的call和apply的应用说明
- 矩阵的基本知识
- MyEclipse Servers视窗出现“Could not create the view: An unexpected exception was thrown”错误解决办法
- [软件人生]关于劳动关系中如何保护自己