动态规划相关问题学习笔记

转载自:http://blog.csdn.net/speedme/article/details/24231197

1. 什么是动态规划

-------------------------------------------

dynamic programming is a method for solving complex problems by breaking them down into simpler subproblems. (通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法)

咦,这不就是分而治之法吗?不,虽然它们的目的是一样的,但它们分解的子问题属性不同。

  • 分而治之将问题划分为互不相交的子问题。
  • 动态规划不同,动态规划应用于子问题重叠情况,即不同的子问题具有公共的子子问题。

动态规划解决的问题必须具备三个属性:最优子结构性质,无后效性,子问题重叠性质。

  • 最优子结构:如果问题的最优解所包含的子问题的解也是最优的,就称该问题具有最优子结构,即满足最优化原理。
  • 无后效性:即某阶段状态一旦确定,就不受这个状态以后决策的影响。也就是说,某状态以后的过程不会影响以前的状态,只与当前状态有关。
  • 重叠子问题:即子问题之间是不独立的,一个子问题在下一阶段决策中可能被多次使用到。(该性质并不是动态规划适用的必要条件,但是如果没有这条性质,动态规划算法同其他算法相比就不具备优势)

动态规划背基本思想:大致上,若要解一个给定问题,我们需要解其不同部分(即子问题),再合并子问题的解以得出原问题的解。 通常许多子问题非常相似,为此动态规划法试图仅仅解决每个子问题一次,从而减少计算量: 一旦某个给定子问题的解已经算出,则将其记忆化存储,以便下次需要同一个子问题解之时直接查表。 这种做法在重复子问题的数目关于输入的规模呈指数增长时特别有用。

2. 流水线调度问题

-----------------------------------------------

  1. Characterize the structure of an optimal solution.           (分析最优解的性质,并刻画其结构特征)
  2. Recursively define the value of an optimal solution.      (递归的定义最优解)
  3. Compute the value of an optimal solution in a bottom-up fashion.       (以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值)
  4. Construct an optimal solution from computed information.       (根据计算最优值时得到的信息,构造问题的最优解)

下面就根据算法导论上的流水线调度的例子来详解。

问题:有两条流水线,每条流水线分别有多个工作站,两个流水线的对应的同一个工作站之间可以传输要加工的产品,但需要一定时间记为ti,j,但单条流水线站之间传输要加工的产品时间可以忽略不计。现在要求工作一个产品所需要的最短时间。流程如下图。

e代表产品进入不同流水线的时间,a代表不同站加工产品所需时间,t代表不同流水线的不同站传递产品的时间。

分析:看题目中的图可能不够清楚,我们拿出一个有具体时间的图来分析。如下图:

图中带有阴影部分的箭头代表最优解。那么他们是怎样得出来的呢。下面我们就利用动态规划的步骤来解决这个问题。

2.1 构造最优解结构

我们从起点开始分析,首先 我们会选择哪条流水线开始呢,有人会说那肯定是第一条流水线开始啊,因为2+7<4+8.但其实并不是这样,算法导论这个例子举的不好,很容易产生这样的误解。其实不一定选择这条路径,因为你要保证下一条也时间最短,假设S11-S22的时间不是2是4的话,那么选择从第一条流水线开始就是错误的,因为2+7+4>4+8,这样就是第二条流水线到达S22的时间更快。(如果你觉得我这里说的混乱的话可以跳过这一段,没太大影响)。

知道这一点之后,我们继续。我们分别记录下达到S11和S21的时间,然后选择下一站,到达S12的路径有两条,到达S22的路径也有两条。我们就是分别从两条中选出最快的哪条作为到达S12和S22的最短时间存入表中。。。。这样一直走到末尾。

那么就可以把问题简化为计算通过S1,j 和S2,j 的最短时间路径:

我们知道到达S1,j 站的的路径只有两条:

  • the fastest way through station S1,j-1 and then directly through stationS1,j, or
  • the fastest way through station S2,j-1, a transfer from line 2 to line 1, and then through stationS1,j.

同理到达S2,j 的路径也只有两条:

  • the fastest way through station S2,j-1 and then directly through stationS2,j, or

  • the fastest way through station S1,j-1, a transfer from line 1 to line 2, and then through stationS2,j.

我们要做的工作就是分别得出两个站的最短时间,然后存入表中。

2.2 递归定义最优解

上一步中我们分析出了,最优解结构,下面就用递归式来表示得出最优解的过程。

我们用fi[j] 来表示到达Si,j 的最短时间。

首先我们计算出到达第一站的时间:

