算法导论笔记:15动态规划

动态规划是一种解决最优化问题的有效方法,尤其在处理子问题重叠的情况。本文以钢条切割和矩阵链乘法为例,详细介绍了动态规划的基本思想、步骤和算法实现。在钢条切割问题中,通过动态规划避免了重复计算,提高了效率;而在矩阵链乘法中,展示了动态规划如何解决指数级别的子问题。这两种问题都体现了动态规划的最优子结构和子问题重叠特性。
摘要由CSDN通过智能技术生成

       动态规划(dynamic programming,这里的programming不是程序,而是表示表格)。它与分治算法类似,都是通过组合子问题的解来求解原问题。分治算法是将原问题分解为互不相交的子问题,递归的求解子问题,然后将解组合起来。

       动态规划则不同,它应用于求解子问题重叠的情况,也就是不同的子问题会涉及相同的子子问题。这样,普通的递归方法会反复的求解那些公共子问题,因而浪费了时间,动态规划则是对公共子问题只求解一次,然后将其解保存在表格中,避免了不必要的重复工作。

 

       动态规划通常用来解决最优化问题,这类问题通常有很多可行解,每个解法都有一个值,希望找到具有最优值(最小值或者最大值)的解。这样的解为一个最优解,有可能会有多个解都能得到最优值。

 

       设计动态规划算法的步骤:

       a:描述一个最优解的结构特征;

       b:递归定义最优解的值;

       c:计算最优解的值,通常采用自底向上的方式计算最优解的值;

       d:利用计算出的信息构造一个最优解。

       第1-3步是动态规划求解问题的基础。如果仅需要一个最优解的值,而非最优解本身,则第4步可以忽略。如果需要求得一个最优解,则有时要在第3步的计算中记录一些附加信息,以便用来构造一个最优解。

 

一:钢条切割问题

       给定一个长度为n的钢条,以及一个价格表p,p中列出了每英寸钢条的价格,将长度为n的钢条切割为若干短钢条出售,求一个钢条的切割方案,使得收益 最大,切割工序没有成本。比如价格表p如下:

 

 

       在该问题中,长度为n的钢条,一共有种不同的切割方案,因为可以再距离钢条左边为i(i=1,2,…,n-1)处,选择切割或者不切割。(类似于一个二进制数),比如下图表示了n=4的切割情况:

      

       在该问题中,对于最优解 , 可以用更短的钢条的最优解来描述:将钢条从左边切割下长度为i的一段,只对剩下的n-i的一段进行继续切割(递归求解),而不对左边长度为i的一段在进行切割。这样对的求解可用下面的公式表示:

        =

这样,原问题的最优解就表示成了子问题的最优解的形式。

 

       为了求解规模为n的原问题,可以先求解形式完全一样,但规模更小的子问题,这样,钢条切割问题满足最优子结构问题的最优解由相关子问题的最优解组合而成,这些子问题可以独立求解。

 

       根据上面的公式,可以写出原始的切割方案:

       CUT-ROD(p,n)

              if  n == 0

                     return  0

              q = -∞

              for  i = 1 to n

                     q = max(q,  p[i]+CUT-ROD(p, n-i))

              return  q

      

       CUT-ROD的效率很差,这是因为CUT-ROD反复的求解一些相同的子问题,下图显示了当n==4时的调用情况:

       下图表示了实际程序运行时,调用cutrod的情况,当n=4时,总共调用cutrod 16次,其中求解相同的子问题若干次:

 

       分析CUT-ROD的运行时间,设T(n)表示第二个参数值为n时,CUT-ROD的调用次数。这个值等于递归调用树中,根为n的子树中的节点总数,T(0)=1。并且:

