1. 动态规划
动态规划是通过组合子问题的解从而得到整个问题的解。
1.1. 与分治算法区别
分治法是将问题分解为独立的子问题,递归求解子问题,最后再合并子问题从而得到问题的解。
动态规划并不要求子问题独立,因此子问题可以包含公共的子子问题。这时候如果用分治法,则会重复计算很多不必要的工作,即重复计算公共的子子问题。
1.2. 思想
动态规划是将子子问题的解存放在一张表中,从而在重复需要的时候不必重复求解,而是直接查表。
1.3. 应用场景
动态规划一般用于最优问题的求解。这样的解为该问题的一个最优解,因为最优解可能不止一个。
1.4. 动态规划设计步骤
Ø 描述最优解结构
Ø 递归定义最优解的值
Ø 按自底向上的方式计算最优解的值
Ø 由计算的值构造一个最优解(构造,最优解怎么取值---每一步。)
在职要求最优解值时,可以不用求解最优解的过程。如果需要构造过程,则在自底向上计算时,需要存储信息,以便记录用于构造最优解的过程。
2. 实例分析
2.1. 装配线调度
有两条装配线,装配站个数相同。相同位置上的装配站所做工作相同。
不同点是每条线上每个装配站完成自己的工作所需时间不同;即便不同装配线相对应的装配站,所作相同工作其时间也不同。
为简单,只考虑两条装配线。
2.1.1. 问题:
一般一个订单只在一条装配线上完成任务;如果来了一个紧急订单,可以在两条装配线上进行调度,以便最快完成任务。最快完成需要多少时间?怎样在两条装配线上流转,以便达到最快?
假设在同一条装配线上的一个装配站,流转到本装配线下一个装配站不需要计算时间;但是流转到另一个装配线的下一个对应的装配站需要计算时间。
定义:
每条装配线都有n个装配站。
装配线i的第j个装配站为Sij(i=1,2;j=1,…,n)。
装配站Sij工作所需的时间为aij(i=1,2;j=1,…,n)。
任务进入到第i条装配线的时间为ei(i=1,2);
任务从最后一个装配站完成后离开装配线的时间为xi(i=1,2)。
任务从第i个装配线的第j个装配站,移动到另一个装配线的下一个装配站(j+1)所需时间为tij(i=1,2;j=1,…,n-1)。最后一个装配站出去后不可以再移动到另一个装配线。
2.1.2. 解法:
蛮力解法:是将所有可能的装配方式计算一次,找出用时最少的。所有装配可能为2^n中装配方式,因此时间为O(2^n),n大了后不可行。
动态规划解法:
第一步最优解结构:考虑从起点进入到装配站Sij最快的线路(时间包括aij)。如果j=1,即第一个装配站,则进入线路只有一条,即从开始到Si1。对于j=2,…,n。则进入Sij的最快线路可能为两种情况。一是直接从该装配线的前一个装配站(第j-1装配站)直接进入,则相应的路径也必定是经过Si(j-1)装配站最快的线路;二是从另条装配线的前一个装配站,经过移动操作后移动过来的,则相应的线路必定是经过S(i^)(j-1)(即,经过另一条线路的第j-1个装配线的最快路径)。
因此最优解(Sij的求解)包含了子问题(求解S1(j-1)或S2(j-1))的一个最优解。这种性质称为最优子结构。这个性质决定了问题是否可以用动态规划来分析的第一个条件。
第二步递归解:利用子问题的最优解,递归定义问题的最优解。
定义:
fij为在两条装配线上选择装配站时,进入第i条装配线第j个装配站Sij最快的可能时间(包含在该装配站工作的时间aij)。
问题的解是指,任务通过所有线路的最快时间。记f*为问题最优解的最快时间。则最优解最后一步要么经过S1n并离开,要么经过S2n并离开。因此:
f*=min{f1n+x1,f2n+x2}
并且基础情况为:
f11=e1+a11
f21=e2+a21
由第一步最优解结构,对f1n,f2n的求解也可递归求解。从而整个递归关系为:
f1j = min{f1(j-1)+a1j,f2(j-1)+t2j+a1j} (j>=2).基础情况f11如上。
f2j=min{f2(j-1)+a2j,f1(j-1)+t1j+a2j} (j>=2).基础情况f21如上。
由上式即可求解所有的fij,以及f*。而fij即为子问题的最优解。
为了记录最优解的构造。
定义:
lij(i=1,2;j=2,…n)为装配线的编号,即值为1或2。其中装配站j-1被通过装配站Sij的最快线路使用。
l11,l21是无意义的,因为没有装配站在第一个装配站的前面。
l*为最优解中,最后一次第n个装配站对应的装配线。
lij的构造和fij的构造是同步的。
最优解的构造是从l*逆向构造出来的。从l*找到最后的n的装配线(如l*=1,则最后从装配线的n装配站离开,即经过S1n),由相应的lin找到li(n-1)(如上需由l1n来计算,如果l1n=1,则倒数第二个也为装配线1上的装配站,即经过S1(n-1);否则如果l1n=2,则倒数第二个为装配线2上的装配站,即经过S2(n-1)),以此反推,直到开头。
第三步计算最优解的值:由递归式计算最优解f*即可。
不好的解法,如果基于递归式的计算有个问题,由于递归求解,低阶的f会被重复计算。而且重复计算的次数将是指数级别的,相当于递归时,栈的展开。
动态规划解法,如果在递归的求解中是从低阶的往高阶的方向求解,且求解的值保存在一个表中,则不会有重复计算f的情况。
在计算的过程中由表格记录下所有的fij,lij。
第四步最优解的构造:
方式是由前述所,由l*递归往前求解。打印方式,一按照递减的方式,直接打印即可;二是按递增的方式,需要用递归来求解。
2.1.3. 扩展
fij,lij的空间为4*n-2;可以降低空间为2*n+2,且仍然可以求解出最优解的构造。
2.2. 矩阵链乘法(与矩阵分块乘法的分治算法对比)
矩阵链乘法是计算矩阵列表A1A2A3…An,n个矩阵相乘的结果;Ai并不需要为方阵,只要满足矩阵乘法即可。
2.2.1. 问题
由矩阵乘法的结合性,如果计算时结合的顺序不同,则最后计算的代价将是不同的。而且差别有可能是非常大的。如何求解计算量最小的结合方式?
2.2.2. 数学基础
两个矩阵相乘的计算量为:A1为p*q矩阵,A2为q*r,则乘积结构A1A2需要进行的乘法运算量为:p*q*r
2.2.3. 定义
链乘中A1A2…An,矩阵Ai的维数为p(i-1)*pi(i=1,2…,n)。
最优化算法中并不实际计算矩阵的相乘,而只是确定最优化的相乘的顺序。
2.2.4. 解法
蛮力解法:穷尽法,计算所有加括号,进行结合的方式P(n)。
当n=1时,加括号方式为1种,P(n)=1。
当n>=2时,整个乘积结构可以看作两个加括号的子乘积的乘积;而最后一步划分可能发生在第k个和第k+1个矩阵之间(k=1,2,…n-1)。划分方式
P(n)=Sum(P(k)*P(n-k)),下标k=1,2,…n-1。
P(n)的解是Catalan数,增长为(4^n)/(n^(3/2))。从而为指数级别的,因此不可取。
动态规划解法:
第一步最优加括号的结构:
定义:
记Aij为对AiA(i+1)…Aj的最优解的结果,其中(i<=j)。如果i<j,则其最优解都将在某个k(k=i,..j-1)处即Ak和A(k+1)之间,即最优解为先计算Ai,..Ak,和A(k+1)…Aj,再求这两个结果的乘积。如此,整个Aij的代价即为Aik,A(k+1)j的代价再加上这两个相乘的代价。
最优子结构,如果Aij中,在第k个位置,即Ak和A(k+1)之间的划分将Aij划分为一个最优的,则对Aij的最优的方式中,Aik的最优解即为Aij中最优解的前半部分,且A(k+1)j的最优解,也组成Aij的最优解的后半部分。即Aij的最优解包括Aik,A(k+1)j的最优解。
第二步一个递归解,
mij为Aij中最优解的代价,则整个链相乘的最优解的代价为m1n。
而如果i=j,则为一个矩阵,Aij=Aii。无需进行相乘。因此
mii=0;(i=1,2,…,n)
如果i<j,则由最优解情况,设在第k位置分割,则
mij=mik+m(k+1)j+p(i-1)*pk*pj
当然,这个时候只是假定已知k的位置,但实际还并不知道。但是因为k=i,..j-1,因此最多为计算j-i次,并取最小值即可。从而递归解为:
mij=min{mik+m(k+1)j+p(i-1)*pk*pj} ,k=i,..j-1,i<j.
平凡的i=j时mii=0。
为了记录最优解的构造:
定义:
sij为Aij最优解的分裂位置k。即sij为使得递归式中取值最小的k。
第三步计算最优解的值:最优解的值可以由递归式求解。
不好的解法,直接用递归,则低阶的重复计算,代价为指数级别,因此效率不行。
动态规划解法,由于每一对满足1<=i<=j<=n的i,j对对应一个问题,总共有C(n,2)+n=O(n^2)个子问题。而递归算法在递归树中,可能会多次遇到同一个子问题。因此有子问题的重叠。子问题的重叠性决定了是否可以用动态规划来解的第二个性质。(第一个为最优子结构。)
求解过程也并不是直接用递归,而是使用表格法。
输入序列p=<p0,p1,…,pn>为矩阵的维数数列。数列长度为n+1。并用mij,sij分别表示ij的代价,以及ij的分裂位置k。并用sij来构造最优解的结构。
实现:为了实现自底向上,因此需要确定哪些表项会用来计算mij。由递归式,计算Aij代价mij时,只依赖于两个个数少于j-i+1个的矩阵的乘积。
做法:首先初始化mii为0;再根据Aij的链表的长度,从2到n进行循环,每次计算出所有长度相同的mij。因此计算mij的时候,只依赖已经计算过的mik和m(k+1)j。
循环包括三层,第一层链长l为2,..n,第二层i为1,..n-l+1,j为i+l-1,第三层k为i,…j,这时候根据已经计算的Aik和A(k+1)j,以及递归式来计算最小的代价mij,以及sij。
mij为对角线为0的一个上三角,并将对角线水平放置,A1..An从左到右进行放置;i为从顶点到对角线,从右边由1增加到n;j为从顶点到对角线,从左边右n减少到1。每次循环计算一行的值,顶点的值即为m1n。中间的值则为mij。
Sij为没有对角线的上三角,比mij少一行,相应的i,j的变化和mij相同。
复杂度,由三层循环,且可以分析这三层的时间复杂度为O(n^3),空间复杂度为O(n^2)。因此比穷举法指数时间高效。
第四步最优解的构造:由sij构造,且由s1n(即s表的顶点),可得最优解最优的一次结合;递归对前后两部分在sij中查询,即可得最终的最优解的构造。
特别:含义n个矩阵的乘积,括号对数为n-1。
2.3. 最长公共子序列(LCS)
应用:找出两条DNA的相似度。找出第三条DNA,其出现在这两条DNA中,且顺序也相同,但可以不连续。
2.3.1. 解法
蛮力法:找出一个链的所有子列,并在另一个链中查找。会有指数级别的复杂度。因此不好。
动态规划解法:
第一步最优子结构,
定义:X的第i个前缀为Xi=<x1,x2,…,xi>,i=0,1,..n。并令X0=0。
X=(x1,x2…xm),Y=(y1,y2…yn)。最优解Z(z1,z2…zk)为X,Y的最优解,则
如果xm=yn,则最优解和解X=(x1,..xm-1)与Y=(y1,..ym-1)的最优解一直。
(如果xm于yn不等,则由Z为最优解,则比至为X,Y中一个的元素。)
这时候如果xm与zk不同,则最优解的子解Z(z1,..z(k-1))一定为Xm-1与Yn的一个LCS.
同理,如果yn与zk不同,则最优解的子解Z(z1,..z(k-1))一定为Xm与Y(n-1)的一个LCS.
第二步递归解
定义cij为子序列Xi和Yj的最优解的长度。则
Cij=0;i=0或j=0时。
Cij=c(i-1)(j-1) + 1 ;i>0,j>0且xi=yj。
Cij=max{c[i,j-1],c[i-1][j]};i>0,j>0且xi与yi不相等。
第三步计算最长公共子序列的长度值,使用cij来填入表中,定义b(1..m)(1…n)
第四步构造一个最优解。
2.4. 最优二叉查找树(霍夫曼树)
3. 动态规划的基础(即应用条件)
3.1. 动态规划的使用需要两个条件:
3.1.1. 一是存在最优子结构(即问题的最优解包括子问题的最优解。具有最优子结构时,只是有可能使用动态规划解决,这时候使用贪心算法也可以),动态规划中使用子问题的最优解来构造整个问题的最优解,因此子问题的最优解必须也是构成整个问题最优解的一部分。
最优子结构的寻找时,有一定的模式:
Ø 问题的最优解为一种选择,在不同的情况下来选择最好的那个。因此可能依赖于多个子问题。
Ø 如果已知一个最优解是需要一个选择,这时候可以不必知道具体的选择,先默认已知选择的值。
Ø 已知选择后,要确定哪些子问题会随之发生,以及如何来描述这些子问题的空间。
Ø 证明最优解的选择中,使用的子问题的解也是子问题的最优先的。
子问题数和每个子问题可能的选择数是不同的概念。且总的复杂度会依赖于这两个数。
应用比较:
最短路径和最长路径。
最短路径(u到v)有最优解子结构。假设中间点为w,则最短路径可以分解为子问题(u,w)和(w,v),且也为最短路径。从而存在最优解子结构。
最长路径(u到v)无最优解子结构。(且为NP问题)因为最长路径中,由中间点w划分的路径后,子问题(u,w)和(w,v)不一定是最长路径。原因在于这时候的子问题不独立,独立的含义是:一个子问题的解不会影响另一个子问题的解,即子问题不会共享资源,从而在合并时出现问题。
3.1.2. 重叠子问题
子问题的空间要“很小”,即用于解问题的子问题要可以重复利用,而不是会产生新的子问题。也即,不同的子问题数为输入规模的多项式级别。
特别要注意,分治法一般会产生新问题。
3.1.3. 最优解的构造,备忘录法。