动态规划.docx
《动态规划.docx》由会员分享,可在线阅读,更多相关《动态规划.docx(12页珍藏版)》请在冰豆网上搜索。
动态规划
动态规划的基本思想
将一个问题分解为子问题递归求解,且将中间结果保存以避免重复计算。
通常用来求最优解,且最优解的局部也是最优的。
求解过程产生多个决策序列,下一步总是依赖上一步的结果,自底向上的求解。
动态规划算法可分解成从先到后的4个步骤:
1.描述一个最优解的结构,寻找子问题,对问题进行划分。
2.定义状态。
往往将和子问题相关的各个变量的一组取值定义为一个状态。
某个状态的值就是这个子问题的解(若有k个变量,一般用k维的数组存储各个状态下的解,并可根据这个数组记录打印求解过程。
3.找出状态转移方程。
一般是从一个状态到另一个状态时变量值改变。
4.以“自底向上”的方式计算最优解的值。
5.从已计算的信息中构建出最优解的路径。
(最优解是问题达到最优值的一组解)
背包问题
01背包:
有N件物品和一个重量为M的背包。
(每种物品均只有一件)第i件物品的重量是w[i],价值是p[i]。
求解将哪些物品装入背包可使价值总和最大。
完全背包:
有N件物品和一个重量为M的背包,每种物品都有无限件可用。
第i种物品的重量是w[i],价值是p[i]。
求解将这些哪些物品装入背包可使这些物品的总量不超过背包重量,且价值总和最大。
多重背包:
有N种物品和一个重量为M的背包。
第i种物品最多有n[i]件可用,每件重量是w[i],价值是p[i]。
求解将哪些物品装入背包可使这些物品的费用总和不超过背包重量,且价值总和最大。
01背包问题
这是最基础的背包问题,特点是:
每种物品仅有意见,可以选择放或不放。
用子问题定义状态:
即c[i][v]表示前i件物品恰放入一个重量为m的背包可以获得的最大价值。
则其状态转移方程便是
C[i][m]=max{c[i-1][m],c[i-1][m-w[i]]+p[i]}
这个方程非常重要,基本上所有跟背包问题相关的问题的方程都是由它衍生出来的。
可以解释为:
“将前i件物品放入重量为m的背包中”这个子问题,若只考虑第i件物品的策略,那么就可以转化为一个只牵涉到前i-1件物品的问额。
如果不放第i件物品,那么问题就转化为“前i-1件物品放入容量为v的背包中”,价值为c[i-1][m];如果放第i件物品,那么问题就转化为“前i-1件物品放入剩下的重量为m-w[i]的背包中”,此时能获得的最大价值就是c[i-1][m-w[i]]再加上通过放入第i件物品获得的价值p[i].
[html] viewplaincopy
1.public class Pack01 {
2.
3. public int [][] pack(int m,int n,int w[],int p[]){
4. //c[i][v]表示前i件物品恰放入一个重量为m的背包可以获得的最大价值
5. int c[][]= new int[n+1][m+1];
6. for(int i = 0;i7. c[i][0]=0;
8. for(int j = 0;j9. c[0][j]=0;
10. //
11. for(int i = 1;i12. for(int j = 1;j13. //当物品为i件重量为j时,如果第i件的重量(w[i-1])小于重量j时,c[i][j]为下列两种情况之一:
14. //
(1)物品i不放入背包中,所以c[i][j]为c[i-1][j]的值
15. //
(2)物品i放入背包中,则背包剩余重量为j-w[i-1],所以c[i][j]为c[i-1][j-w[i-1]]的值加上当前物品i的价值
16. if(w[i-1]<=j){
17. if(c[i-1][j]<(c[i-1][j-w[i-1]]+p[i-1]))
18. c[i][j] = c[i-1][j-w[i-1]]+p[i-1];
19. else
20. c[i][j] = c[i-1][j];
21. }else
22. c[i][j] = c[i-1][j];
23. }
24. }
25. return c;
26. }
27. /**
28. * 逆推法求出最优解
29. * @param c
30. * @param w
31. * @param m
32. * @param n
33. * @return
34. */
35. public int[] printPack(int c[][],int w[],int m,int n){
36.
37. int x[] = new int[n];
38. //从最后一个状态记录c[n][m]开始逆推
39. for(int i = n;i>0;i--){
40. //如果c[i][m]大于c[i-1][m],说明c[i][m]这个最优值中包含了w[i-1](注意这里是i-1,因为c数组长度是n+1)
41. if(c[i][m]>c[i-1][m]){
42. x[i-1] = 1;
43. m-=w[i-1];
44. }
45. }
46. for(int j = 0;j47. System.out.println(x[j]);
48. return x;
49. }
50. public static void main(String args[]){
51. int m = 10;
52. int n = 3;
53. int w[]={3,4,5};
54. int p[]={4,5,6};
55. Pack01 pack = new Pack01();
56. int c[][] = pack.pack(m, n, w, p);
57. pack.printPack(c, w, m,n);
58. }
59.}
动态规划是运筹学中用于求解决策过程中的最优化教学方法。
用来解决某类最优化问题的重要工具。
如果问题是有交叠的子问题所构成,我们就可以用动态规划技术来解决它,一般来说,这样的子问题出现在对给定问题求解的递推关系中,这个递推关系包括了相同问题的更小问题的解。
动态规划法建议,与其对交叠子问题一次又一次的求解,如果把
每个较小子问题只求解一次并把结果记录在表中,即空间换时间。
它往往是解决最优化问题的。
再看动态规划的几个关键点:
(1)怎么描述问题,要把问题描述为交叠的子问题
(2)交叠子问题的初始条件(边界条件)
(3)动态规划在形式上往往表现为填矩阵的形式(在后面会看到,有的可以优化空间复杂度,开一个数组即可,优化也是根据递推式的依赖形式的。
(4)填矩阵的方式,表明了这个动态规划从小到大产生的过程,递推式的依赖形式决定了填矩阵的顺序。
有两点很重要;
(1)填矩阵的顺序
(2)动态规划空间复杂度的优化
这两点都跟递推式的依赖关系有关,在形式上就表现为填矩阵的时候你的顺序要确保每填一个新位置时你所用到的那些未知要已经填好了。
动态规划的难点在于前期的设计:
(1)怎么描述问题,使它能表述一个动态规划问题(具备什么特征?
最优子结构?
多阶段决策)
(2)递推式的写出(逆向思维去分析或正向思维去递归),确定你要求的是哪个值
(3)有了递推式可以画个矩阵的图(一般只从式子上不太容易看出来,当然,对于牛人来说可以藐视。
最优子结构和多阶段决策:
最优子结构:
在动态规划求解过程中,子问题产生的解对于子问题来说也是最优解。
多阶段决策:
一步步的决策,无后效性,决策之依赖于当前状态,不依赖于之前的状态。
[cpp] viewplaincopy
1.// 最长公共子串(连续) LCS
2.// Deng Chao
3.// 2012.12.4
4.
5.#include
6.#include
7.using namespace std;
8.
9.
10.// 查找公共子串
11.// lcs记录公共子串
12.// return 公共子串长度
13.int LCS(const char *str1 , int len1 , const char *str2 , int len2 , char *&lcs)
14.{
15. if(NULL == str1 || NULL == str2)
16. {
17. return -1; //空参数
18. }
19.
20. // 压缩后的最长子串记录向量
21. int *c = new int[len2+1];
22. for(int i = 0 ; i < len2 ; ++i)
23. {
24. c[i] = 0;
25. }
26. int max_len = 0; //匹配的长度
27. int pos = 0; //在str2上的匹配最末位置
28. for(int i = 0 ; i < len1 ; ++i)
29. {
30. for(int j = len2 ; j > 0 ; --j) //更新时从后往前遍历
31. {
32. if(str1[i] == str2[j-1])
33. {
34. c[j] = c[j-1] + 1;
35. if(c[j] > max_len)
36. {
37. max_len = c[j];
38. pos = j-1;
39. }
40. }
41. else
42. {
43. c[j] = 0;
44. }
45. }
46. }
47.
48. if(0 == max_len)
49. {
50. return 0;
51. }
52.
53. // 得到公共子串
54. lcs = new char[max_len];
55. for(int i = 0 ; i < max_len ; ++i)
56. {
57. lcs[i] = str2[pos-max_len+1+i];
58. }
59. cout<<"pos = "<60. delete [] c;
61. return max_len;
62.
63.}
64.
65.// test
66.int main()
67.{
68. const char *str1 = "abacaba";
69. const char *str2 = "caba";
70. int len1 = strlen(str1);
71. int len2 = strlen(str2);
72.
73. char *lcs;
74.
75. int len = LCS(str1 , len1 , str2 , len2 , lcs);
76. cout<<"max length = "<77. for(int i = 0 ; i < len ; ++i)
78. {
79. cout<80. }
81.}
二者含义没搞清楚,雅虎的笔试就这样的写错了。
求最长公共字串的题目写成了最长公共子序列。
子串要求字符必须是连续的,但是子序列就不是这样了。
悲催了。
子序列跟子串的求法类似,都是使用动态规划的思想,s1每次增加一个字符,看与s2当前位置的字符是不是相同,如果相同做相应的处理,如果不同,做另外的处理。
子序列的处理方式:
相同的情况下,该二维数组的位置等于[i-1][j-1]+1
不同的情况下,该二维数组的位置等于MAX(d[i-1][j],d[i][j-1])
下面描述下子串的求法。
最长公共子串,要求字符是连续的。
那么在[s1每次增加一个字符,看与s2当前位置的字符是不是相同]
相同的情况下,二维数组的位置等于[i-1][j-1]+1,
不同的情况下,二维数组的位置等于0,最后再查看二维数组的信息即可得到最长公共子串的长度,同时可以回溯二维数组得到最长公共字串的内容。
[java] viewplaincopy
1.package InnerClass;
2.
3.public class TT {
4. public static void main(String ss[]) {
5. get("abcdefg", "hbcsefgk");
6. }
7.
8. private static void get(String s1, String s2) {
9.
10. int lcslen = 0;
11. int pos_x = -1, pos_y = -1;
12. // 初始化二维数组
13. int flag[][] = new int[s1.length() + 1][s2.length() + 1];
14. for (int i = 0; i < s1.length() + 1; i++) {
15. flag[i][0] = 0;
16. }
17. for (int i = 0; i < s2.length() + 1; i++) {
18. flag[0][i] = 0;
19. }
20.
21. for (int i = 1; i <= s1.length(); i++) {
22. for (int j = 1; j <= s2.length(); j++) {
23. if (s1.charAt(i - 1) == s2.charAt(j - 1)) {
24. flag[i][j] = flag[i - 1][j - 1] + 1;
25. if (flag[i][j] > lcslen) {
26. lcslen = flag[i][j];
27. pos_x = i;// 记录下最大长度在二维数组中的位置
28. pos_y = j;
29. }
30. } else {
31. flag[i][j] = 0;
32. }
33. }
34. }
35. // 方便观察,输出二维矩阵
36. for (int i = 0; i < s1.length() + 1; i++) {
37. for (int j = 0; j < s2.length() + 1; j++) {
38. System.out.print(flag[i][j]);
39. }
40. System.out.println();
41. }
42. // 输出最长子串
43. StringBuilder sb = new StringBuilder();
44. while (flag[pos_x][pos_y] !
= 0) {
45. sb.append(s1.charAt(pos_x - 1));
46. pos_x--;
47. pos_y--;
48. }
49. System.out.println(sb.toString());
50. }