T(n) = 1 + 。根据数学归纳法可证明:T(n) = 。在递归调用树中,总共有个叶子节点,根到每个叶节点的路径都对应一种可能的切割方案。

 

       可以使用动态规划的方法求解,CUT-ROD的效率低是因为它反复求解相同的子问题,动态规划方法是仔细安排求解循序,对每个子问题只求解一次,并将子问题的结果都保存下来,如果随后在此需要子问题的解,直接取值而不用再次计算。所以,动态规划方法是典型的时空权衡的例子,是用空间换时间。时间上的节省是巨大的,可能将一个指数时间转化为一个多项式时间。

      

       动态规划方法有两种等价的实现方法,两种方法具有相同的渐进时间,仅有的差异是某些特殊情况下,自顶向下的方法没有真正递归考察所有可能的子问题,因为没有使用递归,所以自底向上的方法的时间复杂度函数通常具有更小的系数。


       a:带备忘的自顶向下方法该方法与之前的普通递归方法类似,只是会在过程中保存子问题的解,当需要一个子问题的解的时候,先查看是否已经保存过了,如果是,则直接使用即可。否则,按常规的递归方式计算子问题。所以称为带备忘的,因为它记住了之前已经计算出的结果。

       MEMOIZED-CUT-ROD(p,n)

              let  r[0..n] be a new array

              for  i = 0 to n

                     r[i]= -∞

              return  MEMOIZED-CUT-ROD-AUX(p, n, r)

 

       MEMOIZED-CUT-ROD-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]+MEMOIZED-CUT-ROD-AUX(p, n-i,r))

                     r[n] = q

              return q

       在带备忘的自顶向下的方法中,首先检查所需的值是否已知。

 

 

       b:自底向上的方法:这种方法要恰当定义子问题的规模,使得任意子问题的求解只依赖于“更小的“子问题解。所以通常按照规模从小到大的顺序进行求解。当求救某个子问题时,它所依赖的更小的子问题的解都已经求解完毕了。

       BOTTOM-UP-CUT-ROD(p,n)

              let  r[0..n] be a new array

              r[0]= 0

              for  j = 1 to n

                     q= -∞

                     for  i = 1 to j

                            q = max(q, p[i]+r[j-i])

                     r[j]= q

              return  r[n]

 

       这两种算法具有相同的时间复杂度,BOTTOM-UP-CUT-ROD主要是双层嵌套循环,所以时间复杂度为Θ( )。MEMOIZED-CUT-ROD的时间复杂度也是Θ( )。可以使用子问题图进行分析。


       当思考一个动态规划问题时,应该弄清楚子问题以及子问题之间的依赖关系。子问题图可以准确表达这些信息,比如下图反映了n=4时钢条切割问题的子问题图:

 


       每个顶点对应一个子问题,如果求解子问题x的最优解时需要用到子问题y的最优解,那么图中会有一条x指向y的有向边。这个图可以看成是递归调用树的简化版,因为递归调用树中,相同子问题的结点合并为图中的单一顶点。

 

     子问题图可以帮助我们确定动态规划算法的运行时间,由于每个子问题只求解一次,所以算法运行时间等于每个子问题求解时间之和。通常一个子问题的求解时间与图中该顶点的出度成正比,而子问题的数目等于图中的顶点数。所以,通常情况下,动态规划算法的运行时间与顶点和边的数量呈线性关系。

      

       前面只给出了钢条切割问题的最优解的值(最大收益),并没有切得最优解,也就是最佳切割方案,所以,可以简单的扩展算法,得到最优解:

       EXTENDED-BOTTOM-UP-CUT-ROD(p,n)

              let  r[0..n] and s[0..n] be new arrays

              r[0]= 0

              for j =1 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[n]

 

       PRINT-CUT-ROD-SOLUTION(p,n)

              (r,s)= EXTENDED-BOTTOM-UP-CUT-ROD(p, n)

              while n > 0

                     print s[n]

                     n = n-s[n]

       该方法通过数组s保存切割方案。

 

二:矩阵链乘法

       给定n 个矩阵的序列,希望求它们的乘积: 。因为矩阵的乘法满足结合律,所以可以对n个矩阵序列加括号,来改变乘积顺序。比如对于矩阵链< , ,>可以有下面的加括号方案:

       不同的加括号的方案,对于乘积运算的代价影响很大,两个矩阵相乘,A为p

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值