Dynamic Programming(动态规划)

参考文献:算法导论


可以使用动态规划的两个充分条件:

1.最优子结构(一个问题的最优解中包含了子问题的最优解,也可以适用贪心策略)

2.重叠子问题(一个递归树在不同的分支中可能碰到相同的子问题)


DP步骤:

1.描述最优解的结构

2.递归定义最优解的值

3.按自底向上的方式计算最优解的值

4.由计算出的结果构造一个最优解


通过工厂最快路线问题

题设:工厂有2调装配线,每条装配线都有n个装配步骤,两条装配线在执行同一步骤的时间是不同的,同一装配线由上一步骤转移到下一步骤的时间可以忽略,而有一条装配线转移到另一条需要一定的时间



我们可以令fi[j]是我们执行第i条装配线执行第j步的最短时间。

那么此时针对题设,只有两种情况,要么从装配线2转移到装配线1,要么直接从装配线1的上一步转到下一步。


如果是第1条生产线,那么我们很容易会写出一个递归式:f1[j] = min{ f1[j-1]+a1j, f2[j-1]+t2[j-1]+a1j }(t2[j-1]是从第2条装配线的第j-1步移到第一条装配线的第j步所花费的时间,a1[j]是第1条生产线执行第j步的时间)

f1[j-1]+a1j表示从装配线1的j-1步转到j步,此时花费的总时间

f2[j-1]+a1j+t2[j-1]表示从装配线2的j-1步转移到装配线1执行第j步,此时花费的总时间


如果我们使用递归计算,我们可以令调用fi[j]的次数为ri[j]

那么由递归式子,容易得出r1[j] = r2[j] = r1[j+1] + r2[j+1]

如果一条装配线由n步,那么可以得出最初的j11被调用了2^(n-1)次(我们可以建立一个树形模型来讨论递归问题,递归树在《算法导论》第一章有讲过,不是这篇文章的重点),整个算法的时间复杂度是O(2^n)这是无法忍受的低效率。


我们可以简化理解一下递归式,每次计算当前步骤的时间都会用到上一次所计算的时间,那么我们不用每次都从头开始计算,算法可以并行的解每次每条装配线的最优解,那么可以将算法优化为时间复杂度仅为O(n)

算法伪代码

FASTEST-WAY(a, t, e, x, n)

f1[1] <- e1 + a(1,1)
f2[1] <- e2 + a(2,1)
for j <- 2 to n
    do if f1[j-1] + a(1,j) <= f2[j-1]+t(2,j-1)+a(1,j)
            then f1[j] <- f1[j-1]+a(1,j)
                l1[j] <- 1
            else f1[j] <- f2[j-1]+t(2,j-1)+a(1,j)
                l1[j] <- 2
        if f2[j-1]+a(2,j) <= f1[j-1]+t(1,j-1)+a(2,j)
            then f2[j] <- f2[j-1] + a(2,j)
                l2[j] <- 2
            else f2[j] <- f1[j-1]+t(1,j-1)+a(2,j)
                l2[j] <- 1
if f1[n]+x1 <= f2[n] + x2
    then f* <- f1[n] + x1
        l* <- 1
    else f* <- f2[n] + x2
        l* <- 2


矩阵链乘法

题设:给定由n个要相乘的矩阵构成的链<A1 , A2 , A3 , ... , An>
由于矩阵需要相容相乘,例如A1是个i*j的矩阵,A2是j*q的矩阵,则A1*A2的计算次数为i*j*q,
考虑3个矩阵链乘:A1:10*100 , A2:100*5 , A3:5*50
有2种情况:((A1*A2)*A3),(A1*(A2*A3))
第一种情况:10*100*5+10*5*50 = 7500
第二种情况:100*5*50 + 10*100*50 = 75000
则第一种情况是第二种情况计算次数的1/10;我们以标量运算次数来衡量时间,则可以简单理解为第二种情况要比第一种情况慢10倍
我们希望在计算之前找到一种加括号的方式,使得计算次数最少

令一个包含n个矩阵的链所有加括号的可能方案为Pn,我们可以考虑其实该链可以分裂成两个加括号的子链,其分裂位置为k,k可以在第1~n-1个矩阵之后,所以可以写出一个递归式

Pn = ∑P(k)*P(n-k) (k = 1~n-1)

