好的例子,文章,用于理解dynamic规划的书籍

我不知道dynamic编程的原理,我真的很想要它。 DP非常强大,它可以解决这样的问题:

从数字的差异中获得尽可能低的总和

那么,你能否给我build议好的书籍或文章 (最好是带有真实代码的例子),这将解释什么是dynamic规划? 我真的想要简单的例子,然后我继续前进。

dynamic规划是一种有用的algorithmtypes,可以通过将问题分解为更小的子问题来优化难题。 通过存储和重新使用部分解决scheme,它可以避免使用贪婪algorithm的缺陷。 dynamic编程有两种,分别是自下而上和自上而下。

为了使用dynamic规划解决问题,该问题必须具有所谓的最佳子结构的属性。 这意味着,如果问题被分解成一系列的子问题,并find每个子问题的最优解,那么通过解决这些子问题就可以实现最终的解决scheme。 dynamic编程无法解决没有这种结构的问题。

自顶向下

自上而下比较熟悉的是memoization 。 这是存储过去的计算,以避免每次重新计算的想法。

给定一个recursion函数,说:

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

我们可以很容易地从mathformsrecursion地写出如下:

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

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

一个更有效的例子是存储这些值,创build一个自顶向下的dynamic编程algorithm。

 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)。


自下而上

自下而上使用与自上而下使用的记忆相同的技术。 不同的是,自下而上使用被称为复发的比较子问题来优化您的最终结果。

在大多数自下而上的dynamic规划问题中,您经常试图最小化或最大化决策。 在任何给定的位置给予两个(或更多)选项,您必须决定哪个更适合您要解决的问题。 但是,这些决定是基于您以前的select。

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

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

为了支付一堆algorithm教科书,你计划抢劫一个有n件物品的商店。 问题是,你的小背包只能容纳最多W公斤。 知道每件商品的重量(w [i])和价值(v [i]),你想要最大限度地提高被赃物的价值,这些商品的重量至多为W.对于每件商品,你必须做出二元select – 要么接受,要么离开它。

现在,你需要find这个子问题。 作为一个非常聪明的小偷,你意识到一个给定项目i的最大值可以表示为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 

这些是上述的再现关系。 一旦你有这些关系,编写algorithm是非常简单的(和简短!)。

 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. algorithm介绍
  2. 编程挑战
  3. algorithmdevise手册

示例问题

幸运的是, 竞争性编程方面,dynamic编程已经成为现实。 查看UVAJudge上的dynamic规划,了解一些实践问题,这些问题将testing您实现和查找dynamic规划问题的重现的能力。

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

  1. dynamic编程 ;
  2. dynamic规划简介
  3. 麻省理工学院的algorithm导论,第15讲:dynamic规划 ;
  4. algorithmdevise (书)。

我希望这个链接至less有一点帮助。

从…开始

  • 维基百科有关dynamic编程的文章
  • 我build议你在topcoder阅读这篇文章
  • ch6关于algorithm中的dynamic规划 (Vazirani)
  • algorithmdevise手册中的dynamic编程章节
  • algorithm经典书籍dynamic规划篇(algorithm导论 )

如果你想testing自己,我对网上评委的select是

  • Uvadynamic编程问题
  • Timusdynamic编程问题
  • Spojdynamic编程问题
  • TopCoderdynamic编程问题

而且当然

  • 看看algorithmist dynamic编程的类别

你也可以检查好的大学algorithm课程

  • Aduni(algorithm)
  • 麻省理工学院(介绍algorithm(第15章))

毕竟,如果你不能解决问题,那么在这里存在很多algorithm成瘾者

见下文

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

在学习dynamic编程之后,你可以通过解决UVA问题来提高你的技能,在UVA的讨论板上有一些UVAdynamic编程问题的列表

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

编辑:关于它的书algorithm,你可以使用:

  • Pythonalgorithm:掌握Python语言中的基本algorithm :在本书中,您可以看到使用DP的实际工作。
  • algorithm介绍 :描述本书完成algorithm最简单的方法。

你也应该看看dynamic编程中的Memoization

我认为代数dynamic规划值得一提。 这是相当鼓舞人心的展示DP技术,并广泛应用于生物信息学界。 而且,贝尔曼的最优性原则是以非常易懂的方式陈述的。

传统上,DP是通过示例来教导的:algorithm是根据存储解决scheme到中间问题的表条目之间的重复来build立的,从这个表格中可以通过一些案例分析来构build整体解决scheme。

ADP组织DPalgorithm,使问题分解成子问题和案例分析完全与预期的优化目标分离。 这允许重复使用和组合DPalgorithm的不同部分来解决类似的问题。

