理解动态编程的好例子,文章,书籍

时间:2010-11-25 14:36:58

标签: algorithm language-agnostic dynamic-programming

我无法弄清楚动态编程的原理,我确实想要它。 DP非常强大,它可以解决这样的问题:

Getting the lowest possible sum from numbers' difference

那么,你能否建议我好书或文章(最好是带有真实代码的例子),它可以解释我什么是动态编程?我首先想要简单的例子,然后我会继续前进。

14 个答案:

答案 0 :(得分:34)

Dynamic programming是一种有用的算法类型,可用于通过将难题分解为较小的子问题来优化难题。通过存储和重用部分解决方案,它可以避免使用贪婪算法的陷阱。有两种动态编程,自下而上和自上而下。

为了使用动态编程解决问题,问题必须具备所谓的optimal substructure的属性。这意味着,如果问题被分解为一系列子问题并且找到了每个子问题的最优解,那么所得到的解决方案将通过这些子问题的解决方案来实现。没有这种结构的问题不能通过动态编程来解决。

自上而下

自上而下更为人所知的是memoization。这是存储过去计算的想法,以避免每次重新计算它们。

给定一个递归函数,比如说:

fib(n) = 0 if n = 0
         1 if n = 1
         fib(n - 1) + fib(n - 2) if n >= 2

我们可以从数学形式递归地写出这个:

function fib(n)
  if(n == 0 || n == 1)
    n
  else
    fib(n-1) + fib(n-2)

现在,任何已经编程了一段时间或者对算法效率有所了解的人都会告诉你这是一个糟糕的主意。原因是,在每一步,你必须重新计算fib(i)的值,其中i是2..n-2。

更有效的例子是存储这些值,创建一个自上而下的动态编程算法。

m = map(int, int)
m[0] = 0
m[1] = 1
function fib(n)
  if(m[n] does not exist)
    m[n] = fib(n-1) + fib(n-2)

通过这样做,我们最多计算一次fib(i)。


底向上

自下而上使用与自上而下相同的记忆技术。然而,不同之处在于自下而上使用称为重复的比较子问题来优化您的最终结果。

在大多数自下而上的动态编程问题中,您经常尝试最小化或最大化决策。在任何给定点,您都有两个(或更多)选项,您必须确定哪个选项更适合您要解决的问题。但是,这些决定是基于您之前做出的选择。

通过在每个点(每个子问题)做出最佳决策,您确保您的整体结果是最优的。

这些问题中最困难的部分是找到解决问题的重复关系。

要支付一堆算法教科书的费用,您打算抢劫一家拥有 n 项目的商店。问题是您的tiny knapsack最多只能保留 W kg。知道每件物品的重量(w [i])和价值(v [i]),你想要最大化你的赃物的价值,所有这些物品的总重量最多为W.对于每件物品,你必须做出二元选择 - 接受或离开它。

现在,你需要找到子问题是什么。作为一个非常聪明的小偷,你意识到给定项目的最大值,i,最大权重w,可以表示为m [i,w]。另外,m [0,w](最多权重w为0项)和m [i,0](i项目最大权重为0)将始终等于0值。

所以,

m[i, w] = 0 if i = 0 or w = 0

在你的思维全面罩的情况下,你注意到如果你已经尽可能多地装满了你的包,那么除非它的重量小于或等于你之间的差异,否则不能考虑新的项目。最大重量和袋子的当前重量。您可能想要考虑某个项目的另一种情况是,它的重量小于或等于行李中的物品但更有价值。

 m[i, w] = 0 if i = 0 or w = 0
           m[i - 1, w] if w[i] > w
           max(m[i - 1, w], m[i - 1, w - w[i]] + v[i]) if w[i] <= w

这些是上述的递归关系。一旦你有这些关系,编写算法非常容易(而且简短!)。

v = values from item1..itemn
w = weights from item1..itemn
n = number of items
W = maximum weight of knapsack

