理解动态规划的好例子、文章、书籍[关闭]

Posted

技术标签:

【中文标题】理解动态规划的好例子、文章、书籍[关闭]【英文标题】:Good examples, articles, books for understanding dynamic programming [closed] 【发布时间】:2011-05-15 18:12:47 【问题描述】:

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

Getting the lowest possible sum from numbers' difference

那么,您能否向我推荐好书或文章(最好带有带有真实代码的示例)来解释什么是动态编程?我真的想要先简单的例子,然后我会继续。

【问题讨论】:

您的意思是“元编程”吗?当我听到“动态编程”时,我想到了从数据库中提取数据来修改服务器动态生成的 html 例如递归、分治、回溯等 @Achilles:当大多数人使用“动态编程”一词时,他们指的是Dynamic Programming,尤其是在算法上下文中这样做时。 @Achilles:元编程当然不是动态编程。 【参考方案1】:

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 公斤。知道每件物品的重量 (w[i]) 和价值 (v[i]),您希望最大化您的赃物的价值,这些物品的总重量最多为 W。对于每件物品,您必须做出二元选择 -要么接受,要么离开。

现在,您需要找出子问题是什么。作为一个非常聪明的小偷,您意识到给定项目的最大值 i 和最大重量 w 可以表示为 m[i, w]。此外,m[0, w](最多 0 个权重 w)和 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]

其他资源

    Introduction to Algorithms Programming Challenges Algorithm Design Manual

示例问题

幸运的是,在竞争性编程方面,动态编程已经真正流行。查看Dynamic Programming on UVAJudge 中的一些练习题,这些练习题将测试您实施动态编程问题并找到重现问题的能力。

【讨论】:

+1 - 一些自下而上的算法被称为“表格”,因为它们基于计算结果表。表格通常是“向后”计算的,以确保在需要引用之前完成每个项目。简单的自动换行可以使用这种方法(我认为 Sedgewick 以它为例)。它不称为“表格自动换行”,但我是这样想的。还有一个表格 LR 解析算法,IIRC“packrat”基本上是表格 LL 解析。【参考方案2】:

简而言之,动态规划是一种通过将复杂问题分解为更简单的步骤来解决复杂问题的方法,即逐步解决问题。

    Dynamic programming; Introduction to Dynamic Programming; MIT's Introduction to Algorithms, Lecture 15: Dynamic Programming; Algorithm Design(书)。

我希望这个链接至少会有所帮助。

【讨论】:

IMO 动态规划并不是要将问题分解为更简单的步骤,确切地说,而是通过存储这些步骤的结果以供以后重用,从而避免在重复出现等效步骤时重复计算。 @Steve314:那么,告诉***(见第一个链接)。这是关于它的第一句话。 ;-) 如果您不打破复杂性,您将无法避免重复计算,因为您将无法从中获得整个复杂性。虽然我理解并理解你的意思,但这是第二步,真的,因为一旦你看到有重复,你就能够理解重复并分解它。然后,可以重构代码以避免重复。 问题是,所有算法范例都涉及将问题分解为更简单的步骤。分而治之最接近于简单地说明必须这样做,但仍然包含有关如何细分的课程。贪心方法更多的是关于如何选择首先处理哪个子问题,等等 - 每个特定范式的独特之处不仅仅是细分问题,因为细分是所有范式的共同点。【参考方案3】:

开始 wikipedia article about dynamic programming then 我建议你阅读 topcoder 中的this article ch6 about dynamic programming 在算法中 (Vazirani) 算法设计手册中的Dynamic programming chapter 算法经典书籍中的动态编程章节 (Introduction to Algorithms)

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

Uva Dynamic programming problems Timus Dynamic programming problems Spoj Dynamic programming problems TopCoder Dynamic programming problems

当然还有

看算法师dynamic programming category

您还可以查看好的大学算法课程

Aduni (Algorithms) MIT (Introduction to Algorithms (chapter 15))

毕竟,如果你不能解决问题,问SO这里存在许多算法上瘾

【讨论】:

【参考方案4】:

见下文

http://www.topcoder.com/tc?d1=tutorials&d2=dynProg&module=Static

上面文章中的示例和文章参考太多了。

学习动态规划后,你可以通过解决UVA问题来提高你的技能,UVA的discussion board中有一些UVA动态规划问题的列表

Wiki 也有一个很好的简单示例。

编辑: 对于有关它的书籍算法,您可以使用:

Python Algorithms: Mastering Basic Algorithms in the Python Language:在这本书中你可以看到DP的实际工作。 Introduction to Algorithms:本书中描述算法的最简单方法。

