记忆和动态编程之间有什么区别?


Answers:


366

有关Programming.Guide的相关文章:动态编程vs记忆和列表


记忆和动态编程之间有什么区别?

记忆是一个描述优化技术的术语,您可以在其中缓存先前计算的结果,并在再次需要相同的计算时返回缓存的结果。

动态编程是一种迭代解决递归性质问题的技术,适用于子问题的计算重叠的情况。

动态编程通常使用制表来实现,但也可以使用备注来实现。因此,您可以看到,任何一个都不是另一个的“子集”。


一个合理的后续问题是:制表(典型的动态编程技术)和备注之间有什么区别?

当使用制表法解决动态编程问题时,您将解决“ 自下而上 ” 的问题,即首先解决所有相关的子问题,通常是填写n维表。根据表中的结果,然后计算“顶部” /原始问题的解决方案。

如果使用备忘录来解决问题,则可以通过维护已经解决的子问题的图来解决。从“先自上而下 ”的意义上讲,您是“自上而下”的(通常向下递归以解决子问题)。

此处的幻灯片很好(链接现已消失,但幻灯片仍然很好):

  • 如果必须至少一次解决所有子问题,那么自下而上的动态编程算法通常比自上而下的固定算法要好一个常数。
    • 没有递归开销,而维护表的开销更少
    • 对于某些问题,可以利用动态编程算法中的常规表访问模式来进一步减少时间或空间需求
  • 如果根本不需要解决子问题空间中的某些子问题,则记忆化解决方案的优点是仅解决那些绝对需要的子问题

其他资源:


1
您交换了动态编程和备忘。基本上,记忆化是一种递归动态编程。
user1603602 2014年

6
不,我想你错了。例如,维基百科关于记忆的文章中没有任何内容说使用记忆​​时必须涉及递归。
aioobe 2014年

看了答案,如果你想感受NZT-48对上述的效果,你可以浏览一下文章例子,以及
信噪比

45

动态编程是一种算法范式,它通过将给定的复杂问题分解为子问题来解决,并存储子问题的结果以避免再次计算相同的结果。

http://www.geeksforgeeks.org/dynamic-programming-set-1/

记忆化是跟踪先前解决的解决方案的一种简便方法(通常以哈希键值对的形式实现,这与通常基于数组的制表相反),这样在再次遇到它们时就不会重新计算它们。它可以在自下而上或自上而下的方法中使用。

请参阅有关记忆与制表的讨论

因此,动态编程是一种通过解决递归关系/递归并通过列表或备注存储先前找到的解决方案来解决某些类别问题的方法。记忆化是一种跟踪先前解决的问题的解决方案的方法,可以与对给定输入集具有唯一确定性解决方案的任何功能一起使用。


14

动态编程通常被称为记忆化!

  1. 记忆化是自上而下的技术(通过分解解决给定的问题),动态编程是自下而上的技术(从琐碎的子问题开始解决给定的问题)

  2. DP通过从基本案例开始找到解决方案,然后向上解决。DP解决了所有子问题,因为它自下而上

    与“记忆化”不同,“记忆化”仅解决所需的子问题

  3. DP具有将指数时间的蛮力解转换为多项式时间算法的潜力。

  4. DP可能更有效率,因为它具有迭代性

    相反,由于递归,记忆化必须支付(通常是很大的)开销。

更简单地说,“记忆化”使用自顶向下的方法来解决问题,即从核心(主要)问题开始,然后将其分解为子问题,并类似地解决这些子问题。在这种方法中,相同的子问题可能会多次发生并消耗更多的CPU周期,因此会增加时间复杂度。而在动态编程中,相同的子问题不会被多次解决,但是先前的结果将用于优化解决方案。


10

(1)从概念上来说,记忆和DP 实际上是同一回事。因为:考虑DP的定义:“重叠子问题”“和最佳子结构”。备注完全具备这些2。

