一 从斐波那契数列看动态规划

斐波那契数列:
  Fn= Fn−1 + Fn−2
使⽤递归和⾮递归的⽅法来求解斐波那契数列的第n项

1 递归实现

# 子问题的重复计算
def fibnacci(n):
    if n == 1 or n == 2:
        return 1
    else:
        return fibnacci(n - 1) + fibnacci(n - 2)

2 非递归实现

# 动态规划(DP)的思想 = 递推式
def fibnacci_no_recurision(n):
    f = [0, 1, 1]
    if n > 2:
        for i in range(n - 2):
            num = f[-1] + f[-2]
            f.append(num)
    return f[-1]

print(fibnacci_no_recurision(100))

二  钢条切割问题

某公司出售钢条,出售价格与钢条⻓度之间的关系如下表:

问题:现有⼀段⻓度为n的钢条和上⾯的价格表,求切割钢条⽅案,使得总收益最⼤

⻓度为4的钢条的所有切割⽅案如下:(c⽅案最优)

⻓度为n的钢条的不同切割⽅案有⼏种?

 

设⻓度为n的钢条切割后最优收益值为r可以得出递推式:

  • rn = max(pn, r1 + rn!1, r2 + rn!2,··· , rn!1 + r1)

第⼀个参数pn表示不切割

其他n-1个参数分别表示另外n-1种不同切割⽅案,对⽅案i=1,2,...,n-1

  • 将钢条切割为⻓度为i和n-i两段
  • ⽅案i的收益为切割两段的最优收益之和

考察所有的i选择其中收益最⼤的⽅案

三 钢条切割问题之最优子结构

可以将求解规模为n的原问题,划分为规模更⼩的⼦问题:完成⼀次切割后,可以将产⽣的两段钢条看成两个独⽴的钢条切个问题。 
组合两个⼦问题的最优解,并在所有可能的两段切割⽅案中选取组合收益最⼤的,构成原问题的最优解。 
钢条切割满⾜最优⼦结构:问题的最优解由相关⼦问题的最优解组合⽽成,这些⼦问题可以独⽴求解

钢条切割问题还存在更简单的递归求解⽅法

从钢条的左边切割下⻓度为i的⼀段,只对右边剩下的⼀段继续进⾏切割,左边的不再切割

递推式简化为 :

  

 

不做切割的⽅案就可以描述为:左边⼀段⻓度为n,收益为pn,剩余⼀段⻓度为0,收益为r0=0。

(1) 自顶向下递归实现

时间复杂度 O(2n)实现效果差

递归算法由于重复求解相同⼦问题,效率极低

动态规划的思想:

  • 每个⼦问题只求解⼀次,保存求解结果
  • 之后需要此问题时,只需查找保存的结果

p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40]

def cut_rod_recurision(p, n):
    if n == 0:
        return 0
    else:
        res = p[n]
        for i in range(1, n):
            res = max(res, cut_rod_recurision(p, i) + cut_rod_recurision(p, n - i))
    return res


print(cut_rod_recurision(p, 20))


def cut_rod_recurision(p, n):
    if n == 0:
        return 0
    else:
        res = 0
        for i in range(1, n + 1):
            res = max(res, p[i] + cut_rod_recurision(p, n - i))
        return res


print(cut_rod_recurision(p, 20))

 (2) 自底向上递归实现

时间复杂度:O(n2 )

p = [0, 1, 5, 8, 9, 10, 17, 17, 20, 21, 23, 24, 26, 27, 27, 28, 30, 33, 36, 39, 40]

def cut_rop_dp(p, n):
    r = [0]
    for i in range(1, n + 1):
        res = 0
        for j in range(1, i + 1):
            res = max(res, p[j] + r[i - j])
        r.append(res)
    return r[n]


print(cut_rop_dp(p, 20))

(3) 钢条切割重构解

如何修改动态规划算法,使其不仅输出最优解,还输出最优切割⽅案?
对每个⼦问题,保存切割⼀次时左边切下的⻓度

 

四 最⻓公共子序列

个序列的⼦序列是在该序列中删去若⼲元素后得 到的序列。
例:“ABCD”和“BDF”都是“ABCDEFG”的⼦序列

最⻓公共⼦序列(LCS)问题:给定两个序列X和Y,求X和Y⻓度最⼤的公共⼦序列。
例:X="ABBCBDE" Y="DBBCDB" LCS(X,Y)="BBCD"

应⽤场景:字符串相似度⽐对

 

 最优解的递推式:

c[i,j]表示Xi和Yj的LCS⻓度

  

 例如:要求a="ABCBDAB"与b="BDCABA"的LCS:

由于最后⼀位"B"≠"A":因此LCS(a,b)应该来源于LCS(a[:-1],b)与LCS(a,b[:-1])中更⼤的那⼀个

 

计算公共最长子序列长度实现代码:

def lcs_length(x, y):
    m = len(x)
    n = len(y)
    c = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if x[i - 1] == y[j - 1]:  # i j位置上的字符匹配的时候, 来自于左上方+1
                c[i][j] = c[i - 1][j - 1] + 1
            else:
                c[i][j] = max(c[i - 1][j], c[i][j - 1])
    return c[m][n]
print(lcs_length("ABCDBAB", "BDCABA"))

实现输出最⻓公共⼦序列的值

def lcs(x, y):
    '''
    记录最长公共子序列路径
    :param x:
    :param y:
    :return:
    '''
    m = len(x)
    n = len(y)
    c = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
    b = [[0 for _ in range(n + 1)] for _ in range(m + 1)]  # 1 左上方 2 上方 3 左方
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if x[i - 1] == y[j - 1]:  # i j位置上的字符匹配的时候, 来自于左上方+1
                c[i][j] = c[i - 1][j - 1] + 1
                b[i][j] = 1
            elif c[i - 1][j] > c[i][j - 1]:  # 来自于上方
                c[i][j] = c[i - 1][j]
                b[i][j] = 2
            else:
                c[i][j] = c[i][j - 1]
                b[i][j] = 3
    return c[m][n], b


def lcs_traceback(x, y):
    c, b = lcs(x, y)
    i = len(x)
    j = len(y)
    res = []

    while i > 0 and j > 0:
        if b[i][j] == 1:  # 来自左上方=>匹配
            res.append(x[i - 1])
            i -= 1
            j -= 1
        elif b[i][j] == 2:  # 来自上方=>不匹配
            i -= 1
        else:  # == 来自左方=>不匹配
            j -= 1
    return "".join(reversed(res))


# 输出公共最长子序列
print(lcs_traceback("ABCBDAB", "BDCABA"))

五 动态规划

什么问题可以使⽤动态规划⽅法?

最优⼦结构

  • 原问题的最优解中涉及多少个⼦问题
  • 在确定最优解使⽤哪些⼦问题时,需要考虑多少种选择

重叠⼦问题

posted on 2019-04-24 17:57  cs_1993  阅读(350)  评论(0编辑  收藏  举报