ADPalgorithm中有三个松散耦合的部分:

  • build立search空间(用树语法表示);
  • 评分search空间的每个元素;
  • 目标函数select那些我们感兴趣的search空间元素。

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

这篇USACO文章是了解DP的基本知识以及如何提高速度的一个很好的起点。 然后看看这个TopCoder的文章 ,其中也包括基础知识,但没有写得很好。 CMU的这个教程也很不错。 一旦你明白了这一点,你将需要飞跃2D 2D来解决你所指的问题。 通过这个Topcoder文章阅读,并包括苹果问题(标记为中间)。

你也可能会发现看这个麻省理工学院video讲座很有用,这取决于你从video中挑选的东西。

另外请注意,在成功获取DP之前,您需要掌握recursion。 DP 很难 ! 但真正困难的部分是看到解决scheme。 一旦你理解了DP的概念(上面应该给你的),并且你给出了一个解决scheme的草图(例如我的回答你的问题,那么它真的不难以应用,因为DP解决scheme通常是非常简洁并且不会与易于理解的recursion解决scheme的迭代版本相差太远。

你也应该看一下记忆 ,有些人会觉得更容易理解,但它往往和DP一样有效。 简要解释一下,memoization采用recursion函数并caching其结果,以保存将来针对相同参数重新计算结果。

dynamic规划只能解决一些问题

由于没有人提到过,所以dynamic编程解决scheme所需要的属性是:

  • 重叠的子问题。 必须有可能将原来的问题分解成多个子问题,使得一些子问题不止一次出现。 DP优于简单recursion的优点是每个子问题只能解决一次 ,并且必要时保存和重用结果。 换句话说,DPalgorithm交换内存的时间。
  • 最佳的子结构。 只能使用子问题的最优解来计算子问题的最优解。 validation这个属性持有可能需要一些仔细的思考。

示例:全对最短path

作为DPalgorithm的典型例子,考虑使用Floyd-Warshallalgorithm找出图中所有顶点对之间最短path长度的问题。

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

让我们引入第三个参数c ,将d(a, b, c)定义为ab之间最短path的长度,它只访问范围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的最短path:

  1. 避免c (在这种情况下,它与仅使用第一个c-1顶点的最短path相同),或者
  2. 通过c 。 在这种情况下,此path必须是从ac的最短path,接着是从cb的最短path,两个path都被限制为仅访问范围为1到c-1之间的顶点。 我们知道,如果这种情况(通过c )更短,那么这两个path不能访问任何相同的顶点,因为如果他们这样做,它会缩短它们之间的所有顶点(包括c ),所以情况1会已被挑选出来。

这个公式满足最优的子结构性质 – 只需要知道子问题的最优解就可以find最大的问题的最优解。 ( 不是所有的问题都有这个重要的属性 – 例如,如果我们想要find所有顶点对之间的最长path,这个方法就会崩溃,因为从ac的最长path可能访问从cb 。)

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

几乎所有的入门algorithm书籍都有一些dynamic编程的章节。 我build议:

  • algorithm介绍 Cormen等人
  • algorithm介绍: Udi Manber 创造性的方法

如果你想了解algorithm,我发现麻省理工学院有一些相当优秀的讲座video。

例如, 6.046J / 18.410Jalgorithm简介(SMA 5503)看起来相当不错。

本课程涵盖了dynamic编程以及其他许多有用的algorithm技术。 我个人认为所用的书也非常出色,非常值得认真学习algorithm。

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

相关问题:

作为信函math硕士的一部分,我做了一个基于这本书的课程http://www.amazon.co.uk/Introduction-Programming-International-mathematics-computer/dp/0080250645/ref=sr_1_4?ie=UTF8&qid=1290713580&sr = 8-4它的确是一个mathangular度,而不是一个编程的angular度,但是如果你能省时间和精力,这是一个非常彻底的介绍,这似乎对我来说是一个课程,运行了很多书。

我还有Sedgewick的“algorithm”一书的早期版本,里面有一个关于dynamic编程的非常可读的简短章节。 他现在似乎出售了一堆令人眼花缭乱的昂贵书籍。 看着亚马逊,似乎有一个同名的章节http://www.amazon.co.uk/gp/product/toc/0201361205/ref=dp_toc?ie=UTF8&n=266239

规划algorithm,由Steven LaValle有一个关于dynamic规划的章节:

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

参见2.3.1节。

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

如果您尝试dynamic编程来解决问题,我想您会明白其背后的概念。 在Google codejam中,一旦参与者获得了一个名为“ 欢迎使用CodeJam ”的程序,就可以很好地展示使用dynamic编程。