m[n, n] = array(int, int)
function knapsack
  for w=0..W
    m[0, w] = 0
  for i=1 to n
    m[i, 0] = 0
    for w=1..W
      if w[i] <= w
        if v[i] + m[i-1, w - w[i]] > m[i-1, w]
           m[i, w] = v[i] + m[i-1, w - w[i]]
        else
           m[i, w] = m[i-1, w]
      else
        m[i, w] = c[i-1, w]

  return m[n, n]

其他资源

  1. Introduction to Algorithms
  2. Programming Challenges
  3. Algorithm Design Manual

  4. 示例问题

    幸运的是,在竞争性编程方面,动态编程已成为 。查看Dynamic Programming on UVAJudge的一些练习题,这些练习题将测试您的实现能力并找出动态编程问题的重现。

答案 1 :(得分:10)

简而言之,动态编程是一种解决复杂问题的方法,可以将它们分解为更简单的步骤,即逐步解决问题。

  1. Dynamic programming;
  2. Introduction to Dynamic Programming;
  3. MIT's Introduction to Algorithms, Lecture 15: Dynamic Programming;
  4. Algorithm Design(书)。
  5. 我希望这些链接至少会有所帮助。

答案 2 :(得分:6)

开始

如果你想测试自己,我对在线评委的选择是

当然

您还可以检查优秀的大学算法课程

毕竟,如果你无法解决问题,请问这里存在许多算法瘾君子

答案 3 :(得分:5)

见下文

上面的文章中有太多的样本和文章参考。

学习动态编程后,您可以通过解决 UVA 问题来提高技能。 discussion board UVA

Wiki 也有一个很好的简单样本。

修改 关于它的书籍算法,你可以使用:

另外,您应该在动态编程中查看 Memoization

答案 4 :(得分:4)

我想 Algebraic Dynamic Programming 值得一提。这是DP技术的非常鼓舞人心的介绍并且是广泛的 用于生物信息学社区。 此外,贝尔曼的最优性原则以非常易于理解的方式陈述。

传统上,DP是通过示例教授的:算法是按照术语进行的 存储中间问题解决方案的表条目之间的重复次数, 从该表中可以通过一些案例分析构建整体解决方案。

ADP组织DP算法,使问题分解成子问题 案例分析与预期的优化完全分开 目的。这允许重用和组合DP算法的不同部分 对于类似的问题。

ADP算法中有三个松散耦合的部分:

  • 构建搜索空间(以树语法表示);
  • 评分搜索空间的每个元素;
  • 目标函数选择我们感兴趣的搜索空间的那些元素。

然后所有这些部分自动融合在一起,产生有效的算法。

答案 5 :(得分:3)

This USACO article是理解DP基础知识以及如何提供极大加速的良好起点。然后看看this TopCoder article,它也涵盖了基础知识,但编写得不是很好。 CMU的这个教程也很不错。一旦你理解了这一点,你就需要跳到2D DP来解决你所提到的问题。阅读this Topcoder article直至并包括苹果问题(标记为中间)。

您可能还会发现观看this MIT video lecture非常有用,具体取决于您从视频中选择的内容。