根据第一步骤中构造的最优解结构我们可以计算出达到S1,j和S2,j的时间:

总上所属我们可以写出最有解的递归式如下

2.3 以自底向上或自顶向下的记忆化方式(备忘录法)计算出最优值

这一步骤就是上一步中递归式的实现步骤了,下面我就不多说了,直接上伪代码,大家最好自己去实现一遍。

FASTEST-WAY(a, t, e, x, n)
 1  f1[1] ← e1 + a1,1
 2  f2[1] ←e2 + a2,1
 3  for j ← 2 to n
 4       do if f1[j - 1] + a1,j ≤ f2[j - 1] + t2,j-1 + a1,j
 5             then f1[j] ← f1[j - 1] + a1, j
 6                  l1[j] ← 1
 7             else f1[j] ← f2[j - 1] + t2,j-1 + a1,j
 8                  l1[j] ← 2
 9          if f2[j - 1] + a2,j ≤ f1[j - 1] + t1,j-1 + a2,j
10             then f2[j] ← f2[j - 1] + a2,j
11                  l2[j] ← 2
12             else f2[j] ∞ f1[j - 1] + t1,j-1 + a2,j
13                  l2[j] ← 1
14  if f1[n] + x1 ≤ f2[n] + x2
15      then f* = f1[n] + x1
16          l* = 1
17      else f* = f2[n] + x2
18             l* = 2

2.4 根据计算得到的结果,构造出最优解(即输出最佳路径)

伪代码:

PRINT-STATIONS(l, n)
1  i ← l*
2  print "line " i ", station " n
3  for j ← n downto 2
4        do i ← li[j]
5           print "line " i ", station " j - 1

如例子中输出的结果为下:

line 1, station 6

line 2, station 5

line 2, station 4

line 1, station 3

line 2, station 2

line 1, station 1

3 矩阵链乘法

------------------------------------------------

可能大家现在还是有点模糊,下面我们再举一个例子来详细说明动态规划算法流程。

想必大家都学过线性代数,也熟悉矩阵,如果忘了的话也不要紧,下面的问题只涉及到矩阵的乘法。先看题。

问题:给定一个矩阵序列〈A1, A2, ..., An〉做乘法,求如何给他们添加上括号,使得操作数最少?

分析:可能你还有点看不懂题目,我们下面来解释下,先看看两矩阵A,B相乘的代码。

MATRIX-MULTIPLY(A, B)
1 if columns[A] ≠ rows[B]
2     then error "incompatible dimensions"
3     else for i ← 1 to rows[A]                      //   rows[A]
4               do for j ← 1 to columns[B]           //   col[B]
5                       do C[i, j] ← 0
6                          for k ← 1 to columns[A]   //   col[A]
7                               do C[i, j] ← C[i, j] + A[i, k] · B[k, j]
8          return C

从上面的代码中,我们可以看出两个矩阵AB相乘,他们的操作次数为rows[A]*col[B]*col[A].知道这一点之后,我们看下面:

给出三个矩阵:A是10*30,B是30*5,C是5*60,求(AB)C和A(BC)的操作次数。

(AB)C = (10×30×5) + (10×5×60) = 1500 + 3000 = 4500 operations
A(BC) = (30×5×60) + (10×30×60) = 9000 + 18000 = 27000 operations.
这下你应该清楚了题意吧。好,那我们下面想想怎么来减少它的次数。依然按照动态规划的算法流程来解决问题:

(下面,我不会讲的太细,只说些帮助你理解的东西,最好跟着算法导论一起来看,我看了两个多小时才看懂了,囧了。)

3.1 构造最优结构

算法导论中提到一个临界值K(就是这个弄瞎我的眼),如何来理解这个K值是什么呢?举个例子。A1*A2*A3三个矩阵相乘,

  • 当(A1*A2)*A3时,此时K=2,即表示分界点为A2.
  • 当A1*(A2*A3)时,此时K=1,即表示分界点为A1.

对于通用式,Ai*.....*Aj(i<j)  ,他们之间存在一个临界值K把他们分开,使得他们计算出来的结果最小。

即:(Ai*.....*Ak)*(Ak+1*.....*Aj)

知道K值是什么之后,我们就进入下一环节,递归求出最优解。

3.2 递归求最优解

上面我们知道了临界值K实质就是给一个方程式加括号,当方程式有三个一下值时,很好找到K值,但当方程含有三个以上值的时候该怎么办呢?那就是要利用递归的方法了。

对于Ai*.......*Aj  我们记m(i,j) 1 ≤ i ≤ j ≤ n 为方程的最小值。Px为行数或列数(0 ≤ ≤ n)

