什么时候可以使用动态编程来减少递归算法的时间复杂度?


13

动态编程可以减少执行递归算法所需的时间。我知道动态编程可以帮助减少算法的时间复杂度。是否存在这样的一般条件,即如果递归算法得到满足,是否意味着使用动态编程会降低算法的时间复杂度?什么时候应该使用动态编程?


Answers:


9

动态编程很有用,因为您的递归算法会发现自己多次遇到相同的情况(输入参数)。从递归算法到动态编程的一般转换是称为备忘录,其中有一个表存储由递归过程计算过的所有结果。在已使用的一组输入上调用递归过程时,仅从表中获取结果。这将递归斐波那契减少为迭代斐波那契。

动态编程可以更加智能,可以应用更具体的优化。例如,有时无需在任何给定时间将整个表存储在内存中。


那么计数器将是,只要备忘录的空间复杂度大于输入数据(也许只是> O(N)),动态编程就无济于事。也就是说,当您很少遇到相同的情况时。
edA-qa mort-ora-y 2012年

1
备注!=动态编程!
拉斐尔

1
我认为我们不是在说这个,但问题是减少了时间复杂度。动态编程本身仅对问题进行分区。动态编程+记忆是在可能的情况下提高时间复杂度的一种通用方法。
edA-qa mort-ora-y

@ edA-qamort-ora-y:对。我认为重要的是要明确指出这一点,因为OP显然混淆/混合了这些概念。
拉斐尔

8

如果您只是想加快递归算法的速度,那么记忆就足够了。这是一种存储函数调用结果的技术,以便将来使用相同参数的调用可以仅重用结果。这适用于(且仅当)您的功能时

  • 没有副作用,并且
  • 确实仅取决于其参数(即不取决于某些状态)。

当(且仅当)一次又一次使用相同的参数调用该函数时,它将节省您的时间。流行的例子包括斐波那契数的递归定义,即

f(0)=0f(1)=1f(n+2)=f(n+1)+f(n), n0

如果天真地求值,通常以指数形式调用。有了备注,始终已经由计算出来,因此仅剩下线性数量的呼叫。˚F Ñ ˚F Ñ + 1 ff(n)f(n+1)

请注意,与之相反,对于诸如合并排序之类的算法而言,记忆几乎是无用的:通常很少(如果有的话)部分列表是相同的,而相等检查则很昂贵(排序仅稍微贵一点!)。

在实际的实现中,如何存储结果对性能至关重要。使用哈希表可能是显而易见的选择,但可能会破坏局部性。如果您的参数是非负整数,则数组是很自然的选择,但如果仅使用某些条目,则可能会导致巨大的内存开销。因此,记忆是效果与成本之间的折衷。它是否有回报取决于您的特定情况。


动态编程完全是另一回事。适用于财产问题

  • 可以将其划分为子问题(可能以多种方式),
  • 这些子问题可以独立解决,
  • 这些子问题的(最佳)解决方案可以与原始问题的(最佳)解决方案组合,
  • 子问题具有相同的属性(或无关紧要)。

当人们引用Bellman的“最优性原理”时,通常(隐式)暗示这一点。

现在,这只是描述了一类的问题,可以通过某种递归来表示。对这些内容的评估(通常)是有效的,因为可以应用备忘录来产生很大的效果(见上文);通常,较小的子问题是许多较大问题的一部分。流行的示例包括编辑距离Bellman-Ford算法


您是说在某些情况下动态编程会导致更好的时间复杂度,但记忆化却无济于事(或至少没有那么多作用)?你有什么例子吗?还是您只是说动态编程仅对记忆化的部分问题有用?
svick

@svick:仅当DP递归使用备注进行评估时(通常是(!)情况),动态编程本身并不会加速任何事情。再说一次:DP是一种根据递归对问题建模的方法,备忘录是一种可以加快合适的递归算法(无论是否使用DP)的技术。直接将两者进行比较是没有意义的。当然,您尝试将问题建模为DP,因为您希望应用备忘录,因此比naive(r)方法可以更快地解决它。但是,DP的观点也不总是导致最有效的算法。
拉斐尔

如果可以使用多个处理器,则动态编程可以并行化部件,因此可以大大提高实际性能。它实际上并没有改变时间的复杂性。
edA-qa mort-ora-y 2012年

@ edA-qamort-ora-y:任何递归都是如此。但是,尚不清楚这能否带来良好的加速效果,因为在处理器边界上记忆效率较低。
拉斐尔

更正:天真的评估DP重复仍然可以比蛮力快很多。cf. 在这里
拉斐尔
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.