<动态规划详细理解>

动态规划

在计算机算法学习中,动态规划是不可避免的一个领域。动态规划算法与分治法类似,其基本思想是将待求解问题分解成若干子问题,先求解子问题,再结合这些子问题的解得到原问题的解。 与分治法不同的是,适合用动态规划法求解的问题经分解得到的子问题往往不是相互独立的。若用分治法来求解这类问题,则分解得到的子问题数目太多,以致最后解决原问题需要耗费指数级时间。然而,不同子问题的数目常常只有多项式量级。在用分治法求解时,有些子问题被重复计算了许多次。如果能够保存已解决的子问题答案,在需要时再找出已求得的答案,这样可以避免大量的重复计算,从而得到多项式时间算法。为了达到此目的,可以用一个表来记录所有已解决的子问题的答案。不管该问题以后是否被用到,只要它被计算过,就将其结果填入表中。这就是动态规划算法的基本思想。

动态规划算法适用于解最优化问题,通常可按以下4个步骤设计:

  1. 找出最优解的性质,并刻画其结构特征;
  2. 递归地定义最优解;
  3. 由自底向上的方式计算最优值;
  4. 根据计算最优值时得到的信息,构造最优解。

动态规划算法的有效性依赖于问题本身所具有的两个重要性质:最优子结构性质和子问题重叠性质。从一般意义上讲,问题所具有的这两个重要性质是该问题可用动态规划算法求解的基本要素。这对于在设计求解具体问题的算法时,是否选择动态规划算法具有指导意义。

最优子结构性质

设计动态规划算法的第一步通常是要刻画最优解的结构。当问题的最优解包含了其子问题的最优解时,称该问题具有最优子结构性质。问题的最优子结构性质提供了该问题可用动态规划算法求解的重要线索。

例如在矩阵连乘积最优计算次序问题中注意到,若A1A2…An的最优完全加括号方式在Ak和Ak+1之间将矩阵链断开,则由此确定的子链A1A2…Ak和Ak+1Ak+2…An的完全加括号方式也最优,即该问题具有最优子结构性质。在分析该问题最优子结构性质时,所用的方法具有普遍性。首先假设由问题的最优解导出的其子问题的解不是最优的,再设法说明在这个假设下可构造出比原问题最优解更好的解,从而导致矛盾。

在动态规划算法中,利用问题的最优子结构性质,以自底向上的方式递归地从子问题的最优解逐步构造出整个问题的最优解。算法考察的子问题空间规模较小。例如,在矩阵连乘积最优计算次序问题中,子问题空间由矩阵链的所有不同子链组成。所有不同子链的个数为 θ θ θ( n 2 n^2 n2),因而子问题空间的规模为 θ θ θ( n 2 n^2 n2)。

子问题重叠性质

可用动态规划算法求解的问题应具备的另外一个基本要素是子问题重叠性质。在递归算法自顶向下解此问题时,每次产生的子问题并不总是新问题,有些子问题被反复计算。动态规划算法正是利用了这种子问题重叠性质,对每个子问题只解一次,然后将其解保存在一个表格中,当再次需要解此子问题时,只是简单地用常数时间查看一下结果。通常,不同的子问题个数随着问题的大小呈多项式增长。因此,用动态规划算法通常只需要多项式时间,从而获得较高的解题效率。

备忘录方法

备忘录方法是动态规划算法的变形。与动态规划算法一样,备忘录方法用表格保存已解决的子问题的答案,在下次需要解此子问题时,只要简单地查看该子问题的解答,而不必重新计算。 与动态规划算法不同的是,备忘录方法的递归方式是自顶向下的,而动态规划算法则是自底向上递归的。因此,备忘录方法的控制结构与直接递归方法的控制结构相同,区别在于备忘录方法为每个解过的子问题建立了备忘录以备需要时查看,避免了相同子问题的重复求解。

备忘录方法为每一个子问题建立一个记录项,初始化时,该记录存入一个特殊的值,表示该子问题尚未求解。在求解过程中,对每一个待求解子问题,首先查看其相应的记录项。若记录项中存储的是初始化时存入的特殊值,则表示该子问题是第一次遇到,此时计算出该子问题的解,并保存在其相对应的记录项中,以备以后查看。若记录项中存储的已不是初始化时存入的特殊值,则表示该子问题已被计算过,其相对应的记录项中存储的是该子问题的解答。此时,只要从记录项中取出该子问题的解答即可,而不必重新计算。

一般来说,当一个问题的所有子问题都至少要解一次时,用动态规划算法比用备忘录方法好。此时,动态规划算法没有任何多余的计算。同时,对于许多问题,常常可利用其规则的表格存取方式,减少动态规划算法的计算时间和空间需求。当子问题空间中的部分子问题可不必求解时,用备忘录则较有利,因为从控制结构可以看出,该方法只解那些确实需要求解的子问题。

概念总结

动态规划的特点:

  1. 求解一个问题的最优解
  2. 整体问题的最优解是依赖各个子问题的最优解
  3. 我们把大问题分解成若干小问题,这些小问题之间还有重叠的更小的子问题。
  4. 从上向下分析问题,从下往上求解问题。(应用动态规划解决问题时,总是从解决最小问题开始,并把已解决的子问题的最优解存储下来(一维或者二维数组),并利用子问题的最优解逐步解决大的问题)

常见的动态规划类型有:坐标型动态规划,序列型动态规划,划分型动态规划,区间型动态规划,背包型动态规划,最长序列型动态规划,博弈型动态规划,综合型动态规划等。

下面举斐波那契序列的例子说明使用动态规划动态规划算法效率的提升。

斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……
数学方式表示为:f(n) = f(n-1) + f(n-2)

方法1:使用递归

int fibo(int n){
 	if(n == 1 || n == 2) return 1;
 	return fibo(n-1) + fibo(n-2);
}

如图所示,执行递归计算的时候,会出现许多需要重复计算的子问题。例如计算f(5)中,计算f(2)出现了3次。这种大量的重复计算导致递归代码效率低下。因此,可以使用一个表,将计算过的值保存起来,下次需要时直接存取,不需要重复计算。编程实现时使用一个数组当表即可。因此动态规划算法如下。

方法2:使用动态规划

int fibo(int n){
 	if(n < 1) return -1;
 	int F[n+1];
	F[1] = 1;
 	F[2] = 1;
 	for(int i = 3; i <= n; i++){
  		F[i] = F[i-1] + F[i-2];
 	}
 	return F[n];
}

同样是将原问题划分为一个个子问题,通过子问题的求解得到原问题的解。但不同的是动态规划子问题求解过程中对解的保存,上述求F(6)时直接寻秩找到F(5)和F(4)的解即可得出,而F(4)和F(5)的解向前递推,从而避免大量的重复计算。 这种算法的时间复杂度也得到了很大的提高。由原来的指数级别变为线性级别。当然也可以用滚动数组的方法将代码进行优化,优化后的空间复杂度为常数级别。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值