记忆化和动态编程有啥区别?
Posted
技术标签:
【中文标题】记忆化和动态编程有啥区别?【英文标题】:What is the difference between memoization and dynamic programming?记忆化和动态编程有什么区别? 【发布时间】:2011-09-05 07:20:40 【问题描述】:记忆化和动态编程有什么区别?我认为动态编程是记忆的一个子集。对吗?
【问题讨论】:
【参考方案1】:Programming.Guide相关文章:Dynamic programming vs memoization vs tabulation
记忆化和动态编程有什么区别?
记忆化是一个描述优化技术的术语,您可以缓存以前计算的结果,并在再次需要相同的计算时返回缓存的结果。
动态规划是一种迭代解决递归性质问题的技术,适用于子问题的计算重叠时。
动态编程通常使用制表实现,但也可以使用记忆化来实现。如您所见,两者都不是另一个的“子集”。
一个合理的后续问题是:制表(典型的动态编程技术)和记忆化有什么区别?
当您使用制表解决动态规划问题时,您解决的问题是“自下而上”,即首先解决所有相关的子问题,通常通过填写 n em>维表。然后根据表中的结果计算“顶部”/原始问题的解决方案。
如果您使用记忆化来解决问题,您可以通过维护已经解决的子问题的地图来做到这一点。您执行“自上而下”,因为您首先解决“***”问题(通常递归解决子问题)。
来自 here 的一张好幻灯片(链接现已失效,但幻灯片仍然不错):
如果所有子问题必须至少求解一次,自下而上的动态规划算法通常优于自上而下的记忆算法一个常数因子 没有递归开销,维护表开销也更少 对于一些问题,可以利用动态规划算法中的常规表访问模式来进一步减少时间或空间需求 如果子问题空间中的某些子问题根本不需要解决,则记忆解决方案的优势在于只解决那些绝对需要的子问题
其他资源:
***:Memoization, Dynamic Programming 相关问题解答:Memoization or Tabulation approach for Dynamic programming【讨论】:
你交换了动态编程和记忆。基本上,Memoization 是一种递归动态规划。 不,我想你错了。例如,关于记忆化的***文章中没有任何内容说使用记忆化时必然涉及递归。 看了答案,如果你想感受NZT-48对主题的影响,你也可以看一下the article和the example 对不起,我之前没有正确阅读您的回答,但现在我无法取消我的反对票。【参考方案2】:动态编程是解决给定问题的算法范式 通过将复杂问题分解为子问题并存储结果 子问题,以避免再次计算相同的结果。
http://www.geeksforgeeks.org/dynamic-programming-set-1/
记忆是一种简单的方法来跟踪以前解决的解决方案(通常实现为哈希键值对,而不是通常基于数组的制表),因此当它们再次遇到时不会重新计算。它可以用于自下而上或自上而下的方法。
请参阅this discussion,了解记忆与制表。
因此,动态编程是一种通过解决递归关系/递归并通过制表或记忆存储先前找到的解决方案来解决某些类别问题的方法。记忆是一种跟踪先前解决的问题的解决方案的方法,并且可以与任何对给定输入集具有唯一确定性解决方案的函数一起使用。
【讨论】:
【参考方案3】:记忆化和动态规划都只解决单个子问题一次。
记忆化使用递归并自上而下工作,而动态编程则相反,自下而上解决问题。
下面是一个有趣的类比——
自上而下 - 首先你说我将接管世界。你会怎么做?你说我先占领亚洲。你会怎么做?我将首先接管印度。我将成为德里的首席部长,等等等等。
自下而上 - 你说我会成为德里的 CM。然后将接管印度,然后是亚洲的所有其他国家,最后我将接管世界。
【讨论】:
喜欢这个比喻! 我也是,这实际上是对一般生活的好建议。先专业化,然后看看它为你打开了哪些大门。 这是另一个有趣的类比,使用儿童数数和 Gazini 遗忘/记忆:youtu.be/p4VRynhZYIE【参考方案4】:动态编程通常称为记忆!
记忆是自上而下的技术(通过分解来解决给定的问题),动态规划是自下而上的技术(从琐碎的子问题开始解决,向上解决给定的问题)
DP 通过从基本案例开始并向上工作来找到解决方案。 DP解决了所有的子问题,因为它是自下而上的
不像记忆,它只解决需要的子问题
DP 具有将指数时间蛮力解决方案转换为多项式时间算法的潜力。
DP可能更有效,因为它是迭代的
相反,记忆化必须支付由于递归而产生的(通常是重大的)开销。
为了更简单, 记忆使用自上而下的方法来解决问题,即它从核心(主要)问题开始,然后将其分解为子问题并类似地解决这些子问题。在这种方法中,相同的子问题可能会发生多次并消耗更多的 CPU 周期,从而增加时间复杂度。而在动态规划中,相同的子问题不会被多次求解,而是会使用先前的结果来优化解决方案。
【讨论】:
【参考方案5】:(1) 记忆和 DP,在概念上,实际上是一回事。因为:考虑DP的定义:“重叠子问题”“和最优子结构”。 Memoization 完全具备这 2 个。
(2) 记忆是DP,有堆栈溢出的风险,递归很深。 DP 自下而上没有这种风险。
(3) 记忆化需要一个哈希表。所以额外的空间和一些查找时间。
所以回答这个问题:
-概念上,(1) 表示它们是同一个东西。
-考虑到(2),如果你真的想要,memoization 是 DP 的一个子集,从某种意义上说,可以通过 memoization 解决的问题将可以通过 DP 解决,但是可以通过 DP 解决的问题可能无法通过 memoization 解决(因为它可能会堆栈溢出)。
-考虑到 (3),它们在性能上有细微差别。
【讨论】:
【参考方案6】:来自***:
记忆
在计算中,记忆是一种主要使用的优化技术 通过函数调用避免重复来加速计算机程序 计算先前处理的输入的结果。
动态编程
在数学和计算机科学中,动态规划是一种方法 通过将复杂问题分解为更简单的问题来解决它们 子问题。
在将问题分解为更小/更简单的子问题时,我们经常会多次遇到相同的子问题 - 因此我们使用记忆化来保存先前计算的结果,因此我们不需要重复它们。
动态编程经常遇到使用 memoization 有意义的情况,但您可以使用其中一种技术而不必使用另一种。
【讨论】:
OP 在我发布答案后编辑了问题。原始问题询问两者之间的区别。【参考方案7】:我想使用example;
问题:
你正在爬楼梯。到达顶部需要 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/
【讨论】:
【参考方案8】:想想两种方法,
-
我们将较大的问题分解为较小的子问题 - 自上而下的方法。
我们从最小的子问题开始,到达更大的问题 - 自下而上的方法。
在 Memoization 中,我们使用 (1.) 将每个函数调用保存在缓存中并从那里回调。它有点昂贵,因为它涉及递归调用。
在动态规划中,我们使用 (2.) 维护一个表,通过使用保存在表中的数据(通常称为 dp 表)解决子问题自下而上。
注意:
两者都适用于重叠子问题的问题。
由于递归函数调用期间涉及的开销,Memoization 对 DP 的执行相对较差。
渐近时间复杂度保持不变。【讨论】:
【参考方案9】:动态编程 (DP) 和memoization 之间有一些相似之处,在大多数情况下,您可以通过memoization 来实现动态编程过程,反之亦然。但它们确实存在一些差异,您应该在决定使用哪种方法时检查它们:
记忆是一种自上而下的方法,在此过程中,您将一个大问题分解为具有相同属性的较小尺寸的子问题,当尺寸足够小时,您可以通过暴力破解轻松解决它。 动态规划是一种自下而上的方法,您首先计算小案例的答案,然后用它们构造大案例的答案。 在编码过程中,通常通过递归实现记忆,而动态规划通过迭代进行计算。因此,如果您仔细计算了算法的空间和时间复杂度,使用动态编程风格的实现可以为您提供更好的性能。 确实存在使用记忆化具有优势的情况。动态编程需要计算每一个子问题,因为它不知道将来哪一个有用。但是记忆化只计算与原始问题相关的子问题。有时你可能会设计一个理论上有大量 dp 状态的 DP 算法。但是通过仔细分析,您会发现只会使用可接受的数量。在这种情况下,最好使用记忆化来避免大量的执行时间。【讨论】:
【参考方案10】:在动态编程中,
没有递归开销,维护表的开销更少。 表访问的常规模式可用于减少时间或空间要求。在记忆中,
有些子问题不需要解决。【讨论】:
我想把它看作是一本对数表和一个计算器之间的区别。一个执行“动态”计算,而另一个只是查找它们,因此速度非常快(并且在过去已经主动预先计算,因为我们知道它会对某人有用)。【参考方案11】:这是一个来自Fibonacci Number问题的Memoization和DP示例,用Java编写。
这里的动态编程不涉及递归,因为它不受执行堆栈的限制,结果更快并且可以计算更高的值。
public class Solution
public static long fibonacciMemoization(int i)
return fibonacciMemoization(i, new long[i + 1]);
public static long fibonacciMemoization(int i, long[] memo)
if (i <= 1)
return 1;
if (memo[i] != 0)
return memo[i];
long val = fibonacciMemoization(i - 1, memo) + fibonacciMemoization(i - 2, memo);
memo[i] = val;
return val;
public static long fibonacciDynamicPrograming(int i)
if (i <= 1)
return i;
long[] memo = new long[i + 1];
memo[0] = 1;
memo[1] = 1;
memo[2] = 2;
for (int j = 3; j <= i; j++)
memo[j] = memo[j - 1] + memo[j - 2];
return memo[i];
public static void main(String[] args)
System.out.println("Fibonacci with Dynamic Programing");
System.out.println(fibonacciDynamicPrograming(10));
System.out.println(fibonacciDynamicPrograming(1_000_000));
System.out.println("Fibonacci with Memoization");
System.out.println(fibonacciMemoization(10));
System.out.println(fibonacciMemoization(1_000_000)); //*** exception
【讨论】:
以上是关于记忆化和动态编程有啥区别?的主要内容,如果未能解决你的问题,请参考以下文章
三角函数的基本公式与积化和差公式(附积化和差公式的记忆口诀)