动态规划详例解析
来源:互联网 发布:mysql enum 编辑:程序博客网 时间:2024/05/22 03:05
一、综述
动态规划是一种通过组合子问题的解地来求解原问题的方法,思考状态的时候采用递归,计算的时候自底向上穷举所有最优子问题。与分治法的区别在于:分治方法将问题划分为互不相交的子问题,递归地求解子问题,再将他们的解组合起来,求出原问题的解;而动态规划应用于子问题重叠的情况,但是注意,这里的子问题重叠是指在求原问题的子问题时,子问题的子问题重叠,即不同的子问题具有公共的子子问题。动态规划对每个子子问题只求解一次,将其解保存在一个表格里,从而每次求解那些公共子子问题时无需重新计算。
动态规划通常用来求解最优化问题,这类问题有很多可行的解,每个解都有一个值,我们希望找到最优的值。设计一个动态规划的一般步骤如下:
- 刻画一个最优解的结构特征;
- 递归地定义最优解的值;
- 计算最优解的值,通常采用自底向上的方法(与分治法的区别);
- 利用计算出的最优解的值构造该优解值的组合。
这个步骤比较细,我自己的理解是这样的:
- 定义问题的状态,注意,状态不仅可能为一个确切、量化的值,更多情况下是一个描述;
- 递推地表示状态,即状态转移方程,表示从某一状态如何转到其子状态(可以是多个子状态,也可以是单个子状态);
- 自底向上地不重复计算状态,此时一定要注意边界条件。
实际上,根据上述的步骤,我们可以发现,实际上动态规划将所有的最优子状态穷举了,最后找出总问题的最优状态。接下来,我们来看看常见的动态规划的经典题目。
二、经典案例
因为我懒得写main方法入口,所以使用的是JUnit4测试框架,然后又懒得每次写@Test注解的方法,受Spring MVC里AbstractController类的启发,就写了一个抽象类来作为程序运行时的入口,代码如下:
import org.junit.Test;public abstract class EnhanceModual { @Test public void entrance() { internalEntrance(); } public abstract void internalEntrance();}
接下来的类均继承这个抽象类,然后重写internalEntrance()方法即可。
1、斐波那契数的非递归形式
通常,我们写斐波那契数的代码时,按照从顶到底的顺序使用尾递归计算,即为了求F(n),从n开始,求F(n-1)和F(n-2)…这种代码非常的低效,换一种思维,采用动态规划的思维去考虑斐波那契数。
斐波那契数:F(0) = 0,F(1) = 1, F(2) = 1, F(3) = 2,…,F(n)
状态为F(n),即第n个位置上的斐波那契值
状态转移方程:F(n) = F(n-1) + F(n-2) (n≥2)
第三步:自底向上地计算所有值,即从F(0)开始算起。
import java.text.MessageFormat;/** * 斐波那契数列:0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, * 233, 377, 610, 987, 1597, 2584, 4181... * 递推公式:F[n]=F[n-1]+F[n-2](n>=2,F[0]=0,F[1]=1) */public class ImprovedFibonacci extends EnhanceModual { public int improvedFibonacci(int index) { int[] results = { 0, 1 }; int result = 0; if (index < 2) { result = results[index]; return result; } int fabonacciOne = 0;//用来保存两个子状态之一 int fabonacciTwo = 1;//用来保存另一个子状态 // 从底层开始计算,这样的话就可以把记录需要重复计算的项了 for (int i = 2; i <= index; i++) { result = fabonacciOne + fabonacciTwo; fabonacciOne = fabonacciTwo;//子状态更新 fabonacciTwo = result;//子状态更新 } return result; } public void internalEntrance() { for (int i = 0; i <= 19; i++) { int result = improvedFibonacci(i); System.out.println(MessageFormat.format("F[{0}] = {1}", i, result)); } }}运行结果:F[0] = 0F[1] = 1F[2] = 1F[3] = 2F[4] = 3F[5] = 5F[6] = 8F[7] = 13F[8] = 21F[9] = 34F[10] = 55F[11] = 89F[12] = 144F[13] = 233F[14] = 377F[15] = 610F[16] = 987F[17] = 1,597F[18] = 2,584F[19] = 4,181
2、C(N)=(2/N)∑N−1i=0C(i)+N 的非递归实现
在《数据结构与算法分析——C语言描述》一书的P288面提到了
看下图,C(N)的计算过程展开图,从低向上计算时,可以用一个列表存放之前已经计算的值,计算下一个值时,只需要把列表里的值全部取出来求和带入公式里就行了,但是这样需要扫描全表,所以我觉得不是很好,就试着将这个递推公式改了一下。
修改后的的状态转移公式如下,这个公式很好推出来,这里就不写过程了,按照这个公式,我们每次计算的时候都只需要一个前状态,这样的话就不用使用列表存储了,只需要用一个临时变量存储该值即可,这个例子给我们的启示就是有时候状态转移方程可以有多种表示方式,最好能够化简一下,尽可能选择所需子状态最少的状态转移方程。
C(N)=(1+1/N)C(N−1)+2−1/N,N≥1
/** * 数据结构与算法分析:C语言描述P288的递推式的改进 */public class ImprovedRecursion extends EnhanceModual { public double originalRecursion(int n) { double result = 0.0; if (n == 0) { return 1.0; } else { for (int i = 0; i < n; i++) { result += originalRecursion(i); } return 2.0 * result / n + n; } } public double improvedRecursion(int n) { double result = 0.0; double[] results = { 1.0, 3.0 }; if (n < 2) { return results[n]; } else { double resultTmp = 3.0; for (int i = 2; i <= n; i++) { //注意这里(1 + 1.0 / i)不能写成了(1 + 1 / i) result = (1 + 1.0 / i) * resultTmp + 2 - 1.0 / i; resultTmp = result; } return result; } } @Override public void internalEntrance() { // TODO Auto-generated method stub System.out.print("原始递归方法:"); System.out.print(originalRecursion(0)+" "); System.out.print(originalRecursion(1)+" "); System.out.print(originalRecursion(2)+" "); System.out.print(originalRecursion(3)+" "); System.out.println(originalRecursion(4)); System.out.print("改进型方法"); System.out.print(improvedRecursion(0)+" "); System.out.print(improvedRecursion(1)+" "); System.out.print(improvedRecursion(2)+" "); System.out.print(improvedRecursion(3)+" "); System.out.println(improvedRecursion(4)); }}运行结果:原始递归方法:1.0 3.0 6.0 9.666666666666668 13.833333333333334改进型方法1.0 3.0 6.0 9.666666666666666 13.833333333333332
根据运行的结果,显然不同的计算方式对结果的精度也不一样,这些都是在写程序时需要考虑的问题。
3、矩阵连乘问题
- 动态规划详例解析
- 动态规划算法解析
- 动态规划原理解析
- HDU动态规划解析
- 动态规划全解析
- 动态规划解析
- 动态规划原理解析
- 动态规划算法解析
- 动态规划问题详例
- 动态规划算法例题及解析
- 鹰蛋问题解析之动态规划
- 鹰蛋问题解析之动态规划
- 洛谷1594 护卫队(动态规划) 解析
- 动态规划一例
- 动态规划 46例
- 动态规划100例
- 动态规划!!!动态规划!!!
- 两道动态规划(DP)题目解析
- 浅谈android 点击事件分发处理流程
- -汉诺塔-递归算法(JS递归函数)
- 闭包
- 目录结构
- 欢迎使用CSDN-markdown编辑器
- 动态规划详例解析
- book1 unit1 in-class reading : The Gift of Life
- 编写Shell脚本的最佳实践
- Mybatis批量增加、批量更新、批量删除和查询
- 让CSDN记下自己成长
- 内ARCHLine.XP.2017.R1 x64多学科设计
- 【SVN】导入项目检出项目
- 网易校招第4题
- [算法]贪心算法的详解以及使用