【算法导论】笔记-第十四章 动态规划——钢条切割问题

第四部分 高级技术和分析技术

第14章 动态规划

  • 动态规划与分治方法相似,都是通过组合子问题的解来求解原问题

    分治方法将问题划分为互不相交的子问题,递归地求解子问题,再将所有解组合起来
    动态规划应用于子问题重叠的情况,即不同子问题具有公共的子子问题
  • 思想:求解每种子子问题,将其解保存在一个表格中。

  • 应用:通常求解最优化问题

    • 步骤:
      • 刻画一个最优解的结构特征
      • 递归地定义最优解的值
      • 计算最优解的值,通常采用自底向上的方法
      • 利用计算出的信息构造一个最优解

14.1 钢条切割

  • 钢条切割问题:给定一段长度为n英寸的钢条和一个价格表 p i ( i = 1 , 2 , ⋅ ⋅ ⋅ , n ) p_i(i=1,2,···,n) pi(i=1,2,,n),求切割方案,使得销售收益 r n r_n rn最大。如果长度为n英寸的钢条的价格 p n p_n pn足够大,则不需要切割。

  • 价格表样例:

    长度 i i i12345678910
    价格 p i p_i pi1589101717202430
  • r i ( i = 1 , 2 , ⋅ ⋅ ⋅ , 10 ) r_i(i=1,2,···,10) ri(i=1,2,,10)对应的最优切割方案:

    i i i12345678910
    切割方式1232+22+361+62+63+610
    r i r_i ri15810131718222530

    更一般地,对于 r n = max ⁡ ( p n , r 1 + r n − 1 , r 2 + r n − 2 , ⋅ ⋅ ⋅ , r n − 1 + r 1 ) r_n=\max(p_n,r_1+r_{n-1},r_2+r_{n-2},···,r_{n-1}+r_1) rn=max(pn,r1+rn1,r2+rn2,,rn1+r1)

  • 求解思路:

    • 解法一:先将钢条切成两条,有n-1种方案,每一种方案的最优解都等于两个子钢条的最优解。我们从这n-1个伪最优解再挑出最优的解
    • 解法二:将长度未n的钢条分解为左边开始的一段,以及剩余部分继续分解的结果。
  • 最优子结构:问题的最优解由相关子问题的最优解组合而成,而这些子问题可以独立求解。钢条切割问题即满足最优子结构性质。

  • 朴素递归算法求解最优钢条切割问题

    • 伪代码:CUT-ROD(p, n):自顶而下的递归实现,p为价格数组
    if n == 0
        return 0
    q = 负无穷
    for i = 1 to n
        q=max(q,p[i]+CUT-ROD(p, n-i))
    return q
    
    • python代码:
    def cut_rod(p, n):
        if n == 0:
            return 0
        q = -1
        for i in range(1, n+1):
            q = max(q, p[i-1] + cut_rod(p, n-i))
        return q
    
    p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
    n = 5
    print(cut_rod(p, n))
    
    • CUT-ROD的缺陷:n每增加1,程序运行时间差不多就会增加1倍
  • 使用动态规划方法求解最优钢条切割问题

    • 思路:仔细安排子问题求解顺序,对每个子问题只求解一次,并将结果保存下来。如果随后再次需要此子问题的解,只需查找保存的结果。

    • 实现方法:

      • 带备忘的自顶而下法:仍按递归形式编写,但过程会保存每个子问题的解。当需要一个子问题的解时,首先检查是否已经保存过此解。如果是,则直接返回保存的值:否则按通常方式计算这个子问题。

        • 伪代码:

          • MEMORIZED-CUT-ROD(p, n):生成备忘录

            let r[0..n] be a new array
            for i = 0 to n
                r[i] = 负无穷
            return MEMORIZED-CUT-AUX(p, n, r)
            
          • MEMORIZED-CUT-AUX(p, n, r)

            if r[n] >= 0
                return r[n]
            if n == 0
                q = 0
            else q = 负无穷
                for i = 1 to n
                    q = max(q, p[i] + MEMORIZED-CUT-AUX(p, n-i, r))
            r[n] = q
            return q
            
        • python代码:

          def memorized_cut_rod(p, n):
              r = [-1] * (n+1)
              return memorized_cut_aux(p, n, r)
          
          def memorized_cut_aux(p, n, r):
              q = -1
              if r[n] >= 0:
                  return r[n]
              if n == 0:
                  q = 0
              else: 
                  for i in range(1, n+1):
                      q = max(q, p[i-1] + memorized_cut_aux(p, n-i, r))
              r[n] = q
              return q
          
          p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
          n = 8
          
          print(memorized_cut_rod(p, n))
          
        • 运行时间: θ ( n 2 ) \theta(n^2) θ(n2)

      • 自底而上法:任何子问题的求解都依赖于“更小的”子问题的求解。因而我们将子问题按规模排序,按由小到大的顺序进行求解

        • 伪代码:BOTTOM-UP-CUT-ROD(p, n)

          let r[0..n] be a new array
          r[0] = 0
          for j = 0 to n
              q = 负无穷
              for i = 1 to j
                  q = max(q, p[i] + r[j-i])
              r[j]=q
          return r[n]
          
        • python代码:

          def memorized_cut_aux(p, n):
              r = ['']*(n+1)
              for j in range(0, n+1):
                  q = 0
                  for i in range(1, j+1):
                      q = max(q, p[i-1] + r[j-i])
                  r[j] = q
              return r[n]
          
          p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
          n = 8
          print(memorized_cut_aux(p, n))
          
        • 运行时间: θ ( n 2 ) \theta(n^2) θ(n2)

  • 重构解(保存最优切割方案)

    • 伪代码:EXTENDED-BOTTOM-UP-CUT-ROD(p, n)

      let r[0..n] be a new array
      r[0] = 0
      for j = 0 to n
          q = 负无穷
          for i = 1 to j
              if q < p[i] + r[j-i]
                  q = p[i] + r[j-i]
                  s[j] = i
          r[j]=q
      return r and s
      
    • python代码:

      def memorized_cut_aux(p, n):
          r = ['']*(n+1)
          s = ['']*(n+1)
          for j in range(0, n+1):
              q = 0
              for i in range(1, j+1):
                  if q < p[i-1] + r[j-i]:
                      s[j-1] = i
                  q = max(q, p[i-1] + r[j-i])   
              r[j] = q
          return r[n],s
      
      p = [1, 5, 8, 9, 10, 17, 17, 20, 24, 30]
      n = 9
      print(memorized_cut_aux(p, n))
      
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

From Star.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值