• 动态规划算法


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

    斐波那契数列:
      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"))

    五 动态规划

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

    最优⼦结构

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

    重叠⼦问题

  • 相关阅读:
    springcloud(Feign)三
    Hibernate @TableGenrator表管理主键生成策略
    Hibernate @Entity注解配置说明
    Hibernate @SequenceGenerator的注解介绍
    java StrutsTypeConverter的使用
    structs2 DefaultTypeConverter类型转换说明
    Struts2(XWork)拦截器的功能介绍:
    structs2 防止表单的重复提交token
    Structs ActionProxy深度阅读
    Structs2 request_locale国际化介绍
  • 原文地址:https://www.cnblogs.com/harryblog/p/10763877.html
Copyright © 2020-2023  润新知