另外请注意,在成功获取DP之前,您需要掌握递归。 DP 很难!但真正困难的部分是看到解决方案。一旦你理解了DP的概念(上面的内容应该让你)并且你给出了解决方案的草图(例如my answer to your question,那么它实际上并不难以应用,因为DP解决方案通常非常简洁而且与易于理解的递归解决方案的迭代版本相差不远。

您还应该看看memoization,有些人会发现它更容易理解,但它通常与DP一样高效。简要说明一下,memoization采用递归函数并缓存其结果,以便在将来重新计算相同参数的结果。

答案 6 :(得分:2)

动态编程只能解决一些问题

由于还没有人提及它,动态编程解决方案所需的属性是:

  • 重叠子问题。必须能够将原始问题分解为子问题,使得某些子问题不止一次出现。 DP优于普通递归的优点是每个子问题只能一次解决,并且必要时保存并重用结果。换句话说,DP算法会将内存交换一段时间。
  • 最佳子结构。必须可以仅使用最优子问题解决方案来计算子问题的最优解。验证此属性是否成立可能需要仔细考虑。

示例:所有对最短路径

作为DP算法的典型示例,请考虑使用Floyd-Warshall algorithm查找图形中所有顶点对之间的最短路径长度的问题。

假设n个顶点编号为1到n。虽然我们有兴趣计算函数d(a, b),即顶点ab之间最短路径的长度,但很难找到一种方法从函数的其他值有效地计算它d()

让我们引入第三个参数c,并将d(a, b, c)定义为ab之间最短路径的长度,该路径仅访问范围1到1的顶点介于两者之间c。 (它不需要访问所有这些顶点。)虽然这似乎是一个无意义的添加约束,但请注意我们现在有以下关系:

d(a, b, c) = min(d(a, b, c-1), d(a, c, c-1) + d(c, b, c-1))

上面min()的2个参数显示了2个可能的情况。仅使用顶点1到abc的最短路径:

  1. 避免c(在这种情况下,它与仅使用第一个c-1顶点的最短路径相同)或
  2. 通过c。在这种情况下,此路径必须是从ac的最短路径,后面是从cb的最短路径,两条路径都限制为仅访问顶点介于1到c-1之间。我们知道,如果这种情况(通过c)更短,那么这两条路径就不能访问任何相同的顶点,因为如果它们这样做,它将会更短,以跳过所有顶点(包括c )他们之间,所以案件1将被挑选。
  3. 这个公式满足最优子结构属性 - 只需要知道子问题的最优解决方案,找到解决更大问题的最优解。 (并非所有问题都有这个重要的属性 - 例如,如果我们想在所有顶点对之间找到最长的路径,这种方法会因为{{{{}的最长路径而崩溃1}}到a可能会访问从cc的最长路径也访问过的顶点。)

    知道上述函数关系,以及b等于d(a, b, 0)a之间边缘长度的边界条件(如果不存在这样的边,则为无穷大),它是可以计算b的每个值,从d(a, b, c)开始计算到c=1c=nd(a, b, n)a之间的最短距离,可以访问其间的任何顶点 - 我们正在寻找的答案。

答案 7 :(得分:1)

答案 8 :(得分:1)

几乎所有的入门算法书籍都有一些动态编程的章节。我建议:

答案 9 :(得分:1)

如果您想了解算法,我发现麻省理工学院有一些非常优秀的讲座视频。

例如,6.046J / 18.410J Introduction to Algorithms (SMA 5503)看起来是个不错的选择。

该课程涵盖动态编程,以及许多其他有用的算法技术。在我个人看来,这本书也非常出色,非常值得为那些认真学习算法的人买单。

此外,课程附带一份作业清单等等,所以你也有可能在实践中运用这个理论。

相关问题:

答案 10 :(得分:0)

作为函授数学硕士课程的一部分我根据书http://www.amazon.co.uk/Introduction-Programming-International-mathematics-computer/dp/0080250645/ref=sr_1_4?ie=UTF8&qid=1290713580&sr=8-4开设了一门课程。它实际上是一个数学角度而不是编程角度,但如果你能省时间和精力,它就是一个非常彻底的介绍,对我来说似乎很有用,因为这本课程几乎完全出自本书。

我还有Sedgewick的“算法”一书的早期版本,并且有一个关于动态编程的简短章节。他现在似乎卖掉了令人眼花缭乱的各种昂贵书籍。在亚马逊上看,http://www.amazon.co.uk/gp/product/toc/0201361205/ref=dp_toc?ie=UTF8&n=266239

似乎有一个同名的章节

答案 11 :(得分:0)

规划算法,Steven LaValle有一节关于动态规划:

http://planning.cs.uiuc.edu/

参见例如第2.3.1节。

答案 12 :(得分:0)

MIT Open CourseWare 6.00计算机科学与程序设计概论

答案 13 :(得分:0)

如果您尝试动态编程以解决问题,我想您会欣赏它背后的概念。在Google codejam中,一旦参与者获得了一个名为“Welcome to CodeJam”的程序,它就会以极好的方式展示出使用动态编程。