<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_刘子韬的博客</title><subtitle type="text">寒窗数载今朝一介书生风华正茂谨记自强弘毅，豪情万丈明日软件英才鸿图大展心系珞珈山水</subtitle><id>http://feed.cnblogs.com/blog/u/53755/rss</id><updated>2011-05-04T01:16:50Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><generator>CNBlogs BlogServer</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/53755/rss"/><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/05/04/2012284.html</id><title type="text">漫谈算法（三）NP问题</title><summary type="text">Keywords: NP Problme; NP-hard Problem; NP-complete Problem; P Problem</summary><published>2011-05-03T20:07:00Z</published><updated>2011-05-03T20:07:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/05/04/2012284.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/05/04/2012284.html"/><content type="html">&lt;p&gt;Keywords: NP Problme; NP-hard Problem; NP-complete Problem; P Problem&lt;/p&gt;&#xD;
&lt;p&gt;[为什么写这类文章]&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html"&gt;漫谈算法（零）序&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;[这系列文章里会用到的一下符号和公式] &amp;nbsp;&amp;nbsp;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html"&gt;漫谈算法（番外篇） 符号标记以及基本数学公式&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;首先解释一下什么是NP问题，什么是NP hard问题，什么是NP完全问题。&lt;/p&gt;&#xD;
&lt;p&gt;看下面的图，他们之间的关系表示的比较清楚。&lt;/p&gt;&#xD;
&lt;p&gt;P Problem：这个应该最易理解，就是一个问题可以在Polynominal的时间的得到解决，当然，是对于任意input size。&lt;/p&gt;&#xD;
&lt;p&gt;NP Problem：对于一类问题，我们可能没有一个已知的快速的方法得到问题的答案，但是如果给我们一个candidate answer，我们能够在polynominal的时间内验证这个candidate answer到底是不是我们已知问题的答案，这类问题叫做NP problem。所以很显然 P Problem是NP problem的一个子集。&lt;/p&gt;&#xD;
&lt;p&gt;NP-hard Problem：对于这一类问题，用一句话概括他们的特征就是&amp;ldquo;at least as hard as the hardest problems in NP Problem&amp;rdquo;， 就是NP-hard问题至少和NP问题一样难。&lt;/p&gt;&#xD;
&lt;p&gt;NP-complete Problem：对于这一类问题，他们满足两个性质，一个就是在polynomial时间内可以验证一个candidate answer是不是真正的解，另一个性质就是我们可以把任何一个NP问题在polynomial的时间内把他的input转化，使之成为一个NP-complete问题。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011040803294642.png" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;所以对于NP-hard问题，我们可以把他们分成两个部分，一部分可以用polynomial的时间验证一个candidate answer是不是真正的answer，这一部分问题组成了NP-complete集合。&lt;/p&gt;&#xD;
&lt;p&gt;我们经常说的NP=P或者NP！=P，其实就是说目前而言，我们并不知道，是不是对于NP Problem集合里面的所有问题，都能够在Polynomial的时间内解决。当然只里面比较interesting的一点是，如果我们能把NP-complete集合中的任意一个问题在polynomial的时间内解决了，那么所有的NP问题都可以在polynomial的时间内解决。原因，看看上面说的NP-complete的性质就知道了，因为任何一个NP问题都可以在polynomial的时间内把他的input转化，使之成为一个NP-complete问题，所以....&lt;/p&gt;&#xD;
&lt;p&gt;介绍了上面说的一些定义，来举几个经典的NP-complete的问题。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;Vertex Cover&amp;nbsp;&lt;/strong&gt;&lt;a href="http://en.wikipedia.org/wiki/Vertex_Cover"&gt;http://en.wikipedia.org/wiki/Vertex_Cover&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;Informally来说，就是对于一个图G(V,E)，我们在V中选一个subset V'， 使得E中的所有边得两点中的一个点在V'中。 所谓Vertex Cover也就是说V'中的点cover了每一条边（因为每一条边至少有一端是在V'中的啦）给你一个G(V,E)和一个k，问你在整个G中，是否存在一个大小为k的Vertex Cover（Decision Problem）&lt;/p&gt;&#xD;
&lt;p&gt;Formally, a vertex cover of a graph&amp;nbsp;&lt;i&gt;G&lt;/i&gt;&amp;nbsp;is a set&amp;nbsp;&lt;i&gt;C&lt;/i&gt;&amp;nbsp;of vertices such that each edge of&amp;nbsp;&lt;i&gt;G&lt;/i&gt;&amp;nbsp;is incident to at least one vertex in&amp;nbsp;&lt;i&gt;C&lt;/i&gt;. The set&amp;nbsp;&lt;i&gt;C&lt;/i&gt;&amp;nbsp;is said to&amp;nbsp;&lt;i&gt;cover&lt;/i&gt;&amp;nbsp;the edges of&amp;nbsp;&lt;i&gt;G&lt;/i&gt;. The following figure shows examples of vertex covers in two graphs (the set&amp;nbsp;&lt;i&gt;C&lt;/i&gt;&amp;nbsp;is marked with red).&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041303003728.png" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;3-CNF-SAT&lt;/strong&gt;&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/3SAT#3-satisfiability"&gt;http://en.wikipedia.org/wiki/3SAT#3-satisfiability&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;举个例子，&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041302532181.png" /&gt;，每一个括号包括的东西叫一个clause，里面的X1，X2，X3在离散数学里面叫做文字，取值True或者False。每个括号之间是合取，括号里面是析取，这个问题就是，对于这样的一个表达式，是不是能找到一组Xi的值，使得表达式为True。&amp;nbsp;Given the expression, is there some assignment of&amp;nbsp;&lt;i&gt;TRUE&lt;/i&gt;&amp;nbsp;and&amp;nbsp;&lt;i&gt;FALSE&lt;/i&gt;&amp;nbsp;values to the variables that will make the entire expression true?&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;Integer Linear Programming &amp;nbsp;&lt;/strong&gt;&lt;a href="http://en.wikipedia.org/wiki/Linear_program#Integer_unknowns"&gt;http://en.wikipedia.org/wiki/Linear_program#Integer_unknowns&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;当然这个最显而易见了，就是LP中的所有变量都要求是整数了。关于Linear Programming的问题，后面会专门有一篇文章来讲解。O(&amp;cap;_&amp;cap;)O~&lt;/p&gt;&#xD;
&lt;p&gt;下面来看看我们经常会遇到的一些证明问题。&lt;/p&gt;&#xD;
&lt;p&gt;证明一个问题是NP问题。证明给你一个结果，你能在polynomial的时间内验证他的正确性&lt;/p&gt;&#xD;
&lt;p&gt;证明一个问题是NP-hard的。对于证明一个问题是NP-hard，我们经常用到的一个technique是归约（reduction），通常用&amp;lt;=这个符号来表示，如P&amp;lt;=Q，这个就表示&lt;strong&gt;P is reducible to Q or Q is the reduction from P or P is reduced to Q. P问题可以归约到Q问题。可以把P归约到Q。&lt;/strong&gt;这里的reduction的符号可以当成是 比较难易程度的小于等于号，意味着问题Q至少和问题P一样难。当我们要证明一个问题是NP-hard的时候，我们通常要做的是找到一个NPC问题（就用这个代替NP-complete问题），把这个NPC问题归约到NP-hard上去，即NPC&amp;lt;=NP-hard。&lt;/p&gt;&#xD;
&lt;p&gt;证明一个问题是NPC的。要证NPC，我们要分两步走，第一步证明这个问题属于NP，就是验证答案（感觉这句话我都说烂了）。第二步，证明这个问题是NP-hard的。当然这里面第二步是问题的关键，但是第一步也一定要在证明里面提到。&lt;/p&gt;&#xD;
&lt;p&gt;如何证明一个问题是NP-hard 是以上证明的关键，即如何归约。&lt;/p&gt;&#xD;
&lt;p&gt;这里我们归约要做的主要步骤就是，&lt;/p&gt;&#xD;
&lt;p&gt;1.把NPC的input转化到NP-hard的input，即每一个NPC的input，实际上都是一个NP-hard的input。&lt;/p&gt;&#xD;
&lt;p&gt;2.把NP-hard的output转化到NPC的output上来，说明给我一个NP-hard的output，我就能给你一个NPC的output。&lt;/p&gt;&#xD;
&lt;p&gt;注意：以上的两个转化都要在polynomial的时间内完成。&lt;/p&gt;&#xD;
&lt;p&gt;下面举几个例子来证明上面的归约的方法。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 用3SAT 证明 Vertex Cover是NP-hard的。即 3SAT &amp;lt;= Vertex Cover&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;这个就是表明我们已知3SAT这个问题是NP-complete的，如何把3SAT问题归约到Vertex Cover上。&lt;/p&gt;&#xD;
&lt;p&gt;首先，我们要建立我们的通过input建立这两个问题的对应。假设我的3SAT是&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041302532181.png" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;我们按照下面的方法构造我们的Graph，对应每一个个变量Xi，我们构造点 Xi和~Xi，对于每一个clause，我们构造3个点，这3个点直接彼此有边，假设这三个点叫A,B,C。同时我们还要建立A,B,C这三个点和该clause的联系：假设我们的clause是 （X1 V ~X2 V ~X3） 我们就把X1和A连起来，~X2和B连起来，~X3和C连起来。注意，每一个clause有一个全连通的三角，他们共用那6个变量点（X1，~X1，X2，~X2，X3，~X3） 如下图所示。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041501324766.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;要注意的一点是，对于E中的每一个clause，我们都对应图里面的一个三角形（也就是我用矩形圈住的那部分），同时所有的clause共享上面的六个点，也就是2*变量个数 那么多个点是共用的。&lt;/p&gt;&#xD;
&lt;p&gt;通过这个图，我们也就建立起了3SAT和Vertex Cover之间的联系。通常这个也是此类证明问题中最难和最creative的部分。&lt;/p&gt;&#xD;
&lt;p&gt;后面就是表述一下如何进行转换的。通常这个是很trival的部分。&lt;/p&gt;&#xD;
&lt;p&gt;1）假设我有一个解能满足3SAT，那么我就一定能找到相应的解满足Veterx Cover。 如上图，3SAT满足了，必然每一个clause满足，就拿 (X1 V ~X2 V ~X3) 为例，这个式子满足了，必有一个变量为true，它可以是X1或者~X2或者~X3，假设X1为true，这时对应的vertex cover中，我们就选上面6个点中的X1，同时对于下面的三角形中的3个点，我们选除了那个与X1相连的另外两个点。对于每一个clause，我们都可以这样做，同时，我们也cover了这个图中的所有边。也就是我们有了一个满足要求的vertex cover。&lt;/p&gt;&#xD;
&lt;p&gt;2）假设我有一个解能满足Vertex Cover，那么我就一定能找到相应的解满足3SAT。因为要cover这个图，所以三角形里面至少要cover两个点，上面的一对一对的pair里面也至少要cover一个，所以对于一个size为n+2m的vertex cover（n是变量个数，m是clause的个数），我们一定可以找到一个满足的3SAT，（显然啊，因为每个clause都有一个点和上面的一对一对pair的点相连） （说的好拗口，郁闷。还不清楚的可以看下这个链接 &lt;a href="http://users.eecs.northwestern.edu/~fortnow/classes/w08/EECS395/lecture11.pdf"&gt;http://users.eecs.northwestern.edu/~fortnow/classes/w08/EECS395/lecture11.pdf&lt;/a&gt;&amp;nbsp;）&lt;/p&gt;&#xD;
&lt;p&gt;然后，然后，。。。我们就证完了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 用3SAT证明ILP是NP-hard的。即 3SAT &amp;lt;= ILP&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;还是首先找映射，这个映射不涉及图的东西，应该比较容易构造和理解。&lt;/p&gt;&#xD;
&lt;p&gt;还是拿上面那个3SAT的例子说事。对于 每个clause，我们都对应于ILP中的一个constraint，比如 E中有4个变量，X1，X2，X3 和X4， 我们的ILP中也有同样的这4个变量，并且我们要求他们都是只能取0 或 1。对于一个clause，如(X1 V ~X2 V ~X3) ，我们对应的constraint是 &amp;ldquo;X1 + （1-X2）+（1-X3）&amp;gt;=1&amp;rdquo;，很显然了，ILP中的变量选0对应于3SAT中的变量选false，ILP中的变量选1对应于3SAT中的变量选true，这样我们就映射好了。&lt;/p&gt;&#xD;
&lt;p&gt;很显然，这两个问题的input/output的转换trival的不行了。如果一个clause满足了，对应的那个ILP中的constraint肯定也满足了；反之依然。偷个懒~O(&amp;cap;_&amp;cap;)O~&lt;/p&gt;&#xD;
&lt;p&gt;证毕。&lt;/p&gt;&#xD;
&#xD;
&lt;p&gt;&lt;strong&gt;这类NP的证明问题其实还是很有难度的，只能说很锻炼脑子，对于它有没有用，这要看你对&amp;ldquo;useful&amp;rdquo;的定义了，仁者见仁，智者见智吧。&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;========================================================================================&lt;/p&gt;&#xD;
&lt;p&gt;本作品采用&lt;a href="http://creativecommons.org/licenses/by/2.5/cn/" rel="license"&gt;知识共享署名 2.5 中国大陆许可协议&lt;/a&gt;进行许可。 本博客采用知识共享署名 2.5 中国大陆许可协议进行许可。本博客版权归作者所有，欢迎转载，但未经作者同意&lt;b&gt;不得随机删除文章任何内容&lt;/b&gt;，且在文章页面&lt;b&gt;明显位置给出原文连接&lt;/b&gt;，否则保留追究法律责任的权利。如您有任何疑问或者授权方面的协商，请给&lt;a href="http://www.cnblogs.com/Gavin_Liu"&gt;我&lt;/a&gt;留言。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2012284.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/05/04/2012284.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/04/16/2016995.html</id><title type="text">漫谈算法（五）问题复杂度分析（Problem Complexity and Adversarial Lower Bound）</title><summary type="text">Keywords: Problem Complexity; Adversarial Strategy; Lower Bound.</summary><published>2011-04-15T21:47:00Z</published><updated>2011-04-15T21:47:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/16/2016995.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/16/2016995.html"/><content type="html">&lt;div&gt;Keywords: Problem Complexity; Adversarial Strategy; Lower Bound.&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;p&gt;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html"&gt;[为什么写这类文章]&lt;/a&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html" target="_blank"&gt;漫谈算法（零）序&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;[&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html" target="_blank"&gt;这系列文章里会用到的一下符号和公式&lt;/a&gt;] &amp;nbsp;&amp;nbsp;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html" target="_blank"&gt;漫谈算法（番外篇） 符号标记以及基本数学公式&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;strong&gt;&#xD;
&lt;/strong&gt;&#xD;
&lt;p&gt;通常我们说到某个算法，我们经常关心他的时间复杂度，当然，我们通常关心的是这个时间复杂度的upper bound，即上界。upper bound告诉我们在某些很糟糕的情况下，我们的算法的性能。比如我们的基于比较的排序算法，我们知道mergesort（归并排序）是O(nlgn)，但是我们应该能提出这样的疑问，can we do it better?（我们能做的更好吗？） 所以，在这篇文章里，我们将简单介绍一下对于算法的下界，lower bound，它告诉我们对于某个问题，是否存在更好的算法。 &lt;strong&gt;lower bound告诉我们什么不能做到&lt;/strong&gt;。&lt;/p&gt;&#xD;
&lt;p&gt;"&lt;strong&gt;Lower bounds tell us what cannot be done, they are very important in guiding us in our search for what can be done.&lt;/strong&gt;"&lt;/p&gt;&#xD;
&lt;p&gt;（当然对于lower bound的问题，我自己是google了一下，中文方面的资料很少，据本人了解高校内算法课涉及lower bound的也几乎没有，但google本文上面的keywords，可以看到北美的各大高校基本全部都有相关知识章节，本科和研究生都有，莫非这就是教育的差异？？！！这里就权当一个入门的tutorial了）&lt;/p&gt;&#xD;
&lt;p&gt;回归主题：&lt;/p&gt;&#xD;
&lt;p&gt;当然，我们知道，所谓lower bound，也就是找Omega(n)了。（不清楚的童鞋可以看一下文章开头给的链接）&lt;/p&gt;&#xD;
&lt;p&gt;还是决定举一个例子慢慢展开。&lt;/p&gt;&#xD;
&lt;p&gt;比如我们知道基于比较的排序算法最好也就是nlgn了，所以，这也就是说，我们应该能证明sorting = Omega(nlgn)，当然，这里的sorting是指任意的排序算法。这时我们就会发现，问题出来了，我们要证明任意算法都满足一个条件，任意，任意....一些传统的举反例啊，针对某个算法的内部分析啊什么的都不能用了。&lt;/p&gt;&#xD;
&lt;p&gt;对于找解决某一问题的那一类算法的lower bound，我们通常有两种办法，&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;1）decision tree&lt;/strong&gt; 我们通过分析决策树的方法来得到lower bound（这也是算法导论里面提到的方法）&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;2）adversary strategy&lt;/strong&gt; 这个确实不好翻译，竞争者策略？！ 当然这个方法要比上面的decision tree得到的lower bound更tight一些。（当然这个也不绝对，还要看你的strategy）&lt;/p&gt;&#xD;
&lt;p&gt;鉴于基本上lower bound的引入都是通过sorting这个人神共知的algorithm引入的，我这里也不非主流了。&lt;/p&gt;&#xD;
&lt;p&gt;下面先用sorting的lower bound的例子来分别解释一下上面提到的两个方法，然后在举一些例子帮助大家加深理解。恩。就这样。&lt;/p&gt;&#xD;
&lt;p&gt;Let's get started~&lt;/p&gt;&#xD;
&lt;p&gt;对于基于比较的排序，我们知道，这里排序的依据是比较！（这真是句大实话，同时也是句废话O(&amp;cap;_&amp;cap;)O~）&lt;/p&gt;&#xD;
&lt;p&gt;现在假设我们待比较的数据是 &amp;nbsp;a1,a2,...,an，我们知道对于这n个数据（假设里面没有相等的元素），我们可以对这n个数据做一个全排列，里面有n!种。那么什么是decision tree呢？偷个懒，截了个算法导论的图。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041514105244.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;专门截了那么一大段文字，因为讲的很详细，希望不太明白的童鞋都先仔细看了再说。每个非叶子节点的数字可以想象成待比较的元素ai的下标i，其中root表示比较a1和a2，如果是小于走左枝，大于走右枝。下面类似。对于叶子节点，就是一个最后的算有元素下标的全排列，当然这也就是一个可能的最后的排序的结果了。这个应该还是很好理解的，不好理解的我在啰嗦一句，比如 输入是a1，a2，a3，a4， &amp;nbsp;我最后有个叶子节点是 [ 2 4 1 3]，这也就表示最后的排序结果是a2，a4，a1，a3。&lt;/p&gt;&#xD;
&lt;p&gt;这棵树看起来很简单，当然，他还是能给我很多有用的信息的，首先我们知道比较算法的复杂度和进行比较的次数紧密相关的。我们可以从树上看出，从root到leaf，一路上经历的比较次数，就是我们这个分支的高度，当然，很明显，我们是要考虑这棵树的最大的高度，也就是最长的那支。它表示了在最坏的情况下，我们需要的比较次数（这里好像和树的高度的offical的定义有冲突了，明白意思就好O(&amp;cap;_&amp;cap;)O~）&lt;/p&gt;&#xD;
&lt;p&gt;好，现在我们就开始找lower bound了~很显然，我们这棵树是二叉的，大于向左，小于向右，我们最后的高度是h，我们知道完全二叉树高为h的叶子最多有2^h个，同时我们也是到n个数据，最多有n！个排列，所以根据我们的decision tree，我们可以得到：&lt;/p&gt;&#xD;
&lt;p&gt;n! &amp;lt;= 2^h&lt;/p&gt;&#xD;
&lt;p&gt;当然简单变形一下，h&amp;gt;= lg(n!)&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;所以 h = Omega(nlgn)。（不明真相的童鞋请看开头给的链接，里面有一些basic的数学公式）&lt;/p&gt;&#xD;
&lt;p&gt;这说明了什么，这说明了对于基于比较的排序算法，我们至少需要Omega(nlgn)次比较，不然最下面那支的叶子节点的结果我们就得不到。当然，最重要的的是，我们这里得出的结论是对于任意的排序算法，任意的哦！&lt;/p&gt;&#xD;
&lt;p&gt;下面再来看看如何利用Adversary Strategy来得到这个lower bound。&lt;/p&gt;&#xD;
&lt;p&gt;首先解释一下Adversary Strategy的思路：&lt;/p&gt;&#xD;
&lt;p&gt;我们可以想象存在一个Adversary，当然我们中文里应该就叫神~有这么一个神，当我们在进行比较的时候，我们不能立刻得到比较的结果，我们要去问神，只有神知道，（神的力量无法无天....跑了....回到主题），每当我们要进行比较，都要从神那里得到比较的结果。&lt;/p&gt;&#xD;
&lt;p&gt;当然对于神来说，它总是希望拖延我们，然后尽量多的询问它 关于比较的结果。 所以在我们和神直接就存在了一种adversarial的关系。&lt;/p&gt;&#xD;
&lt;p&gt;针对于sorting，我们知道一共有n！种可能，比如a1，a2，a3，我们的可能的结果有[1 2 3] [1 3 2] [2 1 3] [2 3 1] [ 3 1 2 ] [ 3 2 1 ]，姑且叫它候选集合S，每当我们从神的那里得到两个数据的比较结果之后，我们就可以从候选集合中剔除若干元素，缩小候选集合的大小|S|，当|S|=1的时候，我们的任务就完成了。而神要做的是什么呢？神要做的就是他每次都要告诉我们一个结果，但是它总是想尽力的让我们的候选集合剩下的元素尽可能的多。不难看出，每次都让我们剩下的size大于等于前一次的一般，也就是我们每从神那里得到一个答案后，最多把候选集合里面的东西砍掉一般。所以为了让|S|从n！到1，我们至少需要问lg(n!)次，所以lower bound是Omega(nlgn)。当然如果我们没有问到nlgn次，我们的候选集合里面至少有两个元素，这时候算法要给出一个结果，而神总会说答案是另外的，这样我们的算法就fail了，这也就说明了小于nlgn的都是不行的。（有点类似反证）&lt;/p&gt;&#xD;
&lt;p&gt;上面证明了sorting的在worst case下的lower bound，下面我们还可以附带证明在average case的情况下，sorting的lower bound也是Omega(nlgn)。&lt;/p&gt;&#xD;
&lt;p&gt;其实通过上面的decision tree的方法，我们已经知道，找average case下的lower bound，其实也就是找那棵树的所有分支高度的平均值的lower bound。&lt;/p&gt;&#xD;
&lt;p&gt;如果我们的树是平衡二叉树，我们知道，每个分支都是nlgn，所以average case的lower bound就是Omega(nlgn)，但是对于根据比较得到的decision tree不是平衡二叉树呢？其实我们只要证明 &lt;strong&gt;the one that minimizes their average depth is a completely balanced tree。&lt;/strong&gt;这个其实也很显然，如果不是平衡二叉树，说明最深的节点减去最浅的节点的高度差大于等于二，这时我们可以把最深的那两个节点移接到最浅的那支上，这样我们减小了average heights，但是没有影响到别的，这也就说明了只有对于任何不平衡的树，我们都可以使他的average height更小一些，所以也就说明了average depth of leaves must be at least nlgn。证毕。&lt;/p&gt;&#xD;
&lt;p&gt;顺便可以证明一下对于所有的randomized sorting algorithm，比较次数的lower bound也是nlgn。&lt;a href="http://en.wikipedia.org/wiki/Randomized_algorithm"&gt;http://en.wikipedia.org/wiki/Randomized_algorithm&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;决定把这个放到下一篇文章中讲。O(&amp;cap;_&amp;cap;)O~&lt;/p&gt;&#xD;
&lt;p&gt;后面举几个例子来简单具体阐述一下上面说的两个方法。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 有两个长度为n的已经排好的list，a1&amp;lt;a2&amp;lt;...&amp;lt;an, b1&amp;lt;b2&amp;lt;...&amp;lt;bn，我们要把这两个list合并到一个list里面，保证这个list是increasing的，要证明在合并的过程中比较次数的lower bound是 2n-1.&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;假设我们用adversarial strategy证明，假设我们只比较了2n-2次，也就是我们只想神询问了2n-2次，这时我们知道有一个元素ai，他一定是没有和bj或者是bj+1比较的，假设我们的ai = 2i-1，也就是所有的奇数，bi = 2i，所有的偶数，那么这个时候，我们的算法已经给出了output，但是对于无所不能的神来说，它知道你没有比较ai和bj或者是没有比较ai和bj+1。&lt;/p&gt;&#xD;
&lt;p&gt;如果你没有比较ai和bi，你输出的是aibi，但是神可以交换ai和bi的值，这个时候ai = 2i，bi = 2i-1，交换的同时，依然保证a1&amp;lt;a2&amp;lt;...&amp;lt;an, b1&amp;lt;b2&amp;lt;...&amp;lt;bn，所以这个时候，你的算法错了。&lt;/p&gt;&#xD;
&lt;p&gt;同理，如果你没有比较ai和bi+1，神依旧可以交换ai和bj+1的值，导致你的算法还是错的，所以你的2n-2的算法悲剧了，所以证明lower bound是2n-1。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 给定一个长度为n的已经排好的list，a1&amp;lt;a2&amp;lt;...&amp;lt;an, 和一个数x，要找这个list里面有没有一个ai，ai==x。 给一个比较次数的lower bound，并证明。&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;这个我们可以用decision tree的方法找到这个lower bound，考虑下面的decision tree：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041603553096.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;从这个树上我们可以得出：&lt;/p&gt;&#xD;
&lt;p&gt;1）对于每一个非叶子节点，都有两个分支。&lt;/p&gt;&#xD;
&lt;p&gt;2）这个树一共有n个叶子节点（对应了我们n个可能的答案）&lt;/p&gt;&#xD;
&lt;p&gt;假设树的高度是h，我们知道最多可能出现的叶子节点数是 2^0+2^1+2^2+。。。+2^k-1，这是等于2^k-1的，当然，这个是大于等于n的，所以，就有 2^k-1 &amp;gt;=n, 可以得到 k &amp;gt;= lg(n+1)，这也就是，一路比较下去我们至少要用 lg(n+1)次比较。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 给一组无序的n个数，找出这n个数的最大值和最小值。证明比较次数的upper bound和lower bound都是 3n/2-2.&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;对于某个问题的upper bound的证明，我们只需要给出一个算法，这个算法的upper bound是要证的就行了；而对于一个问题的lower bound的证明，我们就需要证明对于任意解决这个问题的算法，都需要这个bound，所以就需要用到decision tree或者是adversarial strategy。&lt;/p&gt;&#xD;
&lt;p&gt;对于upper bound，我们的算法如下，我们每次从n个数中取两个进行比较，这样就比较了n/2次，每次把比较中较大的数放到大集合Big中，把较小的数放到小集合Small中，这样，Big集合的size是n/2,Small集合的size也是n/2.这样我们分别在Big集合中比较n/2-1次得到最大的元素，在small集合中比较n/2-1次得到最小的元素，这样我们的比较次数就是 n/2+n/2-1+n/2-1 = 3n/2-2。&lt;/p&gt;&#xD;
&lt;p&gt;对于lower bound，我们用adversarial strategy来证明，假设一开始我们把所有的元素都给予标记&amp;ldquo;+/-&amp;rdquo;，所有头上为&amp;ldquo;+&amp;rdquo;的代表它可能是最大的元素，头上为&amp;ldquo;-&amp;rdquo;的代表它可能是最小的元素，所以现在每次比较都去掉一些元素标记，直到最后有一个&amp;ldquo;+&amp;rdquo;和一个&amp;ldquo;-&amp;rdquo;，其他的头上都没有标记为止，这样也就表明了我们找到了最大元素和最小元素。很显然，我们一共去掉了2n-2个标记，也就是我们要求神在若干次比较之后去掉2n-2个标记，但是可能有一些比较，可以一下子去掉两个标记，比如两个元素都是&amp;ldquo;+/-&amp;rdquo;的进行比较，我们去掉大元素的&amp;ldquo;-&amp;rdquo;，去掉小元素的&amp;ldquo;+&amp;rdquo;；有些比较只能去掉一个标记。我们知道最多有n/2次比较可以一下子去掉2个marks，其他的一次比较都只能去掉1个mark，所以我们一共要去掉2n-2个标记，所以就至少需要 2n-2-n/2 = 3n/2-2次比较。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;strong&gt;&#xD;
&lt;p&gt;========================================================================================&lt;/p&gt;&#xD;
&lt;p&gt;本作品采用&lt;a rel="license" href="http://creativecommons.org/licenses/by/2.5/cn/"&gt;知识共享署名 2.5 中国大陆许可协议&lt;/a&gt;进行许可。 本博客采用知识共享署名 2.5 中国大陆许可协议进行许可。本博客版权归作者所有，欢迎转载，但未经作者同意&lt;b&gt;不得随机删除文章任何内容&lt;/b&gt;，且在文章页面&lt;b&gt;明显位置给出原文连接&lt;/b&gt;，否则保留追究法律责任的权利。如您有任何疑问或者授权方面的协商，请给&lt;a href="http://www.cnblogs.com/Gavin_Liu"&gt;我&lt;/a&gt;留言。(&lt;a href="http://www.cnblogs.com/Gavin_Liu/"&gt;http://www.cnblogs.com/Gavin_Liu/&lt;/a&gt;)&lt;/p&gt;&#xD;
&lt;/strong&gt;&#xD;
&lt;/div&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2016995.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/16/2016995.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/04/15/2015469.html</id><title type="text">漫谈算法（四）分治算法 Divide and Conquer Algorithm</title><summary type="text">Keywords: Divide and Conquer Algorithm; Mathematical Induction; Recurrence Tree; Master Theorem .</summary><published>2011-04-14T19:21:00Z</published><updated>2011-04-14T19:21:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/15/2015469.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/15/2015469.html"/><content type="html">&lt;div&gt;Keywords:&amp;nbsp;Divide and Conquer Algorithm; Mathematical Induction; Recurrence Tree; Master Theorem&amp;nbsp;.&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html" target="_blank"&gt;[为什么写这类文章]&lt;/a&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html"&gt;漫谈算法（零）序&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;[&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html"&gt;这系列文章里会用到的一下符号和公式&lt;/a&gt;] &amp;nbsp;&amp;nbsp;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html"&gt;漫谈算法（番外篇） 符号标记以及基本数学公式&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;先看一段来自wikipedia的定义：&lt;a href="http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm"&gt;http://en.wikipedia.org/wiki/Divide_and_conquer_algorithm&lt;/a&gt;&lt;/div&gt;&#xD;
&lt;div&gt;Divide and conquer (D&amp;amp;C) is an important algorithm design paradigm based on multi-branched recursion. A divide and conquer algorithm works by recursively breaking down a problem into two or more sub-problems of the same (or related) type, until these become simple enough to be solved directly. The solutions to the sub-problems are then combined to give a solution to the original problem.&lt;/div&gt;&#xD;
&lt;div&gt;简单翻译一下：&lt;/div&gt;&#xD;
&lt;div&gt;分治算法是基于多分枝递归的一种算法设计模式。分治算法递归地把一个大问题分解为多个类型相同的子问题，直到这些子问题足够的简单能被直接解决。最后把这些子问题的解结合起来就能得到原始问题的解。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;根据这个定义，我们可以很明显的知道，对于D&amp;amp;C问题，我们解决它需要两步，一是要找到递归式；二是要根据递归式能找到最后的答案。&lt;/div&gt;&#xD;
&lt;div&gt;首先，什么是递归式。&lt;/div&gt;&#xD;
&lt;div&gt;下面是从算法导论（Introduction to Algorithm Edition 3）上copy下的一小段话，解释的相当清楚。&lt;/div&gt;&#xD;
&lt;div&gt;Recurrences go hand in hand with the divide-and-conquer paradigm, because theygive us a natural way to characterize the running times of divide-and-conquer algorithms.A recurrence is an equation or inequality that describes a function in terms&amp;nbsp;of its value on smaller inputs.&lt;/div&gt;&#xD;
&lt;div&gt;举个例子，&lt;/div&gt;&#xD;
&lt;div&gt;T(n) = 4T(n/2) + n，这个表达式就是我们的递归式。&lt;/div&gt;&#xD;
&lt;div&gt;对于不同的问题，我们得到的递归式各不相同，通常这个是因问题而已。但总体思路是如何分解原始的问题。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;而本文的重点则是在后面，介绍如何在有了递归式的情况下，找到问题的答案。如分析某个问题的时间复杂度。&lt;/div&gt;&#xD;
&lt;div&gt;通常，解决这类问题（一直递归式，找答案）有三种方法：&lt;/div&gt;&#xD;
&lt;div&gt;1. Mathematical Induction 数学归纳法&lt;/div&gt;&#xD;
&lt;div&gt;2. Recursion Tree 画递归树找规律&lt;/div&gt;&#xD;
&lt;div&gt;3. Master Theorem 主定理(好像中文版的算法导论上就是这样翻译的，感觉真挫)&lt;/div&gt;&#xD;
&lt;div&gt;后面就简单针对以上三种方法用具体例子做一个简单的说明。&lt;/div&gt;&#xD;
&lt;div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;1. Mathematical Induction 数学归纳法&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div&gt;使用数学归纳法，这个大家基本都清楚，就是假设一个在n的时候结论成立，证明在n+1的时候结论也成立，当然，在我们这里，稍微有点变化。举个例子。&lt;/div&gt;&#xD;
&lt;div&gt;T(n) = T(n/2) + T(n/4) + T(n/8) + n&lt;/div&gt;&#xD;
&lt;div&gt;现在我们要就上面表达式T(n)，现在我们就先guess T(n) = Theta(n)。&lt;/div&gt;&#xD;
&lt;div&gt;当然我们知道要证明T(n) = Theta(n)，我们需要分别证明T(n) = O(n)和T(n) = Omega(n)。&lt;/div&gt;&#xD;
&lt;div&gt;很显然，这里T(n) = Omega(n)的，因为T(n) = T(n/2) + T(n/4) + T(n/8) + n &amp;gt; n，显然，T(n) = Omega(n).&lt;/div&gt;&#xD;
&lt;div&gt;下面用数学归纳法证明T(n) = O(n)&lt;/div&gt;&#xD;
&lt;div&gt;假设T(n) &amp;lt;= cn，所以其中c是一个常数。&lt;/div&gt;&#xD;
&lt;div&gt;所以T(n) &amp;lt;= c*n/2 + c*n/4 + c*n/8 + n = (7c/8+1)n&lt;/div&gt;&#xD;
&lt;div&gt;我们只需让(7c/8+1)n &amp;lt;= cn，显然我们可以找到一个正常数c使该式成立。所以T(n) = O(n)。&lt;/div&gt;&#xD;
&lt;div&gt;综上，T(n) = Theta(n)。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;上面的证明是没有问题了，但是可能有朋友要问，凭什么你一开始就guess&amp;nbsp;T(n) = Theta(n) 呢？没错，make a good guess 是这种方法的关键，下面就简单的说一下make this guess的intuition。&lt;/div&gt;&#xD;
&lt;div&gt;首先，我们可以简单的画出下面这棵树。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041413141837.jpg" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当然我们可以发现，在每一层，随着递归的深入，每一层的总cost是减小的，所以我们可以推测，在层数很大的情况下，那一层的总cost很小，无法和顶层相比，这也就是说，这个问题的T(n)主要由顶层决定，最顶层是n，所以我们有理由guess答案是cn。c为一常数。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;下面在介绍两个guess的小tricks&lt;/div&gt;&#xD;
&lt;div&gt;1）遇到T(n) = aT(n/b + d) + f(n)的时候，在绝大多数的情况下，我们可以直接忽略d来进行guess，因为当n足够大的时候 n/b + d 和 n/b 几乎是一样的。&lt;/div&gt;&#xD;
&lt;div&gt;2）guess的时候可以加一个常数项，比如上面guess T(n) = O(n)的时候，我们用的是 T(n)&amp;lt;= cn，有时候推导不顺利的时候，可以试试 用T(n) &amp;lt;= cn-d，这样可以摆脱一些常数项的干扰。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;2. Recursion Tree 画递归树找规律&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div&gt;其实这个方法和前面的画树的方法很相似，这里就举一个简单的例子来说明问题。&lt;/div&gt;&#xD;
&lt;div&gt;现在假设我们的递归式是 T(n) = aT(n/b) + n^k。 这里由于画树比较麻烦（偷个懒(*^__^*) 。。。）我就画成表格了，其实原理都一样，只是呈现的方式不一样。&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041413550132.jpg" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;当然，我们知道最后的level是 log_{b}^n,然后我们把最后一列相加，就得到了我们的结果，&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041413581093.jpg" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;最后，再啰嗦一句。&lt;/div&gt;&#xD;
&lt;div&gt;在找树的高度height的时候，有时候可能我们会遇到 T(n) = T(n/3)+T(2n/3)+O(n)类似的问题，这个时候树的高度可能不能一下子得到，其实我们如果画出recursion tree，如下图，可以知道，树的高度有最长的那个分支决定，也就是n---(2/3)n---(2/3)^2n...---1这条分支，所以高度k 应该满足 (2/3)^k * n = 1，所以k = log_{3/2}^{n}.&amp;nbsp;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041413294593.jpg" /&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;&lt;br /&gt;&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;3. Master Theorm 主定理&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;这个其实没有什么好讲的，定理的证明比较复杂，感兴趣的朋友可以直接查阅算法导论第四张最后几个小节，这里就把结论贴上来了。&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;通常，条件符合，就直接得结论。&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041413043120.jpg" /&gt;&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&#xD;
&lt;div&gt;&lt;strong&gt;&#xD;
&lt;p&gt;========================================================================================&lt;/p&gt;&#xD;
&lt;p&gt;本作品采用&lt;a href="http://creativecommons.org/licenses/by/2.5/cn/" rel="license"&gt;知识共享署名 2.5 中国大陆许可协议&lt;/a&gt;进行许可。 本博客采用知识共享署名 2.5 中国大陆许可协议进行许可。本博客版权归作者所有，欢迎转载，但未经作者同意&lt;b&gt;不得随机删除文章任何内容&lt;/b&gt;，且在文章页面&lt;b&gt;明显位置给出原文连接&lt;/b&gt;，否则保留追究法律责任的权利。如您有任何疑问或者授权方面的协商，请给&lt;a href="http://www.cnblogs.com/Gavin_Liu"&gt;我&lt;/a&gt;留言。(&lt;a href="http://www.cnblogs.com/Gavin_Liu/"&gt;http://www.cnblogs.com/Gavin_Liu/&lt;/a&gt;)&lt;/p&gt;&#xD;
&lt;br /&gt;&lt;/strong&gt;&lt;/div&gt;&#xD;
&lt;/div&gt;&#xD;
&lt;div&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2015469.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/15/2015469.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/04/13/2011214.html</id><title type="text">漫谈算法（二） 动态规划 Dynamic Programming</title><summary type="text">Keywords: Dynamic Programming; Recursive Methods</summary><published>2011-04-12T19:11:00Z</published><updated>2011-04-12T19:11:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/13/2011214.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/13/2011214.html"/><content type="html">&lt;p&gt;&lt;strong&gt;Keywords: Dynamic Programming; Recursive Methods&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html"&gt;[为什么写这类文章]&lt;/a&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html" target="_blank"&gt;漫谈算法（零）序&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;[&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html" target="_blank"&gt;这系列文章里会用到的一下符号和公式&lt;/a&gt;] &amp;nbsp;&amp;nbsp;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html" target="_blank"&gt;漫谈算法（番外篇） 符号标记以及基本数学公式&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;动态规划，Dynamic Programming。这里的programming没有翻译成编程，是因为，这里的programming的意思是指一个tabular method。其实这也暗示了DP的本质，用一个table保存子问题的中间结果。（后面会有例子具体介绍）&lt;/p&gt;&#xD;
&lt;p&gt;和分治算法比较类似，但不同的是分治算法把原问题划归为几个相互独立的子问题，从而一一解决，而动态规划则是针对子问题有重叠的情况的一种解决方案。&lt;/p&gt;&#xD;
&lt;p&gt;目前design DP主要有两个思路：&lt;/p&gt;&#xD;
&lt;p&gt;一个是利用recursive method，即首先把问题用递归的方法解决，然后用一个table保存recursive中的中间结果，这不就避免了递归中重复计算的低效了吗？遇到需要计算以前计算过的东西，直接查表就OK，总之一句话，先写recursive，然后比葫芦画瓢基本就能把DP的方法写出来。这里的难点是如何找到recursive。算法导论里面也给的是这个思路。下面的前三个例子全部出自《算法导论》。&lt;/p&gt;&#xD;
&lt;p&gt;另一个思路是exhaust search，这个好像是我们老师发明的方法，这里有篇Kirk的论文，&lt;b&gt;&lt;a href="http://www.cs.pitt.edu/~kirk/papers/dynprog.ps"&gt;&amp;nbsp;How to design dynamic programming algorithms sans recursion&lt;/a&gt;&amp;nbsp;&amp;nbsp;&lt;/b&gt;有兴趣的大家可以仔细研究一下，我下面也会简单举例介绍一下这个方法。&lt;/p&gt;&#xD;
&lt;p&gt;下面的例子中多数代码都是伪代码，旨在illustrate idea。同时节省时间。代码中都省去了backtrack的过程，即只得到了optimal solution的值，省去了如何construct optimal solution的过程。这个一般用一个数组记录一下就OK了。&lt;/p&gt;&#xD;
&lt;p&gt;先来个比较简单的例子（其实后面的也不难O(&amp;cap;_&amp;cap;)O~）&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例：Rod-cutting problem&lt;/strong&gt;(切木头问题。感觉翻译过来怎么就变了味呢？囧。。。)&lt;/p&gt;&#xD;
&lt;p&gt;Input：有一个长n米的木头，和一个price table，table如下：&lt;/p&gt;&#xD;
&lt;p&gt;长度 i &amp;nbsp; &amp;nbsp; 1　　2　　3　　4　　5　　6 。。。&lt;/p&gt;&#xD;
&lt;p&gt;价格 Pi　1　　5　　8　　9　　10　　17。。。&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;意思很明显，就是长度为1米的木头可以买1元，长5米的可以卖10元，依次类推&lt;/p&gt;&#xD;
&lt;p&gt;Output：找一个cut的方法，使最后赚的钱最多。&lt;/p&gt;&#xD;
&lt;p&gt;很显然，这个递归的主要思路是我切一刀之后，分成两段，一段我按table的价钱卖了，另一段我当成一个新的子问题，继续作为我的函数的新的参数，这样不就递归了吗？(*^__^*) 但是问题是这一刀怎么切，没错，我们就来个找最大值，即max_{i =1 to n} Pi + Cut(n-i).&lt;/p&gt;&#xD;
&lt;p&gt;所以，递归函数应该是：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;span&gt;Cut(P, n){&amp;nbsp;&lt;/span&gt;&lt;span&gt;//&lt;/span&gt;&lt;span&gt;P 就是我的table，n是木头长度&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;if&lt;/span&gt;&lt;span&gt;&amp;nbsp;n&amp;nbsp;&lt;/span&gt;&lt;span&gt;==&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;0&lt;/span&gt;&lt;span&gt;;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&lt;span&gt;q&amp;nbsp;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;infinity&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;for&lt;/span&gt;&lt;span&gt;&amp;nbsp;i&amp;nbsp;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;1&lt;/span&gt;&lt;span&gt;&amp;nbsp;to n&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;span&gt;q&amp;nbsp;&lt;/span&gt;&lt;span&gt;=&lt;/span&gt;&lt;span&gt;&amp;nbsp;max(q,P[i]&lt;/span&gt;&lt;span&gt;+&lt;/span&gt;&lt;span&gt;Cut(P,n&lt;/span&gt;&lt;span&gt;-&lt;/span&gt;&lt;span&gt;i))&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span&gt;return&lt;/span&gt;&lt;span&gt;&amp;nbsp;q;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;span&gt;}&lt;/span&gt;&lt;/p&gt;&#xD;
&lt;p&gt;然后，根据这个recursive写DP&lt;/p&gt;&#xD;
&lt;p&gt;Cut(P, n){&lt;/p&gt;&#xD;
&lt;p&gt;for(int i = 1; i&amp;lt;=n; i++){&lt;/p&gt;&#xD;
&lt;p&gt;q = -infinity;&lt;/p&gt;&#xD;
&lt;p&gt;for(int j = 1; j&amp;lt;=i; j++)&lt;/p&gt;&#xD;
&lt;p&gt;q = max(q, P[j] + r[i-j]);&lt;/p&gt;&#xD;
&lt;p&gt;r[i] = q;&lt;/p&gt;&#xD;
&lt;p&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;return r[n];&lt;/p&gt;&#xD;
&lt;p&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 最长公共字串&amp;nbsp;Longest common subsequence problem &amp;nbsp;&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;问题描述：这个，很。。。显而易见吧，不知道的，。。。看这里&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Longest_common_subsequence_problem"&gt;http://en.wikipedia.org/wiki/Longest_common_subsequence_problem&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;当然这里，我们也是要先找递归的。假设我的两个sequence，一个是X，长度为n；另一个是Y，长度为m。&lt;/p&gt;&#xD;
&lt;p&gt;现在假设我有两个point，一个是i，指在X的最后一个元素上，另一个是j，指在Y的最后一个元素上。我们的递归应该是分三种情况的。&lt;/p&gt;&#xD;
&lt;p&gt;1）如果X[i] == Y[j] 那么LCS(X[i],Y[j]) = LCS(X[i-1],Y[j-1]) + 1&lt;/p&gt;&#xD;
&lt;p&gt;这个很明显，因为发现了一组公共元素，就看剩下的有多少公共元素。&lt;/p&gt;&#xD;
&lt;p&gt;2) 如果X[i] != Y[j] 那么 LCS(X[i],Y[j]) = max( LCS(X[i-1], Y[j]), LCS(X[i], Y[j-1]) )&lt;/p&gt;&#xD;
&lt;p&gt;这个其实也很容易相同，就是如果发现不同的，就去掉X或Y的最后一个，然后和另一个完整的比较，这样去掉X还是Y的最后一个，就有两种可能，所以就是要找中间max的一个。&lt;/p&gt;&#xD;
&lt;p&gt;3) 如果 i=0 或者 j=0，就return 0&lt;/p&gt;&#xD;
&lt;p&gt;因为有一个sequence已经完了。&lt;/p&gt;&#xD;
&lt;p&gt;所以递归这里还是比较明显的：&lt;/p&gt;&#xD;
&lt;p&gt;LCS(n,m){&lt;/p&gt;&#xD;
&lt;p&gt;if m==0 || n==0&lt;/p&gt;&#xD;
&lt;p&gt;return 0;&lt;/p&gt;&#xD;
&lt;p&gt;if(X[n] == Y[m])&lt;/p&gt;&#xD;
&lt;p&gt;return LCS(n-1,m-1)+1;&lt;/p&gt;&#xD;
&lt;p&gt;else&lt;/p&gt;&#xD;
&lt;p&gt;return max( LCS(n,m-1), LCS(n-1,m) );&lt;/p&gt;&#xD;
&lt;p&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;现在有了递归，我们就应该能把它写成tabular的DP。&lt;/p&gt;&#xD;
&lt;p&gt;初始化：Table LCS的第一行第一列都设为0。&lt;/p&gt;&#xD;
&lt;p&gt;for i = 1 to m&lt;/p&gt;&#xD;
&lt;p&gt;for j = 1 to n&lt;/p&gt;&#xD;
&lt;p&gt;if(X[i]==Y[j])&lt;/p&gt;&#xD;
&lt;p&gt;LCS[i,j] = LCS[i-1,j-1]+1;&lt;/p&gt;&#xD;
&lt;p&gt;else&lt;/p&gt;&#xD;
&lt;p&gt;LCS[i,j] = max(LCS[i,j-1], LCS[i-1,j] );&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 矩阵链相乘，求最小的乘法次数的问题&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;&amp;nbsp;&lt;/strong&gt;问题具体描述，看这里&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Matrix_chain_multiplication"&gt;http://en.wikipedia.org/wiki/Matrix_chain_multiplication&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;先形式化一下我们的input，矩阵链{A1，A2，...，An}，其中对于矩阵Ai，它是一个Pi-1 * Pi的矩阵。m[i,j] 表示{Ai,Ai+1,..., Aj}相乘的最小的乘法次数（强烈呼吁出现一个类似Latex功能的插件）&lt;/p&gt;&#xD;
&lt;p&gt;这个递归的思路其实和第一个问题有点类似，先看怎么递归。&lt;/p&gt;&#xD;
&lt;p&gt;首先我们要找出一个点来分隔这个链，相当于这个点把这个问题转换为了 该点前一部分的矩阵链的最小的乘法次数问题和该点后一部分的矩阵链的最小的乘法次数问题。&lt;/p&gt;&#xD;
&lt;p&gt;但是这个点怎么找呢？和第一个例子一样，我们直接遍历的来找。&lt;/p&gt;&#xD;
&lt;p&gt;所以我们的递归应该是这样的：&lt;/p&gt;&#xD;
&lt;p&gt;m[i,j] = min_{i&amp;lt;= k &amp;lt;=j} (m[i,k] + m[k+1,j] + Pi-1*Pk*Pj)&lt;/p&gt;&#xD;
&lt;p&gt;当然，这是对于i!=j的情况，当i==j的时候呢？显然 是0了，因为我们的矩阵链就一个矩阵。&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;即&lt;/p&gt;&#xD;
&lt;p&gt;Matrix-chain(i,j){&lt;/p&gt;&#xD;
&lt;p&gt;if(i==j)&lt;/p&gt;&#xD;
&lt;p&gt;return 0;&lt;/p&gt;&#xD;
&lt;p&gt;q = infinity;&lt;/p&gt;&#xD;
&lt;p&gt;for k = i to j&lt;/p&gt;&#xD;
&lt;p&gt;q = min(q, Matrix-chain(i,k)+Matrix-chain(k+1,j)+Pi-1*Pk*Pj&amp;nbsp;);&lt;/p&gt;&#xD;
&lt;p&gt;return q;&lt;/p&gt;&#xD;
&lt;p&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;现在就是把这个recursive的东西搞成DP了。算法导论上有具体的代码，这里只写个简要的。&lt;/p&gt;&#xD;
&lt;p&gt;之类就假设我们保存中间结果的是一个n*n的table m.&lt;/p&gt;&#xD;
&lt;p&gt;初始化：m的对角元素初始化为0.&lt;/p&gt;&#xD;
&lt;p&gt;for l = 2 to n&lt;/p&gt;&#xD;
&lt;p&gt;for i=1 to n-l+1&lt;/p&gt;&#xD;
&lt;p&gt;j = i+l-1&lt;/p&gt;&#xD;
&lt;p&gt;m[i,j] = infinity&lt;/p&gt;&#xD;
&lt;p&gt;for k=i to j-1&lt;/p&gt;&#xD;
&lt;p&gt;q = m[i,k]+m[k+1,j]+Pi-1*Pk*Pj;&lt;/p&gt;&#xD;
&lt;p&gt;if(q &amp;lt; m[i,j])&lt;/p&gt;&#xD;
&lt;p&gt;m[i,j] = q;&lt;/p&gt;&#xD;
&lt;p&gt;return m&lt;/p&gt;&#xD;
&lt;p&gt;参照上面的recursive的例子，这个DP的代码应该不难理解。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 最长回文序列&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;回文序列就是从左往右读和从右往左读是一样的。比如civic,racecar, and aibohphobia。其中长度为1的所有string都是回文的。&lt;/p&gt;&#xD;
&lt;p&gt;目标就是给一个string，找出这个string中最长的回文序列，比如给character，找出carac。&lt;/p&gt;&#xD;
&lt;p&gt;下面主要介绍一下，我们老师发明的那个方法，&lt;strong&gt;穷尽搜索加减枝&lt;/strong&gt;~还是&lt;strong&gt;exhaust search with pruning&lt;/strong&gt;好听 O(&amp;cap;_&amp;cap;)O~&lt;/p&gt;&#xD;
&lt;p&gt;这个方法的主要思想也就是首先通过构造一棵树，使得树的叶子节点成为一个可能的最终的解，换句话说也就是最后的答案一定蕴育在树的最下面一层的某个叶子节点中。很显然，一般情况下这个树还是比较好构造的，毕竟不用考虑什么条件，就是一个exhaust 展开的过程，但是随着这个树的展开，分枝越来越多，这样肯定是不make sense的，所以需要找到一些pruning rules，来修剪这棵树。其实说了半天，肯定也比较模糊，还是先看个例子吧，我觉得看完例子，在看这段话肯定比较有感觉。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 最长递增字串&amp;nbsp;&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;问题描述：&lt;a href="http://en.wikipedia.org/wiki/Longest_increasing_subsequence_problem"&gt;http://en.wikipedia.org/wiki/Longest_increasing_subsequence_problem&lt;/a&gt;&amp;nbsp;&amp;nbsp; （其实显而易见）&lt;/p&gt;&#xD;
&lt;p&gt;假设我的一串input是 X1，X2，X3，。。。，Xn，按下图构造我们的树（enumeration tree）。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041012031160.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;如何构造这棵树通过上面这个图，我想应该显而易见了吧，最终我的的解一定是在某个叶子节点上的。&lt;/p&gt;&#xD;
&lt;p&gt;在这里，最主要的是找到prunning rules。&lt;/p&gt;&#xD;
&lt;p&gt;当然对于这个LIS问题来说，什么才是我们的prunning rules呢？&lt;/p&gt;&#xD;
&lt;p&gt;1）很显然，如果某个节点的序列不是递增的，我们应该直接剪掉，因为。。。显然啊~&lt;/p&gt;&#xD;
&lt;p&gt;2）对于同一层的节点来说，如果同一层的某两个节点具有相同的递增长度，我们应该保留 那个序列，他的最后一个元素的数值最小。（怎么感觉说的这么别扭。怒）比如在某一层上 &amp;nbsp;一个节点是 2 4 7， 另一个节点是 3 4 6，他们的递增长度都是3，但是因为6&amp;lt;7，所以我们应该保留3 4 6，把2 4 7这个节点直接剪掉。&lt;/p&gt;&#xD;
&lt;p&gt;3）对于同一层的节点来说，如果某两个节点最后一个元素的数值一样，我们应该保留长度较长的那个节点，剪掉较短的那个。举例：一个节点 &amp;nbsp;2 &amp;nbsp;3 &amp;nbsp;6，一个节点 &amp;nbsp;3 &amp;nbsp;6， 我们保留 2 3 6，剪掉 3 6。&lt;/p&gt;&#xD;
&lt;p&gt;其实我们可以发现，（2）（3）实际上说的是一回事，所以，我们可以根据（1）（2）写出我们的DP，也可以根据（1）（3）写出我们的DP。&lt;/p&gt;&#xD;
&lt;p&gt;神马？这样就可以了？Yes， it's too simple, sometimes naive???!!!&lt;/p&gt;&#xD;
&lt;p&gt;根据（1）（2）&lt;/p&gt;&#xD;
&lt;p&gt;A[l,s] 这个是我们table A里面的一个cell，l表示树的level，s表示长度，A[l,s]表示在该节点的最后一个元素的数值。&lt;/p&gt;&#xD;
&lt;p&gt;for l = 1 to n&lt;/p&gt;&#xD;
&lt;p&gt;for s = 1 to l&lt;/p&gt;&#xD;
&lt;p&gt;if A[l,s] then&lt;/p&gt;&#xD;
&lt;p&gt;A[l+1,s] = min(A[l+1,s], A[l,s])&lt;/p&gt;&#xD;
&lt;p&gt;if A[l,s] &amp;lt; X_{l+1} then&lt;/p&gt;&#xD;
&lt;p&gt;A[l+1,s+1] = min(A[l+1,s+1], X_l+1)&lt;/p&gt;&#xD;
&lt;p&gt;根据（1）（3）&lt;/p&gt;&#xD;
&lt;p&gt;A[l,s] 这个是我们table A里面的一个cell，l表示树的level，s表示该节点的最后一个元素的下标，即最后一个元素是Xs，A[l,s]表示长度。&lt;/p&gt;&#xD;
&lt;p&gt;for l = 1 to n&lt;/p&gt;&#xD;
&lt;p&gt;for s = 1 to l&lt;/p&gt;&#xD;
&lt;p&gt;if A[l,s] then&lt;/p&gt;&#xD;
&lt;p&gt;A[l+1,s] = max(A[l+1,s], A[l,s])&lt;/p&gt;&#xD;
&lt;p&gt;if Xs &amp;lt; X_{l+1}&amp;nbsp;//如果在l+1层新添加的元素可以增加我们的序列的长度&lt;/p&gt;&#xD;
&lt;p&gt;//A[l+1,s]+1 表示在原来的基础上在加上这个新元素&lt;/p&gt;&#xD;
&lt;p&gt;//A[l+1,l+1] 表示在以新来的的元素X_{l+1}结尾的序列中，找一个最长的出来。&lt;/p&gt;&#xD;
&lt;p&gt;A[l+1,l+1] = max(A[l+1,l+1], A[l+1,s]+1)&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 找零钱问题&lt;/strong&gt; &amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;Input： 若干个Coins， X1，X2，..., Xn 和一个 数值 L&lt;/p&gt;&#xD;
&lt;p&gt;Output： 能不能用上面的若干面值的Coins 凑成 正好是L的金额。&lt;/p&gt;&#xD;
&lt;p&gt;同样，在这个问题中，和前面LIS中构造树的方法一样。&lt;/p&gt;&#xD;
&lt;p&gt;现在来看看我们的prunning rules。&lt;/p&gt;&#xD;
&lt;p&gt;1）如果某个节点的面值总和大于L，直接剪掉。&lt;/p&gt;&#xD;
&lt;p&gt;2）同一个level上，如果有两个节点的面额一样，随便剪掉一个。&lt;/p&gt;&#xD;
&lt;p&gt;恩，这个剪枝规则更简单了。&lt;/p&gt;&#xD;
&lt;p&gt;下面写DP。&lt;/p&gt;&#xD;
&lt;p&gt;用sum(l，s)=1 来表示在第l层，面额s是可以取到的。&lt;/p&gt;&#xD;
&lt;p&gt;for l = 1 to n&lt;/p&gt;&#xD;
&lt;p&gt;for s = 1 to L&lt;/p&gt;&#xD;
&lt;p&gt;if sum(l,s) then&lt;/p&gt;&#xD;
&lt;p&gt;sum(l+1,s) = 1&lt;/p&gt;&#xD;
&lt;p&gt;if sum(l,s) + X_{l+1} &amp;lt; L&lt;/p&gt;&#xD;
&lt;p&gt;sum(l+1,s+X_{l+1}) = 1&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 背包问题&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;Input：若干个物品，X1，X2，...,Xn，他们的价值是V1，V2，...,Vn，他们的重量是W1，W2，...,Wn，和给你一个重量的limit L，&lt;/p&gt;&#xD;
&lt;p&gt;Output：在weight总和less than L的情况下，拿哪些物品，使他们的value最大。&lt;/p&gt;&#xD;
&lt;p&gt;同样，在这个问题中，和前面LIS中构造树的方法一样。&lt;/p&gt;&#xD;
&lt;p&gt;现在来看看我们的prunning rules。&lt;/p&gt;&#xD;
&lt;p&gt;1）如果某个节点的重量的总和超过了L，剪掉。&lt;/p&gt;&#xD;
&lt;p&gt;2）在同一层，如果某两个节点具有相同的weight，剪掉value小的。&lt;/p&gt;&#xD;
&lt;p&gt;3）在同一层，如果某两个节点具有相同的value，剪掉weight大的。&lt;/p&gt;&#xD;
&lt;p&gt;和LIS的例子一样，我们可以用（1）（2）也可以用（1）（3）&lt;/p&gt;&#xD;
&lt;p&gt;下面写DP。&lt;/p&gt;&#xD;
&lt;p&gt;根据（1）（2）&lt;/p&gt;&#xD;
&lt;p&gt;sum(l,s)表示在l层重量为s的某个节点的value&lt;/p&gt;&#xD;
&lt;p&gt;for l = 1 to n&lt;/p&gt;&#xD;
&lt;p&gt;for s = 1 to \sum_{i=1}^l Wi (latex的标记方法，无奈，这个editor里没法打数学公式)&lt;/p&gt;&#xD;
&lt;p&gt;if sum(l,s) then&lt;/p&gt;&#xD;
&lt;p&gt;sum(l+1,s) = max( sum(l+1,s), sum(l,s) )&lt;/p&gt;&#xD;
&lt;p&gt;if sum(l,s) + X_{l+1} &amp;lt; L then&lt;/p&gt;&#xD;
&lt;p&gt;sum(l+1, s+X{l+1}) = max( sum(l+1, s+X{l+1}), sum(l,s) +&amp;nbsp;V_{l+1})&lt;/p&gt;&#xD;
&lt;p&gt;根据（1）（3）的这里就不写了，和上面的原理一样，比葫芦画瓢就应该能写出来。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例：字符串pattern匹配&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;Input: 一序列字符串X[m]，每个字符带cost： A(3) B(4) B(6) A(2) C(2) C(5) A(1) ....,假设该字符串长为m;一个待匹配的pattern Y[n]: A B A C...，假设pattern的长为n&lt;/p&gt;&#xD;
&lt;p&gt;Output: 找到一个cost和最小的字符串，匹配pattern（这里的匹配各个字符在原字符串中是可以有间隔的，例如字符串是A(3) B(4) B(6) A(2) C(2) C(5) A(1)，pattern是ABAC，我可以取A(3) B(6) A(2) C(5)，这样总cost是3+6+2+5 = 16&amp;nbsp;）&lt;/p&gt;&#xD;
&lt;p&gt;作为最后一个例子，将用上面提到的两种方法来解决这个问题。&lt;/p&gt;&#xD;
&lt;p&gt;首先，我们来找找递归的方法。&lt;/p&gt;&#xD;
&lt;p&gt;其实这个思路和找最长公共字串的问题有一些相似。这里扫描给定的字符串序列还是pattern都是从最后一个字符开始往前扫描。&lt;/p&gt;&#xD;
&lt;p&gt;Cost(i, j) 有这么几种情况，&lt;/p&gt;&#xD;
&lt;p&gt;1）i == 0，直接返回infinity。因为这也就表示我们没有在给定的字符串X中找到我们的pattern Y。&lt;/p&gt;&#xD;
&lt;p&gt;2）j == 0，直接返回0。表示我们找到了目标。&lt;/p&gt;&#xD;
&lt;p&gt;3）if X[i] ！= Y[j]，return Cost(i-1,j)这个时候，由于我们的pattern没有匹配，所以我们只能进行探索X下一个字符。注意这里j没有减一，很显然，因为pattern没有匹配（再多啰嗦一句）&lt;/p&gt;&#xD;
&lt;p&gt;4）if X[i] == Y[j]，return min(Cost(i-1,j-1)+c(X[i]), Cost(i-1,j) ) &amp;nbsp;这里的min表示，当我们遇到一个匹配的时候，我们有两个选择，用这个X中的字符当作我们最后匹配中的一个，也可以是我们放弃这次匹配，继续往下找。&lt;/p&gt;&#xD;
&lt;p&gt;下面把这个recursive写完整。&lt;/p&gt;&#xD;
&lt;p&gt;Cost(m,n){&lt;/p&gt;&#xD;
&lt;p&gt;if(m == 0)&lt;/p&gt;&#xD;
&lt;p&gt;return infinity;&lt;/p&gt;&#xD;
&lt;p&gt;if (n == 0)&lt;/p&gt;&#xD;
&lt;p&gt;return 0;&lt;/p&gt;&#xD;
&lt;p&gt;if(X[m] != Y[n])&lt;/p&gt;&#xD;
&lt;p&gt;return Cost(m-1, n);&lt;/p&gt;&#xD;
&lt;p&gt;else&lt;/p&gt;&#xD;
&lt;p&gt;return min( Cost(m-1,n-1)+c(X[i]), Cost(m-1,n) );&amp;nbsp;&lt;/p&gt;&#xD;
&lt;p&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;下面老规矩，根据recursive call 写DP。&lt;/p&gt;&#xD;
&lt;p&gt;假设我们的table是一个m*n的table，那么&lt;/p&gt;&#xD;
&lt;p&gt;当然很显然，在这个问题的初始化的时候，我们要把m=0的那一行初始化为无穷大，把n=0的那一列初始化为0.&amp;nbsp;在初始化完后，&lt;/p&gt;&#xD;
&lt;p&gt;for(int i = 0; i &amp;lt; m; i++)&lt;/p&gt;&#xD;
&lt;p&gt;for(int j = 0; j&amp;lt; n; j++){&lt;/p&gt;&#xD;
&lt;p&gt;if X[i] != Y[j]&lt;/p&gt;&#xD;
&lt;p&gt;Cost[i,j] = Cost[i-1,j];&lt;/p&gt;&#xD;
&lt;p&gt;else&lt;/p&gt;&#xD;
&lt;p&gt;Cost[i,j] = min( Cost[i-1,j-1]+c(X[i]), Cost[i-1,j] );&lt;/p&gt;&#xD;
&lt;p&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;现在我们来考虑怎么用exhaust search的方法解决这个问题。&lt;/p&gt;&#xD;
&lt;p&gt;还是和上面的那个图一样的树。我们知道X的size是m，所以我们可以穷举中他的字串，一共2^m个，我们最后匹配的pattern一定是这2^m中的一个，所以我们按照这个思路构造我们的树，这棵树一共有m层。每一层得节点A[l,s]表示在l层，匹配了pattern中s个字符的最小cost。&lt;/p&gt;&#xD;
&lt;p&gt;很显然，我们的prunning rules是 对于同一层中的两个节点，如果他们匹配的长度相等，即s相等，我们应该prune掉那个cost大的。&lt;/p&gt;&#xD;
&lt;p&gt;for(int l = 0; l &amp;lt; m; l++)&lt;/p&gt;&#xD;
&lt;p&gt;for(int s = 0; s &amp;lt; l; s++){&lt;/p&gt;&#xD;
&lt;p&gt;if A[l,s] then&lt;/p&gt;&#xD;
&lt;p&gt;A[l+1,s] = min( A[l+1,s], A[l,s]);&lt;/p&gt;&#xD;
&lt;p&gt;if X[l+1] == Y[s+1]&lt;/p&gt;&#xD;
&lt;p&gt;A[l+1,s+1] = min( A[l+1,s+1], A[l+1, s] + cost(X[l+1]))&lt;/p&gt;&#xD;
&lt;p&gt;}&lt;/p&gt;&#xD;
&lt;p&gt;最后啰嗦一句，这个exhaust search的方法其实也不是一下子很容易让人理解，所以，还是推荐看一下老师的一个tutorial的论文&amp;nbsp;&lt;b&gt;&lt;a href="http://www.cs.pitt.edu/~kirk/papers/dynprog.ps"&gt;&amp;nbsp;How to design dynamic programming algorithms sans recursion&lt;/a&gt;&amp;nbsp;。&lt;/b&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&#xD;
&lt;p&gt;========================================================================================&lt;/p&gt;&#xD;
&lt;p&gt;本作品采用&lt;a rel="license" href="http://creativecommons.org/licenses/by/2.5/cn/"&gt;知识共享署名 2.5 中国大陆许可协议&lt;/a&gt;进行许可。 本博客采用知识共享署名 2.5 中国大陆许可协议进行许可。本博客版权归作者所有，欢迎转载，但未经作者同意&lt;b&gt;不得随机删除文章任何内容&lt;/b&gt;，且在文章页面&lt;b&gt;明显位置给出原文连接&lt;/b&gt;，否则保留追究法律责任的权利。如您有任何疑问或者授权方面的协商，请给&lt;a href="http://www.cnblogs.com/Gavin_Liu"&gt;我&lt;/a&gt;留言。&lt;/p&gt;&#xD;
&lt;strong&gt;&#xD;
&lt;p&gt;(&lt;a href="http://www.cnblogs.com/Gavin_Liu/"&gt;http://www.cnblogs.com/Gavin_Liu/&lt;/a&gt;)&lt;/p&gt;&#xD;
&lt;/strong&gt;&#xD;
&lt;br /&gt;&#xD;
&lt;p&gt;&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2011214.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/13/2011214.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html</id><title type="text">漫谈算法（番外篇） 符号标记以及基本数学公式</title><summary type="text">Keywords：Big O； Little O；Big Omega；Little Omega；Theta；Mathematical Formula</summary><published>2011-04-12T04:52:00Z</published><updated>2011-04-12T04:52:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html"/><content type="html">&lt;p&gt;Keywords：Big O； Little O；Big Omega；Little Omega；Theta；Mathematical Formula。&lt;/p&gt;&#xD;
&lt;p&gt;说到算法，大家最熟悉的恐怕就是这个大欧标记了，即O。什么基于比较的排序算法最好是O(nlgn)了什么的。（注，本系列文章里不区分log和lg，两个符号都是表示以二为底的对数）&lt;/p&gt;&#xD;
&lt;p&gt;更general一点，我们可以写成O(g(n))，这个东西是什么吗，其实他是一个集合，表示所以满足条件的一系列函数，这一系列函数有什么特点呢？那就是g(n)是他们的上界，更准确的说，是一个constant c， c*g(n)是他们的上界。如下图，（截自算法导论）&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041212213717.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;假设f(n)表示我们quick sort的时间，我们写作f(n) = O(nlgn)，这时候你可能要问了，刚才上面不是说O(nlgn)表示一个集合吗，这么集合里面显然都是函数，那怎么能写 函数 = 函数的集合呢？没错，其实这样写确实不好，但是我们现在已经约定俗成了，这里的等号就表示元素和集合关系的属于的符号。&lt;/p&gt;&#xD;
&lt;p&gt;现在来看一下Big O的formal的定义。&lt;/p&gt;&#xD;
&lt;p&gt;O(g(n)) = {f(n) : 存在一个正常数c，和一个 N0， 对于所有的N&amp;gt;N0， 0&amp;lt;= f(n) &amp;lt;= c*g(n)}&lt;/p&gt;&#xD;
&lt;p&gt;其实结合上面的图和这个定义，Big O的意思还是很明显的。他是f(n)的一个upper bound。&lt;/p&gt;&#xD;
&lt;p&gt;同理，我们可以定义 Big Omega 和 Theta。&lt;/p&gt;&#xD;
&lt;p&gt;Omega(g(n)) = {f(n): 存在一个正常数c，和一个 N0， 对于所有的N&amp;gt;N0， 0&amp;lt;= c*g(n) &amp;lt;=&amp;nbsp;f(n)&amp;nbsp;}&lt;/p&gt;&#xD;
&lt;p&gt;当然，这是我们f(n)的一个lower bound。&lt;/p&gt;&#xD;
&lt;p&gt;Theta(g(n)) = {f(n): 存在正常数c1和c2，和一个 N0， 对于所有的N&amp;gt;N0， c1*g(n) &amp;lt;= f(n) &amp;lt;= c2*g(n)}&lt;/p&gt;&#xD;
&lt;p&gt;下面是算法导论里面完整的图。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041212344265.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;下面给一下Litter O 和 Litter Omega的概念。&lt;/p&gt;&#xD;
&lt;p&gt;通过上面的定义我们可以发现，在Big O，Big Omega和Theta的定义中，我们都出现了&amp;ldquo;=&amp;rdquo;，当然，类比当我们数字的比较，我们有大于等于，小于等于，同样，我们也有大于，小于。&lt;/p&gt;&#xD;
&lt;p&gt;当然对于这个等号，我们可以说是一个tight的bound，比如2n^2 = O(n^2)，这个bound就是asymptotically tight的，但是对于2n = O(n^2)来说，这个就不是tight的。所以，这时我们的Little O就孕育而生了。用算法导论里面的原话就是： We use o-notation(Litter O) to denote an upper bound that is not asymptotically tight.&lt;/p&gt;&#xD;
&lt;p&gt;下面来看Little O的formal的定义。&lt;/p&gt;&#xD;
&lt;p&gt;o(g(n)) = {f(n): 对于&lt;strong&gt;任意&lt;/strong&gt;的正常数c，存在一个N0，对于所有的N&amp;gt;N0, 0&amp;lt;= f(n) &amp;lt; c*g(n)}&lt;/p&gt;&#xD;
&lt;p&gt;同样，我们可以定义Little Omega。&lt;/p&gt;&#xD;
&lt;p&gt;w(g(n)) = {f(n): 对于&lt;strong&gt;任意&lt;/strong&gt;的正常数c，存在一个N0，对于所有的N&amp;gt;N0, 0&amp;lt;= c*g(n) &amp;lt;=f(n)}&lt;/p&gt;&#xD;
&lt;p&gt;============================================华丽的分割线==================================&lt;/p&gt;&#xD;
&lt;p&gt;对于这些标记，其实我们还可以从growth rate和极限的方面来考虑。&lt;/p&gt;&#xD;
&lt;p&gt;比如对于little O，&amp;nbsp;it means that&amp;nbsp;&lt;span &gt;&lt;i&gt;g&lt;/i&gt;(&lt;i&gt;x&lt;/i&gt;)&lt;/span&gt;&amp;nbsp;grows much faster than&amp;nbsp;&lt;span &gt;&lt;i&gt;f&lt;/i&gt;(&lt;i&gt;x&lt;/i&gt;)&lt;/span&gt;, or similarly, the growth of&lt;span &gt;&lt;i&gt;f&lt;/i&gt;(&lt;i&gt;x&lt;/i&gt;)&lt;/span&gt;&amp;nbsp;is nothing compared to that of&amp;nbsp;&lt;span &gt;&lt;i&gt;g&lt;/i&gt;(&lt;i&gt;x&lt;/i&gt;)&lt;/span&gt;.（偷懒一下，直接从wikipedia上抄下来）&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041212485733.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;其余的大家自己也应该都可以琢磨出来。(*^__^*) 嘻嘻&amp;hellip;&amp;hellip;&lt;/p&gt;&#xD;
&lt;p&gt;最后给几个常用且简单的数学公式，权当remind了。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/52744/2011041212511942.jpg" /&gt;&lt;/p&gt;&#xD;
&lt;p&gt;========================================================================================&lt;/p&gt;&#xD;
&lt;p&gt;本作品采用&lt;a rel="license" href="http://creativecommons.org/licenses/by/2.5/cn/"&gt;知识共享署名 2.5 中国大陆许可协议&lt;/a&gt;进行许可。 本博客采用知识共享署名 2.5 中国大陆许可协议进行许可。本博客版权归作者所有，欢迎转载，但未经作者同意&lt;b&gt;不得随机删除文章任何内容&lt;/b&gt;，且在文章页面&lt;b&gt;明显位置给出原文连接&lt;/b&gt;，否则保留追究法律责任的权利。如您有任何疑问或者授权方面的协商，请给&lt;a href="http://www.cnblogs.com/Gavin_Liu"&gt;我&lt;/a&gt;留言。&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2011105.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011108.html</id><title type="text">漫谈算法（一）如何证明贪心算法是最优 using exchange argument</title><summary type="text">Keywords: Greedy Algorithm; Exchange Argument</summary><published>2011-04-10T01:11:00Z</published><updated>2011-04-10T01:11:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011108.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011108.html"/><content type="html">&lt;p&gt;Keywords: Greedy Algorithm; Exchange Argument&lt;/p&gt;&#xD;
&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html" target="_blank"&gt;[为什么写这类文章]&lt;/a&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html"&gt;漫谈算法（零）序&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;[&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html"&gt;这系列文章里会用到的一下符号和公式&lt;/a&gt;] &amp;nbsp;&amp;nbsp;&lt;a target="_blank" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/12/2011105.html"&gt;漫谈算法（番外篇） 符号标记以及基本数学公式&lt;/a&gt;&lt;/p&gt;&#xD;
&lt;p&gt;这里主要是介绍一种证明贪心算法是最优的一种方法：Exchange Argument （不知道应该怎么翻译到中文，交换参数？感觉听起来挺别扭的，不像是一个方法的名字~o(╯□╰)o）&lt;/p&gt;&#xD;
&lt;p&gt;Exchange Argument的主要的思想也就是 先假设 存在一个最优的算法和我们的贪心算法最接近，然后通过交换两个算法里的一个步骤(或元素)，得到一个新的最优的算法，同时这个算法比前一个最优算法更接近于我们的贪心算法，从而得到矛盾，原命题成立。&lt;/p&gt;&#xD;
&lt;p&gt;下面来看一个更为formal的解释：&lt;/p&gt;&#xD;
&lt;p&gt;步骤：&lt;/p&gt;&#xD;
&lt;p&gt;Step0: 给出贪心算法A的描述&lt;/p&gt;&#xD;
&lt;p&gt;Step1: 假设O是和A最相似(假设O和A的前k个步骤都相同，第k+1个开始不同，通常这个临界的元素最重要)的最优算法&lt;/p&gt;&#xD;
&lt;p&gt;Step2: [Key] 修改算法O(用Exchange Argument，交换A和O中的一个元素)，得到新的算法O&amp;rsquo;&lt;/p&gt;&#xD;
&lt;p&gt;Step3: 证明O&amp;rsquo; 是feasible的，也就是O&amp;rsquo;是对的&lt;/p&gt;&#xD;
&lt;p&gt;Step4: 证明O&amp;rsquo;至少和O一样，即O&amp;rsquo;也是最优的&lt;/p&gt;&#xD;
&lt;p&gt;Step5: 得到矛盾，因为O&amp;rsquo; 比O 更和A 相似。&lt;/p&gt;&#xD;
&lt;p&gt;证毕。&lt;/p&gt;&#xD;
&lt;p&gt;当然上面的步骤还有一个变种，如下：&lt;/p&gt;&#xD;
&lt;p&gt;Step0:&amp;nbsp;给出贪心算法A的描述&lt;/p&gt;&#xD;
&lt;p&gt;Step1: 假设O是一个最优算法（随便选，arbitrary）&lt;/p&gt;&#xD;
&lt;p&gt;Step2: 找出O和A中的一个不同。（当然这里面的不同可以是一个元素在O不再A，或者是一个pair的顺序在A的和在O的不一样。这个要根据具体题目）&lt;/p&gt;&#xD;
&lt;p&gt;Step3：Exchange这个不同的东西，然后argue现在得到的算法O' 不必O差。&lt;/p&gt;&#xD;
&lt;p&gt;Step4: Argue 这样的不同一共有Polynomial个，然后我exchange Polynomial次就可以消除所有的不同，同时保证了算法的质量不比O差。这也就是说A 是as good as 一个O的。因为O是arbitrary选的，所以A是optimal的。&lt;/p&gt;&#xD;
&lt;p&gt;证毕&lt;/p&gt;&#xD;
&lt;p&gt;下面给几个例子：&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 Maximum Cardinality Disjoint Interval Problem&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;问题描述：给一些时间片段集合T={（a1，b1）（a2，b2），。。。，（an，bn）}，找出一个元素个数最多的子集S，子集中的每个元素的时间片段没有交叉。&lt;/p&gt;&#xD;
&lt;p&gt;Greedy Algorithm: 每次都选所有interval 中bi最小的那个，把（ai，bi）加入S，然后把（ai，bi）在T中删除，同时把T中所有和（ai，bi）有交叉的interval删除，然后再在T中找最小的bj，循环上面的操作，直到没有可以在添加的。&lt;/p&gt;&#xD;
&lt;p&gt;证明上面说的Greedy Algorithm是最优的。&lt;/p&gt;&#xD;
&lt;p&gt;下面就用第一个证明的步骤来证。&lt;/p&gt;&#xD;
&lt;p&gt;我们的Greedy Algorithm记为A，假设A不是最优的，那么就一定存在一个O，O是和A最相近的一个最优的算法，最相近是指和O和A的前K-1个选择都相同，第K个是不同的。&lt;/p&gt;&#xD;
&lt;p&gt;假设对于A，A第k个选择的是（ai，bi）；而O第K个选择的是（aj，bj）。从A的定义我们可以直到，bi&amp;lt;=bj。&lt;/p&gt;&#xD;
&lt;p&gt;现在我们构造一个O'，O' = O-（aj，bj）+（ai，bi）。&lt;/p&gt;&#xD;
&lt;p&gt;1）很显然，O'是这个问题的一个解，也就是说O'中的intervals没有重叠的。&lt;/p&gt;&#xD;
&lt;p&gt;在O'中，（ai，bi）前的intervals和A中的一样，所以前一部分没有重叠。在（ai，bi）后的intervals和O中的一样，所以也没有重叠，同时bi&amp;lt;=bj，所以（ai，bi）也不会和它相邻的重叠，所以O'中的所有intervals都没有重叠。&lt;/p&gt;&#xD;
&lt;p&gt;2）O'是一个最优解，因为他的intervals的个数和O一样。&lt;/p&gt;&#xD;
&lt;p&gt;综上，我们找到了一个最优解O'，它和A具有的共同的intervals有K个，这和我们前提假设最多有k-1个相矛盾，所以，A是最优的。证毕。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 Scheduling with Deadline&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;问题描述：有一系列的tasks{a1，a2，。。。，an}，每个task需要在cpu上跑{p1，p2，。。。，pn}个units的时间，cpu是非抢占式的，也就是task一旦获得cpu，就运行直到他完成，{c1，c2，。。。，cn}是每个task的完成时间，我们现在要minimize的就是 （c1+c2+。。。+cn）/n。所有的task一起release。&lt;/p&gt;&#xD;
&lt;p&gt;Greedy Algorithm：每次选运行时间最小的那个task运行。&lt;/p&gt;&#xD;
&lt;p&gt;证明上面说的Greedy Algorithm是最优的。&lt;/p&gt;&#xD;
&lt;p&gt;同样还是用第一个证明的步骤来证。&lt;/p&gt;&#xD;
&lt;p&gt;假设A不是最优的，那么一定存在一个最优的O，和A最接近，他们选择的前k-1个task是相同的。&lt;/p&gt;&#xD;
&lt;p&gt;如下：&lt;/p&gt;&#xD;
&lt;p&gt;No.　　1　　2　　3　　。。。　　k　　。。。。。。。。。&lt;/p&gt;&#xD;
&lt;p&gt;A &amp;nbsp;&amp;nbsp;　　。。。。。。。。。。。　　ai　　。。aj 。。。。。&lt;/p&gt;&#xD;
&lt;p&gt;O &amp;nbsp;　　 。。。。。。。。。。。　　aj　　。。。。ai 。。。&lt;/p&gt;&#xD;
&lt;p&gt;根据A的定义，我们知道，pi &amp;lt;= pj的。&lt;/p&gt;&#xD;
&lt;p&gt;现在我们就来构造O'，当然，O'就是把O中的ai和aj两个元素交换一下，即&lt;/p&gt;&#xD;
&lt;p&gt;No.　　1　　2　　3　　。。。　　k　　。。。。。t。。。&lt;/p&gt;&#xD;
&lt;p&gt;A &amp;nbsp;&amp;nbsp;　　。。。。。。。。。。。　　ai　　。。aj 。。。。。&lt;/p&gt;&#xD;
&lt;p&gt;O &amp;nbsp;　　 。。。。。。。。。。。　　aj　　。。。。ai 。。。&lt;/p&gt;&#xD;
&lt;p&gt;O' 　　 。。。。。。。。。。。　　ai　　。。。。aj 。。。&lt;/p&gt;&#xD;
&lt;p&gt;对于整个的完成时间，我们可以给出计算公式的，假设b1，b2，。。。，bn是一个调度序列（bi是task的index，也就是{bi}是所有task的index的一个permutation），即task执行顺序是a_{bi},a_{b2}, ..., a_{bn}。举个例子如果只有5个task，a1,a2,a3,a4,a5。{bi} = {2,1, 3, 5, 4},那也就是task的执行序列为 a2,a1,a3,a5,a4。(博客里面不能打数学公式就是不爽啊~)&lt;/p&gt;&#xD;
&lt;p&gt;还是用上面的5个例子算了，对于a2,a1,a3,a5,a4，我们知道总时间其实是5个a2的时间，即p2，4个a1的时间，即p1，依次类推，所以总时间是5*p2+4*p1+3*p3+2*p5+p4。&lt;/p&gt;&#xD;
&lt;p&gt;好，有了上面的计算总时间的概念，下面就来分别计算一下O和O'的总时间，其实我们需要比较它们谁大谁小，通常只要比较一下他们的差就行了，即Time(O)-Time(O')。同时很显然，他们的差只是由aj和ai引起的，其他的并没有改变。我们知道在O中，aj是第k个选的，ai是第t个选的，而在O'中，ai是第k个选的，aj是第t个选的，所以 Time(O)-Time(O') = (n-k+1)*pj + (n-t+1)*pi - (n-k+1)*pi - (n-t+1)*pj = k*(pj-pi) - t*(pj-pi) = (pi-pj)*(k-t)&amp;gt;=0,即我们得到Time(O) &amp;gt;= Time(O'),这也就是说我们找到了一个O'，他是最优的，同时O'和A有K个common element，所以得出矛盾。证毕。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;例 Kruskal Algorithm for Minimum Spanning Tree（最小生成树）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;问题描述：对于边集合E，先按每个边的cost排序，从小到大，然后按如下方法构造一个MST，每次选cost最小的那个边，添加进我们的MST，如果一个边使我们现有的MST出现环路，则把这条边舍弃，然后重复上面的操作。（感觉现在表达越来越搓了，没看明白怎么回事的童鞋看这里吧&amp;nbsp;&lt;a href="http://en.wikipedia.org/wiki/Kruskal's_algorithm"&gt;http://en.wikipedia.org/wiki/Kruskal's_algorithm&lt;/a&gt;）&lt;/p&gt;&#xD;
&lt;p&gt;下面我们就用第二种证明方法来证明Kruskal Algorithm算法是最优的。&lt;/p&gt;&#xD;
&lt;p&gt;假设我们的graph是G(V,E)，有一个optimal的算法O，它生成的MST是T1，Kruskal生成的树是T2，这样，因为T2！=T1（如果相等我们的Kruskal就最优了，就不用证了），所以至少有一条边e，e在T1中，同时e不在T2中。这个与众不同的e就是我们的切入点。可以想象，如果我们把e在T1中去掉，T1就变成了两部分，即Ta和Tb，我们知道 {Ta中的点}V{Tb中的点} = V（就是Ta中的点并上Tb中的点就是我们G中的点集V），所以在T2中，一定有一条边f（f！=e），f的一个点在{Ta中的点}，f的另一个点在{Tb中的点}。根据我们的Greedy算法Kruskal，我们没有选e，是因为e在我们的图中造成了回路。在进一步想，有回路是因为我们先选了f，这就说明cost(f) &amp;lt;= cost(e)。 （恩，这就是我们的重点啊，笑一个O(&amp;cap;_&amp;cap;)O~）&lt;/p&gt;&#xD;
&lt;p&gt;下面的也就很显然了，我们考虑构造一棵新的树，T3，对于T3 = E(T1) - e + f，也就是T3中的边是T1中的所有边除了e，同时加上边f。&lt;/p&gt;&#xD;
&lt;p&gt;1）很显然，我们知道T3是一棵spanning tree，f连通了由去掉e而分隔的两部分。&lt;/p&gt;&#xD;
&lt;p&gt;2）很显然，T3的cost是&amp;lt;= T1的cost的。 因为cost(f) &amp;lt;= cost(e)，且其他不变。即T3是MST。&lt;/p&gt;&#xD;
&lt;p&gt;所以同个上面的构造，我们就构造出了一棵树，这个树是MST，同时它比T1更接近于T2。（多了一条common的边）&lt;/p&gt;&#xD;
&lt;p&gt;我们知道T1和T2对多有n条边是不同的，通过上面的步骤，我们可以经过n次变换，得到T2，同时cost &amp;lt;=T1，所以，T2是MST。证毕。&lt;/p&gt;&#xD;
&lt;p&gt;========================================================================================&lt;/p&gt;&#xD;
&lt;p&gt;本作品采用&lt;a rel="license" href="http://creativecommons.org/licenses/by/2.5/cn/"&gt;知识共享署名 2.5 中国大陆许可协议&lt;/a&gt;进行许可。 本博客采用知识共享署名 2.5 中国大陆许可协议进行许可。本博客版权归作者所有，欢迎转载，但未经作者同意&lt;b&gt;不得随机删除文章任何内容&lt;/b&gt;，且在文章页面&lt;b&gt;明显位置给出原文连接&lt;/b&gt;，否则保留追究法律责任的权利。如您有任何疑问或者授权方面的协商，请给&lt;a href="http://www.cnblogs.com/Gavin_Liu"&gt;我&lt;/a&gt;留言。&lt;/p&gt;&#xD;
&lt;strong&gt;&#xD;
&lt;p&gt;(&lt;a href="http://www.cnblogs.com/Gavin_Liu/"&gt;http://www.cnblogs.com/Gavin_Liu/&lt;/a&gt;)&lt;/p&gt;&#xD;
&lt;/strong&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2011108.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011108.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html</id><title type="text">漫谈算法（零）序</title><summary type="text">这学期选了一门算法课（CS2510），搞的人死去活来，因为作业实在多的让人XX，每周3次作业从来没断过，而且都很难。。。。同时我们老师据说是北美算法界得重要人物，上课思路奇快，不用PPT，不用稿子，一支笔，在黑板上刷刷刷，加之英语，搞得我时常跟不上。。。怨念。。。不过还是学到了很多东西，基本cover了算法导论里面的各个内容。受益匪浅。准备写一些对基本算法知识的介绍。同时也权当是自己复习了。想给这个系列起个名字，思考良久，认为“漫谈”这个词应该能反映一些我要写的东西的本质。漫，有一点点漫无边际的意思，因为可能要cover算法里面很多sub areas。漫，同时也有一些随意的意思，我是想到哪里写</summary><published>2011-04-10T01:03:00Z</published><updated>2011-04-10T01:03:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html"/><content type="html">&lt;p&gt;这学期选了一门算法课（&lt;a target="_blank" href="http://www.cs.pitt.edu/~kirk/cs2150/"&gt;CS2510&lt;/a&gt;），搞的人死去活来，因为作业实在多的让人XX，每周3次作业从来没断过，而且都很难。。。。同时&lt;a target="_blank" href="http://www.cs.pitt.edu/~kirk/"&gt;我们老师&lt;/a&gt;据说是北美算法界得重要人物，上课思路奇快，不用PPT，不用稿子，一支笔，在黑板上刷刷刷，加之英语，搞得我时常跟不上。。。怨念。。。&lt;/p&gt;&#xD;
&lt;p&gt;不过还是学到了很多东西，基本cover了算法导论里面的各个内容。受益匪浅。准备写一些对基本算法知识的介绍。同时也权当是自己复习了。&lt;/p&gt;&#xD;
&lt;p&gt;想给这个系列起个名字，思考良久，认为&amp;ldquo;漫谈&amp;rdquo;这个词应该能反映一些我要写的东西的本质。&lt;/p&gt;&#xD;
&lt;p&gt;漫，有一点点漫无边际的意思，因为可能要cover算法里面很多sub areas。&lt;/p&gt;&#xD;
&lt;p&gt;漫，同时也有一些随意的意思，我是想到哪里写到哪里，语言可能比较随和，与科学网里的那些文章风格相异。&lt;/p&gt;&#xD;
&lt;p&gt;同时最重要说明的是，这里讲的算法，其实更是重在算法的分析上，mathematical analysis，不是为准备面试而写。重在介绍algorithm作为computer science这个学科里面的一个重要研究方向的概要。涉及的方面可能比较宽泛。&lt;/p&gt;&#xD;
&lt;p&gt;先简单给一个目录吧，争取能坚持写完。&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;一。贪心算法（Greedy Algorithm）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;二。动态规划（Dynamic Programming）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;三。NP-Complete问题&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;四。分治问题（Divide and Conquer）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;五。问题复杂度分析（Problem Complexity and Adversarial Lower Bound）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;六。平均情况分析和随机算法（Average case analysis and randomized algorithm）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;七。图 （Graph Algorithm）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;八。线性规划（Linear Programming）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;九。近似算法（Approximation Algorithm）&lt;/strong&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;strong&gt;十。Online Algorithm&lt;/strong&gt;（这个实在不知道怎么翻译了，囧）&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2011104.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/10/2011104.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2011/04/01/2001713.html</id><title type="text">Java SWT 64位 下载 Eclipse</title><summary type="text">Java SWT 64位 下载 Eclipse官网上的方法试了半天没搞成，用ant buildorg.eclipse.swt.gtk.linux.x86_64的时候总是报一个错，作业赶的又紧，搜了半天，终于找到一个64位可以用的swt.jar了~~~o(&amp;gt;_&amp;lt;)o ~~[点击下载]</summary><published>2011-03-31T23:11:00Z</published><updated>2011-03-31T23:11:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/01/2001713.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/01/2001713.html"/><content type="html">&lt;p&gt;Java SWT 64位 下载 Eclipse&lt;/p&gt;&#xD;
&lt;p&gt;官网上的方法试了半天没搞成，用ant build&amp;nbsp;org.eclipse.swt.gtk.linux.x86_64的时候总是报一个错，作业赶的又紧，搜了半天，终于找到一个64位可以用的swt.jar了~~~o(&amp;gt;_&amp;lt;)o ~~&lt;/p&gt;&#xD;
&lt;p&gt;[&lt;a href="http://www.cs.pitt.edu/~ztliu/download/swt.jar"&gt;点击下载&lt;/a&gt;]&amp;nbsp;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/2001713.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2011/04/01/2001713.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2010/11/21/1883069.html</id><title type="text">Knowledge Harvesting(知识收获)是什么</title><summary type="text">How can we truly capitalize on the knowledge of our organization’s experts? How do we capture what is in their heads and then share it with others in an accessible and understandable format? How do we make tacit knowledge explicit? Knowledge harvesting is not a catch-all solution, but it is one way to capture, document and subsequently use the knowledge of experts and other top performers. As Eisenhart(2001) explains, “the ultimate goal of knowledge harvesting is to capture an individual’s decis</summary><published>2010-11-21T05:08:00Z</published><updated>2010-11-21T05:08:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2010/11/21/1883069.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2010/11/21/1883069.html"/><content type="html">&lt;p&gt;&lt;span style="font-size: 12pt;"&gt;How can we truly capitalize on the knowledge of our organization&amp;rsquo;s experts? How do we capture what is in their heads and then share it with others in an accessible and understandable format? How do we make tacit knowledge explicit? Knowledge harvesting is not a catch-all solution, but it is one way to capture, document and subsequently use the knowledge of experts and other top performers. As Eisenhart(2001) explains, &amp;ldquo;the ultimate goal of knowledge harvesting is to capture an individual&amp;rsquo;s decision-making process with enough clarity that someone else guided by it could repeat the steps of the process and achieve the same result.&amp;rdquo;&#xD;
&lt;/span&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;span style="font-size: 12pt;"&gt;What does knowledge harvesting involve?&lt;/span&gt;&lt;/p&gt;&#xD;
&lt;p&gt;&lt;span style="font-size: 12pt;"&gt;Most approaches to knowledge harvesting follow a set of careful steps. Here, we adapt an eight-step process as presented by Knowledge Harvesting Inc.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;1) &lt;strong&gt;Focus&lt;/strong&gt;. What specific knowledge and expertise are we looking for? The answer to this question will affect the overall strategy for capturing that information.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;2) &lt;strong&gt;Find&lt;/strong&gt;. Locate the experts whose knowledge we want to harvest. We can go through a staff directory, look at key documents and find out who authored them, or simply ask around.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;3) &lt;strong&gt;Elicit&lt;/strong&gt;. Harvesters, or interviewers, can get experts to talk about their knowledge &amp;ndash;even when they are not aware that they possess it. It is important for skilled harvesters to get the dialogue started.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;4) &lt;strong&gt;Organize&lt;/strong&gt;. Once the knowledge has been gathered, it must be arranged in a coherent and systematic form that is easy to access.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;5) &lt;strong&gt;Package&lt;/strong&gt;. As discussed in several other chapters of this Toolkit, we must think about our audience and its needs. Which format will best serve our audience with the knowledge we&amp;rsquo;ve elicited?&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;6) &lt;strong&gt;Share&lt;/strong&gt;. Connected to 5) is the question of: what is the ultimate purpose of sharing this knowledge? Why and for whom have we packaged what we know? Again, the exact means for doing all of this will depend on a careful appreciation of the audience. Generally, we start by making our knowledge available in an on-line repository.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;7) &lt;strong&gt;Apply&lt;/strong&gt;. This will be done by members of an organization in their every-day work. It is important to keep track of whether, and how, that knowledge is being applied and to record any feedback.&lt;/span&gt;&lt;br /&gt;&lt;span style="font-size: 12pt;"&gt;8) &lt;strong&gt;Evaluate &lt;/strong&gt;and adapt. Based on the feedback of users, the effectiveness of our efforts must be evaluated and adapted to the changing needs of an organization.&lt;/span&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/1883069.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2010/11/21/1883069.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/Gavin_Liu/archive/2010/10/17/1853538.html</id><title type="text">见面了，说话了，握手了，合影了，完整了...</title><summary type="text">见面了，说话了，握手了，合影了，完整了...大学四年之后，终于和校长零距离接触了...</summary><published>2010-10-17T04:12:00Z</published><updated>2010-10-17T04:12:00Z</updated><author><name>Gavin.Liu</name><uri>http://www.cnblogs.com/Gavin_Liu/</uri></author><link rel="alternate" href="http://www.cnblogs.com/Gavin_Liu/archive/2010/10/17/1853538.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/Gavin_Liu/archive/2010/10/17/1853538.html"/><content type="html">&lt;p&gt;见面了，说话了，握手了，合影了，完整了...&lt;/p&gt;&#xD;
&lt;p&gt;大学四年之后，终于和校长零距离接触了...&lt;/p&gt;&#xD;
&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2010/52744/2010101712105470.jpg" alt="" width="1361" height="907" /&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/Gavin_Liu/aggbug/1853538.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/Gavin_Liu/archive/2010/10/17/1853538.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