(2)记忆是DP,具有递归深度的堆栈溢出风险。DP自下而上没有此风险。

(3)记住需要一个哈希表。这样就增加了空间,并增加了一些查找时间。

所以要回答这个问题:

- 从概念上讲,(1)表示它们是同一件事。

-考虑到(2),如果真的需要,备忘录是DP的子集,从某种意义上说,备忘录可以解决的问题可以由DP解决,但是DP可以解决的问题可能无法通过备忘录解决(因为它可能会堆栈溢出)。

-考虑到(3),它们的性能差异很小。


6

从维基百科:

记忆化

在计算中,记忆是一种优化技术,主要用于通过使函数调用避免对先前处理的输入重复结果的计算来加速计算机程序。

动态编程

在数学和计算机科学中,动态编程是一种通过将复杂问题分解为更简单的子问题来解决它们的方法。

当将一个问题分解为较小/较简单的子问题时,我们经常遇到相同的子问题而不是一次—因此,我们使用“记忆化”来保存先前计算的结果,因此无需重复进行。

动态编程经常遇到使用记忆的合理情况,但是您可以使用其中一种技术而不必使用另一种。


我发布答案后,OP编辑了问题。最初的问题是问两者之间有什么区别。
yurib

4

记忆化和动态编程都只能解决单个子问题一次。

记忆使用递归并自上而下进行,而动态编程以相反的方向进行,从而解决了自下而上的问题。

以下是一个有趣的类比-

自上而下 -首先您说我将接管世界。你会怎么做?你说我先接管亚洲。你会怎么做?我将首先接管印度。我将成为德里等的首席部长。

自下而上 -您说我将成为德里的CM。然后将接管印度,然后接管亚洲所有其他国家,最后我将接管世界。


3

我想一个例子

问题:

您正在爬楼梯。它需要n步才能到达顶部。

每次您可以爬1或2步。您可以通过几种不同的方式登顶?

在此处输入图片说明

带记忆的递归

通过这种方式,我们在备忘录数组的帮助下修剪(从树或灌木中去除多余的材料)递归树,并将递归树的大小减小到nn。

public class Solution {
    public int climbStairs(int n) {
        int memo[] = new int[n + 1];
        return climb_Stairs(0, n, memo);
    }
    public int climb_Stairs(int i, int n, int memo[]) {
        if (i > n) {
            return 0;
        }
        if (i == n) {
            return 1;
        }
        if (memo[i] > 0) {
            return memo[i];
        }
        memo[i] = climb_Stairs(i + 1, n, memo) + climb_Stairs(i + 2, n, memo);
        return memo[i];
    }
}

动态编程

正如我们所看到的,该问题可以分解为子问题,并且包含最优子结构属性,即可以根据子问题的最优解有效地构造其最优解,我们可以使用动态编程来解决该问题。

public class Solution {
    public int climbStairs(int n) {
        if (n == 1) {
            return 1;
        }
        int[] dp = new int[n + 1];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
}

示例取自https://leetcode.com/problems/climbing-stairs/


2

只是想两种方式,

  1. 我们将较大的问题分解为较小的子问题-自上而下的方法。
  2. 我们从最小的子问题开始,再到更大的问题-自下而上的方法。

Memoization中,我们使用(1.),将每个函数调用保存在缓存中并从那里进行回调。由于涉及到递归调用,所以它有点贵。

动态编程中,我们使用(2.),其中我们维护一个表,通过使用表中保存的数据(通常称为dp-table)解决子问题,从而解决了问题。

注意:

  • 两者都适用于重叠子问题。

  • 由于递归函数调用中涉及的开销,因此记忆对DP的执行相对较差。

  • 渐近时间复杂度保持不变。

0

动态编程中

  • 没有递归的开销,有较少的维护表开销。
  • 表访问的常规模式可用于减少时间或空间要求。

背诵

  • 某些子问题不需要解决。
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.