对于上面的方程你可能不理解,你随便取i和j的值代入进去试一下就懂了。如下:

m[1,2]=m[1,1]+m[2,2]+P0*P1*P2 = P0*P1*P2

上面的例子只要运算一次,下面的就要多次了。

m[1,3]=min{m[1,1]+m[2,3]+P0*P1*P3 , m[1,2]+m[2,3]+P0*P2*P3}

求的之前要先求m[2,3]和m[1,2]的最小值,求出来了之后,再求上面的最小值。

这下应该清楚了吧?如果还是晕晕乎乎的话,那就看下面的实现部分吧,跟着代码走一遍(我就是这样才理解的).

3.3 计算出最优解

上面我们得出了他的递归式,下面我们就来实现它。

伪代码:

MATRIX-CHAIN-ORDER(p)
 1 n ← length[p] - 1
 2 for i ← 1 to n
 3      do m[i, i] ← 0
 4 for l ← 2 to n      ?l is the chain length.
 5      do for i ← 1 to n - l + 1
 6             do j ← i + l - 1
 7                m[i, j] ← ∞
 8                for k ← i to j - 1
 9                    do q ← m[i, k] + m[k + 1, j] + pi-1 pkpj
10                       if q < m[i, j]
11                          then m[i, j] ← q
12                               s[i, j] ← k
13 return m and s

解释一下:

2-3:将m[i,j],i=j时的值初始化为0.

4:     l其实就是j-i+1,即m[i,j]中元素的个数

5-7:同时移动i和j,保持i,j的距离

8-12:执行的就是递归式中的min()操作,寻找最小的K值,并保存下使得方程最小时,K的值

下面给出java代码,运行下。

 1 // Matrix Ai has dimension p[i-1] x p[i] for i = 1..n
 2 MatrixChainOrder(int p[])
 3 {
 4     // length[p] = n + 1
 5     n = p.length - 1;
 6     // m[i,j] = Minimum number of scalar multiplications (i.e., cost)
 7     // needed to compute the matrix A[i]A[i+1]...A[j] = A[i..j]
 8     // cost is zero when multiplying one matrix
 9     for (i = 1; i <= n; i++)
10        m[i,i] = 0;
11
12     for (L=2; L<=n; L++) { // L is chain length
13         for (i=1; i<=n-L+1; i++) {
14             j = i+L-1;
15             m[i,j] = MAXINT;
16             for (k=i; k<=j-1; k++) {
17                 // q = cost/scalar multiplications
18                 q = m[i,k] + m[k+1,j] + p[i-1]*p[k]*p[j];
19                 if (q < m[i,j]) {
20                     m[i,j] = q;
21                     s[i,j]=k// s[i,j] = Second auxiliary table that stores k
22                                // k      = Index that achieved optimal cost
23
24                 }
25             }
26         }
27     }
28 }

算法导论中还有个例子:


matrix


dimension



A1


30 × 35


A2


35 × 15


A3


15 × 5


A4


5 × 10


A5


10 × 20


A6


20 × 25

此时m[2,5]的计算过程如下。

3.4 得出最佳方案

得出最终的结果,无疑就是把方程式输出来,直接看伪代码:

 1 public class MatrixOrderOptimization {
 2     protected int[][]m;
 3     protected int[][]s;
 4     public void matrixChainOrder(int[] p) {
 5         int n = p.length - 1;
 6         m = new int[n][n];
 7         s = new int[n][n];
 8         for (int i = 0; i < n; i++) {
 9             m[i] = new int[n];
10             m[i][i] = 0;
11             s[i] = new int[n];
12         }
13         for (int ii = 1; ii < n; ii++) {
14             for (int i = 0; i < n - ii; i++) {
15                 int j = i + ii;
16                 m[i][j] = Integer.MAX_VALUE;
17                 for (int k = i; k < j; k++) {
18                     int q = m[i][k] + m[k+1][j] + p[i]*p[k+1]*p[j+1];
19                     if (q < m[i][j]) {
20                         m[i][j] = q;
21                         s[i][j] = k;
22                     }
23                 }
24             }
25         }
26     }
27     public void printOptimalParenthesizations() {
28         boolean[] inAResult = new boolean[s.length];
29         printOptimalParenthesizations(s, 0, s.length - 1, inAResult);
30     }
31     void printOptimalParenthesizations(int[][]s, int i, int j,  /* for pretty printing: */ boolean[] inAResult) {
32         if (i != j) {
33             printOptimalParenthesizations(s, i, s[i][j], inAResult);
34             printOptimalParenthesizations(s, s[i][j] + 1, j, inAResult);
35             String istr = inAResult[i] ? "_result " : " ";
36             String jstr = inAResult[j] ? "_result " : " ";
37             System.out.println(" A_" + i + istr + "* A_" + j + jstr);
38             inAResult[i] = true;
39             inAResult[j] = true;
40         }
41     }
42 }

 