那么我们用递归树的方式来分解这个加括号方案,可以理解为一个压栈和弹栈的过程,压栈次数必须大于弹栈次数,是一个Catalan数序列(Catalan数,我理解为一个古典概率问题,证明有代数和几何方式,个人感觉几何的折现法比较易懂,大家有兴趣可以看看),其解为C(2n,n)/n+1,解该递归式子的时间复杂度为O(2^n)。


我们按照DP四个步骤来分析:

最优解的结构

我们考虑Ai*A(i+1)*...*Aj的矩阵链乘法(记为Ai...j),如果我们从k位置分裂矩阵链,则Ai..k和Ak+1...j,必须是最优的解,因为如果有更优的解,那么更优解必定会替换当前解。

递归定义

令m[i,j]为Ai..j的最优解,则
如果i = j, m[i,j] = 0;
如果i < j, m[i,j] = min{ m[i,k]+m[k+1,j]+p(i-1)pkpj};(k=i...j-1, pi-1*pi表示第i个矩阵的维度)

计算最优解的值

MATRIX-CHAIN-ORDER(p)

n <- length[p]-1
for i <- 1 to n
    do m[i,i] <- 0
for l <- 2 to n
    do for i <- 1 to n-l+1
        do j <- i+l -1
            m[i,j] <- ∞
            for k <- i to j-1
                do q <- m[i,k]+m[k+1,j]+p(i-1)p(k)p(j)
                    if q<m[i,j]
                        then m[i,j] <- q
                            s[i,j] <- k
return m and s

例如:

计算下属矩阵的最小标量计算次数

A1  30*35

A2  35*15

A3  15*5

A4  5*10

A5  10*20

A6  20*25

第一步:计算1-2,2-3,3-4,4-5,5-6的计算次数

m:

  1 2 3 4 5 6
1 0 30*35*15        
2   0 35*15*5      
3     0 15*5*10    
4       0 5*10*20  
5         0 10*20*25
6           0

s:

  1 2 3 4 5 6
1 0 1        
2   0 2      
3     0 3    
4       0 4  
5         0 5
6           0

计算过程:

A1*A2 = m[1,1] + m[2,2] + p0p1p2 = 30*35*15 = 15750


第二步:计算1-3,2-4,3-5,4-6

m:

  1 2 3 4 5 6
1 0 15750 7875      
2   0 2625 4375    
3     0 750 2500  
4       0 1000 3500
5         0 5000
6           0

s:

  1 2 3 4 5 6
1 0 1 1      
2   0 2 3    
3     0 3 3  
4       0 4 5
5         0 5
6           0

计算过程

例如1-3:

(A1*A2)*A3 = m[1,2] + m[3,3] + p0p2p3 = 30*35*15 + 30*15*5 = 18000

A1*(A2*A3) = m[1,1] + m[2,3] + p0p1p3 = 35*15*5 + 30*35*5 = 7875

7875 < 18000

所以A1...3加括号方式为A1*(A2*A3),计算结果为7875


以此类推,最终计算结果

m:

  1 2 3 4 5 6
1 0 15750 7875 9375 11875 15125
2   0 2625 4375 7125 10500
3     0 750 2500 5375
4       0 1000 3500
5         0 5000
6           0

s:

  1 2 3 4 5 6
1 0 1 1 3 3 3
2   0 2 3 3 3
3     0 3 3 3
4       0 4 5
5         0 5
6           0


构造一个最优解

由m表可知:A1...6至少要执行15125次标量计算

由s表得出加括号的结果:

A1...6在3,4之间拆分

A1...3在1,2之间拆分

A4...6在5,6之间拆分

我们可以绘制一颗树来表示括号结构

                  A1...6

                /          \

       A1...3            A4...6

      /        \            /       \

  A1     A2...3    A4        A5...6

           /       \                /        \

        A2      A3          A5        A6

那么可以得出括号结构为:((A1*(A2*A3))*(A4*(A5*A6)))

由m表可知:A1...6至少要执行15125次标量计算

由s表得出加括号的结果:

A1...6在3,4之间拆分

A1...3在1,2之间拆分

A4...6在5,6之间拆分

我们可以绘制一颗树来表示括号结构

                  A1...6

                /          \

       A1...3            A4...6

      /        \            /       \

  A1     A2...3    A4        A5...6

           /       \                /        \

        A2      A3          A5        A6

那么可以得出括号结构为:((A1*(A2*A3))*(A4*(A5*A6)))


你可能感兴趣的:(算法,dp,动态规划)