您还应该看看动态编程中的 Memoization

【讨论】:

【参考方案5】:

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

传统上,DP 是通过例子来教授的:算法是用术语来表达的 存储中间问题解决方案的表条目之间的重复, 从这张表中可以通过一些案例分析构建整体解决方案。

ADP 组织 DP 算法,以便将问题分解为子问题 和案例分析与预期的优化完全分离 客观的。这允许重用和组合 DP 算法的不同部分 类似的问题。

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

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

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

【讨论】:

【参考方案6】:

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 采用递归函数并缓存其结果,以节省将来为相同参数重新计算结果。

【讨论】:

【参考方案7】:

动态规划只能解决一些问题

由于还没有人提到它,因此动态编程解决方案需要具备的属性是:

重叠的子问题。 必须可以将原始问题分解为子问题,以使某些子问题不止一次出现。与普通递归相比,DP 的优势在于这些子问题中的每一个都只会被解决一次,并且如果需要,结果会被保存和重用。换句话说,DP 算法用内存换取时间。 最优子结构。必须能够仅使用子问题的最优解来计算子问题的最优解。验证此属性是否成立需要仔细考虑。

示例:所有对最短路径

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

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

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

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

上面min() 的两个参数显示了两种可能的情况。从ab 的最短方法只使用顶点1 到c

    避免c(在这种情况下,它与仅使用第一个c-1 顶点的最短路径相同),或者 通过c 发送。在这种情况下,该路径必须是从ac 的最短路径,然后是从cb 的最短路径,两条路径都被限制为仅访问范围为1 到c-1 的顶点之间。我们知道,如果这种情况(通过c)更短,那么这两条路径不能访问任何相同的顶点,因为如果它们这样做了,跳过它们之间的所有顶点(包括c)会更短,所以会选择案例 1。

这个公式满足最优子结构属性——只需要知道子问题的最优解,就可以找到更大问题的最优解。 (并非所有问题都有这个重要的属性——例如,如果我们想找到所有顶点对之间的最长路径,这种方法会失败,因为从@987654348 开始的最长路径@ 到 c 可能会访问从cb 的最长路径也访问过的顶点。)

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

【讨论】:

【参考方案8】:

http://mat.gsia.cmu.edu/classes/dynamic/dynamic.html

【讨论】:

【参考方案9】:

几乎所有介绍算法的书籍都有一些关于动态规划的章节。我建议:

Introduction to Algorithms Cormen 等人 Introduction to Algorithms: A Creative Approach 由 Udi Manber 提供

【讨论】:

【参考方案10】:

如果你想了解算法,我发现 MIT 有一些非常优秀的讲座视频。

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

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

此外,课程附带作业清单等,因此您也可以在实践中运用理论。

相关问题:

Learning Algorithms and Data Structures Fundamentals https://***.com/questions/481260/whats-a-good-way-to-start-learning-about-data-structures-algorithms

【讨论】:

【参考方案11】:

作为函授数学MSc的一部分,我根据http://www.amazon.co.uk/Introduction-Programming-International-mathematics-computer/dp/0080250645/ref=sr_1_4?ie=UTF8&qid=1290713580&sr=8-4这本书做了一个课程,它确实更像是一个数学角度而不是编程角度,但如果你能抽出时间和精力,这是一个非常透彻的介绍,这对我来说似乎很有效,因为这门课程几乎已经超出了书本。

我还有 Sedgewick 的“算法”一书的早期版本,其中有一个非常易读的简短章节关于动态编程。他现在似乎在出售各种令人眼花缭乱的昂贵书籍。在amazon上看,http://www.amazon.co.uk/gp/product/toc/0201361205/ref=dp_toc?ie=UTF8&n=266239好像有一个同名章节@

【讨论】:

【参考方案12】:

Planning Algorithms, by Steven LaValle 有一个关于动态规划的部分:

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

例如参见第 2.3.1 节。

【讨论】:

【参考方案13】:

麻省理工学院开放课件 6.00 计算机科学与编程概论

【讨论】:

【参考方案14】:

如果您尝试动态编程来解决问题,我想您会理解它背后的概念。在 Google codejam 中,一旦给参与者一个名为“Welcome to CodeJam”的程序,它就很好地揭示了动态编程的使用。

【讨论】:

以上是关于理解动态规划的好例子、文章、书籍[关闭]的主要内容,如果未能解决你的问题,请参考以下文章

动态规划答疑篇

谈谈动态规划的本质

从01背包问题理解动态规划---初体验

用这个例子理解动态规划,怎么可能不懂?

动态规划题解(转)

算法学习——动态规划1