《计算机算法设计与分析》课程第三章 动态规划

 

什么是动态规划?

  • dynamic programming is a method for solving a complex problem by breaking it down into a collection of simpler subproblems.

       上面是维基百科对于动态规划的解释。个人理解,简单来说,动态规划就是把原问题分解成若干子问题,分解到子问题足够小,可以直接解决;通过先求解子问题,将每次求解子问题的结果存下来,减少重复计算;再根据子问题的结果反推原问题,得到原问题的解。

动态规划的基本要素

1.最优子结构性质

        由局部子问题的最优解可以推出全局的最优解。

        一个例子:全校一共有10个班级,我已经知道了每个班身高最高的是谁,现在要找到全校身高最高的那个人,是不是他一定在每个班身高最高的人当中。

       这个问题即含有最优子结构性质,在这个问题中,寻找每个班身高最高的人即为子问题,我知道了每个子问题的最优解,即可由该最优解推出,全校身高最高的人这个原问题最优解。

       现在我们换个问题,现在我要找到全校学生的最大身高差,若已经知道了每个班的最大身高差(子问题最优解),能直接通过每个班的最大身高差得到全校的最大身高差(原问题最优解)吗?

       显然,不能!

      在这个问题中,原问题的最优解不一定由子问题最优解推出,如全校的最大身高差可能由1班最高的同学和2班最矮的同学形成。

      全校的最大身高差可以出现在两个班内,无法由局部最优解推出全局最优解,故不满足最优子结构性质。

     但这个问题真的没有最优子结构吗?思考一下,最大身高差即最高减去最低,那么是不是我们维护处全校的最高身高和最低身高,就可以知道最大身高差了!而全校的最高身高和最低身高是满足最优子结构的。换一个着眼点,就可能发现问题不同的性质。

2.重叠子问题性质

      一个问题必须拥有重叠子问题,才能使用动态规划求解。

      重叠子问题:一个问题可以被分为若干个子问题,且这些子问题会重复出现。 

       如,计算斐波那契数列fib[10]时,需计算fib[9]和fib[8];而计算fib[9]时,又需要计算fib[8],fib[8]即是重叠子问题

3.无后效性

       从不同的路径走到一个共同状态,而后续的状态变迁都是一样的,和之前采用何种路径到这个状态没有关系,即前面的各种决策结果由这个状态表示,在考虑后半段的决策方面没有任何区别。

动态规划解题步骤

1.找出最优解的性质,并刻画其结构特征

       分析问题,通过使用Cut&Paste法判断问题是否具有最优子结构,并写出dp数组。

2.递归地定义最优解

       通过dp数组,写出状态转移方程。

3.以自底向上的方式,或备忘录法(记忆化搜索)计算最优解

4.根据计算最优解时得到的信息构造最优解

动态规划实现的两种形式

 (以求解斐波那契数列为例)

1.自底向上的动态规划

        自底向上方法是先计算子问题,再由子问题计算父问题,利用数组保存了先计算的值,为后面的调用服务。

        在求斐波那契数列时,我们很容易就可以得到f[i]=f[i-1]+f[i-2],若要求f[n],我们可以先算出f[1],f[2],f[3]......,以此递推到f[n]。

2.自顶向下的备忘录法(记忆化搜索)

        备忘录法通过递归方式实现,创建了一个数组来保存求出的斐波拉契数列中的每一个值,在递归的时候如果发现前面f(n)的值计算出来了就不再计算,如果未计算出来,则计算出来后保存在数组中,下次在调用f(n)的时候就不会重新递归了。

        一般而言,由于备忘录法使用了递归,递归的过程中会产生额外的开销,因此自底向上的动态规划性能一般优于备忘录法;但有些时候,状态转移时需要的状态难以通过循环直接得到,此时使用备忘录法可灵活解决。

算法实例

1.矩阵链乘问题

       计算矩阵链A[1:n]的最优计算方式,包含了子矩阵链A[1:k]和A[k+1:n]的最优计算方式。

       类似于区间dp思路,列出状态转移方程:f[i][j]=0,i=j;

       f[i][j]=min(f[i][j],f[i][k]+f[k+1][j]+pi-1*pk*pj,i<j;

       假设是对矩阵链A[i:j]一分为二得到最优解时的断开位置,则f[i][k]和f[k+1][j]和分别是两个子矩阵链A[i:k]和A[k+1:j]的最优解。两个矩阵最后要相乘才能得到A[i:j],因此,最后要加上pi-1pkpj,也就是两子矩阵相乘的数乘次数,才能得到总的数乘次数。

2.最长公共子序列

       设两字符串为a,b,则dp数组f[i][j]表示有a的前i个字符和b的前j个字符所形成的最长公共子序列的长度;

       状态转移方程:f[i][j]=f[i-1][j-1]+1,a[i]=b[j];

       F[i][j]=max(f[i-1][j],f[i][j-1]),a[i]!=b[j];

       时间复杂度:O(n^2);

3.0-1背包问题

       设dp数组f[i][j]为前i件物品放入体积为j的背包所产生的最大价值;

       状态转移方程:f[i][j]=f[i-1][j],j<w[i];

       F[i][j]=max(f[i-1][j-w[i]]+v[i],f[i-1][j]),j>=w[i];

       若一共n个物品,背包容量为m,则需枚举各个物品和各个体积,时间复杂度:O(nm);

       观察发现01背包问题的状态转移方程中第i个物品的状态仅由第i-1个物品的状态得到,与之前的状态无关,造成了空间的浪费,对dp数组进行优化,设f[i]为n个物品为放入体积为j的背包所产生的最大价值;由于每个物品只能选择一次,在遍历体积时需从大到小枚举;

       生成最优解过程通过pass[i]表示产生i的价值的最后一件物品是谁递归得到;

注:本博客仅为个人对于《计算机算法设计与分析》课程所学知识点的整理及个人的一些理解,可能会有不严谨的地方,请读者自酌。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值