4. 动态规划原理

-----------------------------------------

通过前面的两个例子,大家对动态规划有了初步的理解。下面我们从原理的解读来分析动态规划,标题1中我们讲到动态规划需要维持三种特性最优子结构,子问题重叠,无后效性。

4.1 最优子结构

如果一个问题的最优解包含其子问题的最优解,我们就称此问题具有最优子结构性质。

【例题1】余数最少的路径。
    如图所示,有4个点,分别是A、B、C、D,相邻两点用两条连线C2k,C2k-1(1≤k≤3)表示两条通行的道路。连线上的数字表示道路的长度。定义从A到D的所有路径中,长度除以4所得余数最小的路径为最优路径。
    求一条最优路径。

【分析】在这个问题中,如果A的最优取值可以由B的最优取值来确定,而B的最优取值为(1+3) mod 4 = 0,所以A的最优值应为2,而实际上,路径C1-C3-C5可得最优值为(2+1+1) mod 4 = 0,所以,B的最优路径并不是A的最优路径的子路径,也就是说,A的最优取值不是由B的最优取值决定的,即其不满足最优化原理,问题不具有最优子结构的性质。
    由此可见,并不是所有的“决策问题”都可以用“动态规划”来解决,运用“动态规划”来处理问题必须满足最优化原理。

4.2 重叠子问题

问题利用递归方法会反复地求解相同的子问题,而不是一直生成新的子问题,我们就成该问题是重叠子问题。

在分治方法中通常就每一步都生成全新的子问题,动态规划就是利用重叠子问题性质:对每个子问题球结一次,将解存入一个表中,当再次需要这个子问题时直接查表,每次查表的代价为常数时间。

4.3 无后效性

它是这样一种性质:某阶段的状态一旦确定,则此后过程的演变不再受此前各种状态及决策的影响,简单的说,就是“未来与过去无关”,当前的状态是此前历史的一个完整总结,此前的历史只能通过当前的状态去影响过程未来的演变。具体地说,如果一个问题被划分各个阶段之后,阶段I中的状态只能由阶段I-1中的状态通过状态转移方程得来,与其它状态没有关系,特别是与未发生的状态没有关系。从图论的角度去考虑,如果把这个问题中的状态定义成图中的顶点,两个状态之间的转移定义为边,转移过程中的权值增量定义为边的权值,则构成一个有向无环加权图,因此,这个图可以进行“拓扑排序”,至少可以按它们拓扑排序的顺序去划分阶段。

简单来说就是在状态i求解时用到状态j而状态j求解时用到状态k…..状态N。

而如果求状态N时有用到了状态i这样求解状态的过程形成了环就没法用动态规划解答了,这也是上面用图论理解动态规划中形成的图无环的原因。

也就是说当前状态是前面状态的完美总结,现在与过去无关。
当然,要是换一个划分状态或阶段的方法就满足无后效性了,这样的问题仍然可以用动态规划解。

动态规划问题还有还多,这里就不一一举例子了,如最长公子序列,背包问题,最有二叉搜索树。之后再添加上去,先把上面两个例子理解了。

不得不说动态规划问题好难啊,我ri。

看下这篇博客吧:最大子数组和

时间: 2024-10-01 05:00:51

动态规划相关问题学习笔记的相关文章

由LCS到编辑距离—动态规划入门—算法学习笔记

一切计算机问题,解决方法可以归结为两类:分治和封装.分治是减层,封装是加层. 动态规划问题同样可以用这种思路,分治. 它可以划分为多个子问题解决,那这样是不是用简单的递归就完成了?也许是的,但是这样会涉及太多的不便的操作.因为子问题有重叠! 针对这种子问题有重叠的情况的解决,就是提高效率的关键. 所以动态规划问题可以总结为:最优子结构和重叠子问题. 解决这个子问题的方式的关键就是:memoization,备忘录. 动态规划算法分以下4个步骤: 描述最优解的结构 递归定义最优解的值 按自底向上的方

端口相关知识学习笔记

