动态规划
动态规划算法
基本概念
动态规划的过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划
基本思想和策略
基本思想与分治法类似,也是将待求解的问题分解为若干个子问题阶段,按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解
由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。
与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是相互独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)
适应的情况
能采用动态规划求解的问题的一般要具有3个性质:
- 最优化原理:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理
- 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态影响,只与当前的状态有关
- 有重叠子问题:即子问题之间不是独立的,一个子问题在下一阶段决策中可能多次使用到(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势了)
求解步骤
动态规划所处理的问题是一个多阶段决策问题,一般由初始状态开始,通过对中间阶段决策的选择,达到结束状态。这些决策形成了一个决策序列,同时确定了完成整个过程的一条活动路线(通常是求最优的活动路线)。如图所示。动态规划的设计都有着一定的模式,一般要经历以下几个步骤:
![](http://img-my.csdn.net/uploads/201303/26/1364262688_1892.jpg)
- 划分阶段:按照问题的时间或空间特征,把问题分为若干个阶段。在划分阶段时,注意划分后的阶段一定要是有序的或者是可排序的,否则问题就无法求解
- 确定状态和状态变量:将问题发展到各个阶段时所处于的各种客观情况用不同的状态表示出来。当然,状态的选择要满足无后效性
- 确定决策并写出状态转移方程:因为决策和状态转移有着天然的联系,状态转移就是根据上一阶段的状态和决策来导出本阶段的状态。所以如果确定了决策,状态转移方程也就可写出来了,但事实上常常是反过来做,根据相邻两个阶段的状态之间的关系来确定决策方法和状态转移方程
- 寻找边界条件:给出的状态转移方程是一个递推式,需要一个递推的终止条件或边界条件。一般,只要解决问题的阶段、状态和状态转移决策确定了,就可以写出状态转移方程(包括边界条件)
装配线调度问题
问题描述
某汽车工厂有2个装配线,每个装配线有n个装配站(按顺序编号1~n),两个装配线对应的装配站执行相同的功能,但所用的时间可能不同.经过第i条流水线(i=1,2)的第j个装配站所花的时间为Aij。从第i条流水线的第j个装配站移到第j+1个装配站的时间可以忽略,而移到另外一个流水线的下一个装配站则需要一定的时间Tij。 汽车进入流水线不需要花时间,出流水线时需要花时间Tin。 汽车的装配需要按顺序经过所有装配站。 现在已知装配时间Aij和转移时间Tij,要求输出装配一辆汽车所需要的最短时间。
动态规划的算法思想
动态规划与贪心策略类似,将一个问题的解决方案视为一系列决策的结果。不同的是,贪心算法每采用一次贪心选择便做出一个不可撤回的决策,而在动态规划中,还要考察每个最优决策序列中是否包含一个最优决策自序列。使用动态规划时,所求的问题应具有以下两种性质:
- 最优子结构性质
所求问题的最优子结构性质是采用动态规划算法的条件之一,这种性质又被称为最优化原理。动态规划方法采用最优化原理来建立用于计算最优解的递归式。所谓最优化原理即不管前面的策略如何,此后的决策必须是基于当前状态(由上一次决策产生)的最优决策。由于对于有些问题的某些递归式来说并不一定能保证最优原则,因此在求解问题时有必要对它进行验证。若不能保持最优原则,则不可应用动态规划方法。在得到最优解的递归式之后,需要执行回溯以构造最优解。当最优决策序列中包含最优决策子序列时,可建立动态规划递归方程,它可以帮助我们高效的解决问题
- 子结构重迭性质
人们总希望编写一个简单的递归程序来求解动态规划方程。然而,如果不努力的去避免重复计算,递归程序的复杂性将非常可观。如果在递归程序设计中解决了重复计算问题,复杂性将大幅度下降。这种方法的思想是:由程序设置“备忘录”,每计算出一个新的子结构的解时,都保存起来。当遇到一次递归时,判断是否已经计算,如果已经计算,只需取出先前保存的结果即可。动态规划递归方程也可以用迭代方式来求解,这时很自然的避免了重复计算。尽管迭代程序与避免重复计算的递归程序有相同的重复性,但迭代程序不需要附加的递归栈空间,因此将避免重复计算的递归程序更快
装配线调度代码(c语言版)
#include <stdio.h> #include <stdlib.h> #include <string.h> #define LINE 2 #define N 6 int main() { // 装配线装配时间 const int product[LINE][N] = {{7, 9, 3, 4, 8, 4}, {8, 5, 6, 4, 5, 7}}; // 装配线转移时间 const int transport[LINE][N - 1] = {{2, 3, 1, 3, 4}, {2, 1, 2, 2, 1}}; // 进装配线的时间 const int e1 = 2; const int e2 = 4; // 出装配站的时间 const int o1 = 3; const int o2 = 2; // 保存从起点到终点s(i, j)的最短时间 int final[LINE][N] = {{0}, {0}}; // 保存总时间 int time = 0; // 保存经过路线 int line[LINE][N] = {{0}, {0}}; // 保存最后从哪条线走出装配线 int line_out = -1; // 保存正序的最短装配路线 int path[N]; memset(path, -1, sizeof(path)); int i, j; // 计算最快时间 final[0][0] = e1 + product[0][0]; final[1][0] = e2 + product[1][0]; for (j = 1; j < N; j ++) { if (final[0][j - 1] + product[0][j] <= final[1][j - 1] + transport[1][j - 1] + product[0][j]) { final[0][j] = final[0][j - 1] + product[0][j]; line[0][j] = 0; }else { final[0][j] = final[1][j - 1] + transport[1][j - 1] + product[0][j]; line[0][j] = 1; } if (final[1][j - 1] + product[1][j] <= final[0][j - 1] + transport[0][j - 1] + product[1][j]) { final[1][j] = final[1][j - 1] + product[1][j]; line[1][j] = 1; }else { final[1][j] = final[0][j - 1] + transport[0][j - 1] + product[1][j]; line[1][j] = 0; } } if (final[0][N - 1] + o1 <= final[1][N - 1] + o2) { line_out = 0; time = final[0][N - 1] + o1; }else { line_out = 1; time = final[1][N - 1] + o2; } printf("总共花费的时间是:%d\n", time); // 站号递增的顺序输出各装配站 for (j = N - 1, i = line_out; j >= 1; j --) { i = line[i][j]; path[j] = i; } for (i = 1; i < N; i ++) { printf("line: %d, station: %d\n", path[i] + 1, i); } printf("line: %d, station: %d\n", line_out + 1, N); return 0; }
最长公共子序列(LCS)
题目描述
如果Z是X的一个子序列又是Y的一个最长公共子序列。例如,如果X = {A, B, C, B, D, A, B}, Y = {B, D, C, A, B, A},则{B, C, B, A}是X和Y的一个LCS,序列 {B,D, A,B}也是,因为没有长度为5的或更大公共子序列
思路
(1)梳理状态的变化情况,找到最优子结构:
设X = (x1, x2, x3, ..., xm), Y = (y1, y2, y3...yn)为两个序列,并设Z = (z1, z2, z3,...zk)为X和Y的任意一个LCS
- 如果xm == yn,那么zk == xm == yn,而且Z(k - 1)是X(m-1)和Y(n-1)的一个LCS;
- 如果xm != yn,那么zk != xm,蕴含Z是X(m-1)和Y的一个LCS;
- 如果xm != yn,那么zk != yn,蕴含Z是X和Y(n-1)的一个LCS;
(2)找到一个递归的解:
如果xm == yn,必须找到X(m - 1)和Y(n - 1)的一个LCS。将xm = yn添加到这个LCS上,可以产生X和Y的一个LCS;如果xm != yn,就必须解决两个子问题:找到x(m - 1)和y一个LCS,以及找出X和Y(n-1)的一个LCS,以及找出X和Y(n - 1)的一个LCS,这两个LCS中,较长的就是X和Y的一个LCS。由LCS的最优子结构可得到递归式:
![](http://img-my.csdn.net/uploads/201303/25/1364179710_8510.png)
最长公共子序列代码(c语言版)
#include <stdio.h> #include <stdlib.h> #include <string.h> #define N 1002 int num[N][N], b[N][N]; int longestCommonString(char *str1, char *str2, int len1, int len2); int main() { int l, len1, len2; char str1[N], str2[N]; while (scanf("%s %s", str1, str2) != EOF) { len1 = strlen(str1); len2 = strlen(str2); l = longestCommonString(str1, str2, len1, len2); printf("lcs is %d\n", l); } return 0; } int longestCommonString(char *str1, char *str2, int len1, int len2) { int i, j; for (i = 0; i < len1; i ++) { num[i][0] = 0; } for (j = 0; j < len2; j ++) { num[0][i] = 0; } for (i = 1; i <= len1; i ++) { for (j = 1; j <= len2; j ++) { if (str1[i - 1] == str2[j - 1]) { num[i][j] = num[i - 1][j - 1] + 1; b[i][j] = 0; }else { if (num[i - 1][j] >= num[i][j - 1]) { num[i][j] = num[i - 1][j]; b[i][j] = 1; }else { num[i][j] = num[i][j - 1]; b[i][j] = -1; } } } } return num[len1][len2]; }