端口相关知识学习笔记 端口相关知识学习笔记 本周主要精力是放在挂接上,所以知识矩阵的学习回归到根本上,所以这周发的学习笔记是关于计算机端口的相关介绍. 有过一些黑客攻击方面知识的读者都会知道,其实那些所谓的黑客并不是像人们想象那样从天而降,而是实实在在从您的计算机"大门"中自由出入.计算机的" 大门"就是我们平常所说的"端口",它包括计算机的物理端口,如计算机的串口.并口.输入/输出设备以及适配器接口等(这些端口都是可见的),但更多的是不可见的软

PE结构、SEH相关知识学习笔记

原文:http://www.pediy.com/kssd/index.html -- 病毒技术 -- 病毒知识 -- Anti Virus专题 PE结构的学习 原文中用fasm自己构造了一个pe,这里贴一个用masm的,其实是使用WriteFile API将编写的PE数据写成文件~也没啥好说的,PE结构在这里没有仔细介绍,需要可以另外查询,剩下要说的的基本都在代码注释里了 参考:点击打开链接(PEDIY技术之新思路(二)_用'高级'编译器MASM实现自定义PE文件结构) Pe.asm: REMO

高老大 ‘SQL Server 优化器特性导致的内存授予相关BUG’ 学习笔记

今天高老大出了好文章.在这里 自己本来对这一块比较混乱,正好借这个机会学习一下. 就用高老大的脚本.需要的直接去他那里找吧,这里就省了. 加查询优化标记前后对比 可以看到GrantedMemory是504928KB,大约是213096/1024=208.101562MB(这里的那个值好像每次都有差别,但是差距不太大.不影响效果) 加上跟踪标记后 4560/1024大概只有4MB吧. 差别很大吧~ 其实这里这些值还可能通过查询计划的XML里看,里面有更详细的信息. 如 <QueryPlan Deg

OpenSSH服务及其相关_学习笔记

OpenSSH服务及其相关: Telnet    Tcp/23 缺点: 1.认证是明文 2.数据传输明文 ssh:Secure Shell  Tcp/22 Openssh(开源) 协议:sshv1(缺陷,废弃).sshv2 客户端: linux:ssh windows:putty.SecureCRT.SSHSecureShellClient.Xmanager ssh_config        配置文件 服务端: sshd openssh(ssh,sshd) sshd_config       

Maven相关内容学习笔记一:基本配置和使用

首先必须推荐的这本书<Maven实战> 许晓斌,机械工业出版社 Maven简介 其实使用Maven也有很久时间了,大部分都是别人建好了工程我使用一下,实际上并没有非常详细的使用经验,这次到新公司来,逼着自己从头开始搭建一个Maven工程,但有了以前的经验,上手还是很快的. Maven是在Ant之后出现的,能够自动下载构建并管理依赖,这是它与Ant最大的区别.Ant也能实现生命周期的管理,但与Maven相比,付出的成本要更高一下. 安装和配置 下载Maven http://maven.apach

复制相关参数学习笔记--master上的参数

特别声明: 所有的过滤规则不建议在主库上设置.   server_id 是一个整数,范围:1 至 power(2,32)-1 之间. 推荐使用端口号+ip最后一位的方式. 唯一区别ID,同一个集群不可重复,从5.6开始可动态修改. server_uuid 唯一区别ID,同一个集群不可重复,从5.6开始可动态修改. 从5.6开始,系统根据函数自动生成唯一的一个值,默认存放在$datadir/auto.cnf  ,MySQL启动时,会判断auto.cnf文件是否存在,如果不存在auto.cnf文件就

复制相关参数学习笔记--slave上的参数

server_id server_uuid relay_log io_thread 读取过来的本地日志. relaylog文件名前缀,可以是全路径. relay_log_index relaylog索引文件前缀名,和realy_log一样,也可以是fullpath. 不可动态修改. read_only 除非有super权限,否则无法修改数据,不过不影响自主创建临时表.ANALYZE TABLE.OPTIMIZE TABLE也不影响. 可动态修改. replication_same_server_

UINavigationController的创建和相关设置---学习笔记四

导航控制器 一.设置字体大小,背景等. 二.自定义返回按钮. 三.设置手势. 一.导航中也有个appearance属性,通过它可以设置所有导航的颜色. 二.自定义返回按钮. 1.首先需要知道的是,要把一个控制器加到导航上,导航做的操作是首先push到这个控制器上. 2.设置返回按钮的思路:创建按钮->调整按钮内容的边距->设置导航Item的左Item上->导航的子控制器的个数必须大于等于1 三.设置手势. 1.首先设置导航手势的代理. 2.然后再调用手势代理的方法,且必须大于1时才能使用