﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-游戏人生-随笔分类-A算法导论</title><link>http://www.cppblog.com/Fox/category/6990.html</link><description>游戏人生 != ( 人生 == 游戏 ) &lt;br /&gt;
站点迁移至：&lt;a href="http://www.yulefox.com"&gt;http://www.yulefox.com&lt;/a&gt;。请订阅本博的朋友将RSS修改为&lt;a href="http://feeds.feedburner.com/yulefox"&gt;http://feeds.feedburner.com/yulefox&lt;/a&gt;</description><language>zh-cn</language><lastBuildDate>Mon, 07 Dec 2009 01:13:14 GMT</lastBuildDate><pubDate>Mon, 07 Dec 2009 01:13:14 GMT</pubDate><ttl>60</ttl><item><title>UUID算法分析</title><link>http://www.cppblog.com/Fox/archive/2009/12/06/uuid_algorithm_analysis.html</link><dc:creator>Fox</dc:creator><author>Fox</author><pubDate>Sun, 06 Dec 2009 07:07:00 GMT</pubDate><guid>http://www.cppblog.com/Fox/archive/2009/12/06/uuid_algorithm_analysis.html</guid><wfw:comment>http://www.cppblog.com/Fox/comments/102647.html</wfw:comment><comments>http://www.cppblog.com/Fox/archive/2009/12/06/uuid_algorithm_analysis.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Fox/comments/commentRss/102647.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Fox/services/trackbacks/102647.html</trackback:ping><description><![CDATA[本文同步自<a href="http://www.yulefox.com/20091206/uuid_algorithm_analysis.html/" target="_blank">游戏人生</a>
<p>Writen by Fox(yulefox.at.gmail.com)</p>
<p>在具体讨论之前，本文先厘清UUID（Universally Unique IDentifier）与GUID（Globally Unique IDentifier）的关系。</p>
<p>在分布式、网络、单机环境下，为了能够使用具有某种形式的ID唯一标识系统中的任一元素，这样的ID可以不依赖中心认证自动生成，于是UUID就诞生了。</p>
<p>UUID标准的历史沿革和具体实现在<a title="RFC4122" href="http://www.ietf.org/rfc/rfc4122.txt" target="_blank">RFC 4122</a>、<a title="ITU-T rec. x.667" href="http://www.itu.int/ITU-T/studygroups/com17/oid.html" target="_blank">ITU-T Rec. X.667</a>和<a title="ISO/IEC 9834-8:2008" href="http://www.iso.org/iso/iso_catalogue/catalogue_tc/catalogue_detail.htm?csnumber=53416" target="_blank">ISO/IEC 9834-8:2008</a>中均有详细描述。ITU和ISO采用的标准和RFC 4122都是在UUID的早期版本基础上完成，各版本之间具有一致性和兼容性。</p>
<p>因为不能保证UUID的唯一性，ITU和ISO针对UUID的使用都有<a id="eac3" title="免责声明" href="http://www.itu.int/ITU-T/asn1/uuid.html" target="_blank">免责声明</a>。</p>
<p>GUID一般是指Microsoft对于UUID标准的实现，UUID的实现则多见于其他系统（*NIX、MAC OS等）中。在了解了这一区别后，本文将统一使用UUID来指代对应的原理、算法及实现。</p>
<p>文中关于UUID的讨论全部基于RFC 4122和ITU-T Rec.
X.667以及OSF、IETF、ITU－T、ISO、FIPS的各种标准文档。而UUID的细节（如结构、表示、算法、实现等）均以ITU-T
Rec. X667为唯一蓝本，文中&#8220;本标准&#8221;即指代该蓝本。</p>
<p><strong> o 介绍</strong> </p>
<p>UUID是长度为16-byte（128-bit）的ID，一般以形如f81d4fae-7dec-11d0-a765-00a0c91e6bf6的字符串作为URN（Uniform Resource Name，统一资源名称）。</p>
<p><strong> o 动机</strong> </p>
<p>无须中心认证，自动生成，支持一台机器每秒生成10M次（100纳秒级，其隐含原因是指能够区分的最小时间单位为100ns，将时间作为因子时，连续生成两个UUID的时间至少要间隔100ns）。方便存取、分配、排序、查找。</p>
<p><strong> o 结构</strong></p>
<blockquote>
<p><br>
<span style="font-family: courier new;">&nbsp;&nbsp;&nbsp;76543210765432107654321076543210</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&nbsp;&nbsp;&nbsp;+ &#8211; - &#8211; = &#8211; - &#8211; = &#8211; - &#8211; = &#8211; - &#8211; +</span><br style="font-family: courier new;"><span style="font-family: courier new;">
15 |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; TimeLow&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 12</span><br style="font-family: courier new;"><span style="font-family: courier new;">
11 |&nbsp;&nbsp;&nbsp; TimeMid&nbsp;&nbsp;&nbsp; |&nbsp;&nbsp; Version..&nbsp;&nbsp; |&nbsp; 8</span><br style="font-family: courier new;"><span style="font-family: courier new;">
7&nbsp; |Vari.. |Clock..|&nbsp;&nbsp;&nbsp;&nbsp; Node&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; 4</span><br style="font-family: courier new;"><span style="font-family: courier new;">
3&nbsp; |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Node&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |&nbsp; 0</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&nbsp;&nbsp;&nbsp;+ &#8211; - &#8211; = &#8211; - &#8211; = &#8211; - &#8211; = &#8211; - &#8211; +</span><br style="font-family: courier new;"><span style="font-family: courier new;">
15 &#8211; 12: TimeLow 时间值的低位</span><br style="font-family: courier new;"><span style="font-family: courier new;">
11 &#8211; 10: TimeMid 时间值的中位</span><br style="font-family: courier new;"><span style="font-family: courier new;">
09 &#8211; 08: VersionAndTimeHigh 4位版本号和时间值的高位</span><br style="font-family: courier new;"><span style="font-family: courier new;">
07: VariantAndClockSeqHigh 2位变体（ITU-T）和时钟序列高位</span><br style="font-family: courier new;"><span style="font-family: courier new;">
06: ClockSeqLow 时钟序列低位</span><br style="font-family: courier new;"><span style="font-family: courier new;">
05 &#8211; 00: Node 结点</span><br style="font-family: courier new;"><span style="font-family: courier new;">
hexOctet = hexDigit hexDigit</span><br style="font-family: courier new;"><span style="font-family: courier new;">
hexDigit =</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&#8220;0&#8243; / &#8220;1&#8243; / &#8220;2&#8243; / &#8220;3&#8243; / &#8220;4&#8243; / &#8220;5&#8243; / &#8220;6&#8243; / &#8220;7&#8243; / &#8220;8&#8243; / &#8220;9&#8243; /</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&#8220;a&#8221; / &#8220;b&#8221; / &#8220;c&#8221; / &#8220;d&#8221; / &#8220;e&#8221; / &#8220;f&#8221; /</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&#8220;A&#8221; / &#8220;B&#8221; / &#8220;C&#8221; / &#8220;D&#8221; / &#8220;E&#8221; / &#8220;F&#8221;</span><br style="font-family: courier new;"><span style="font-family: courier new;">
UUID =</span><br style="font-family: courier new;"><span style="font-family: courier new;">
TimeLow</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&#8220;-&#8221; TimeMid</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&#8220;-&#8221; VersionAndTimeHigh</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&#8220;-&#8221; VariantAndClockSeqHigh ClockSeqLow</span><br style="font-family: courier new;"><span style="font-family: courier new;">
&#8220;-&#8221; Node</span></p>
</blockquote>
<p>UUID由上述6个域构成，每个域编码为若干字节，并以16进制数表示这128位的UUID，相邻域以减号&#8220;-&#8221;分隔
（VariantAndClockSeqHigh和ClockSeqLow对应的两个字节例外，如上所示）。该结构中包含版本（Version）、变体
（Variant）、时间（Time）、时钟序列（Clock Sequence）、节点（Note）信息（以无符号整型值表示）。</p>
<p><strong> o 合法性</strong> </p>
<p>除判断variant位设置是否正确、基于时间生成的UUID时间值是否为未经分配的将来时间外，实际应用中没有其他机制可以判定UUID是否合法。</p>
<p><strong> o 变体</strong> </p>
<p>Variant位是UUID第7字节（VariantAndClockSeqHigh）的最高3位，</p>
<blockquote style="font-family: courier new;">
<p>7 6 5&nbsp; Description<br>
0 &#8211; &#8211;&nbsp; NCS向后兼容<br>
1 0 &#8211;&nbsp; 本标准<br>
1 1 0&nbsp; Microsoft向后兼容<br>
1 1 1&nbsp; ITU-T Rec. X.667保留</p>
</blockquote>
<p><strong> o 版本</strong> </p>
<p>UUID的生成有时间、名称、随机数三种策略，以第9字节（VersionAndTimeHigh）的最高4位表示。</p>
<p>目前UUID定义有5个版本：</p>
<blockquote style="font-family: courier new;">
<p>7 6 5 4&nbsp; Ver&nbsp; Description<br>
0 0 0 1&nbsp; 1&nbsp;&nbsp;&nbsp; 基于时间的版本（本标准）<br>
0 0 0 0&nbsp; 2&nbsp;&nbsp;&nbsp; 使用嵌入式POSIX（DCE安全版本）<br>
0 0 1 1&nbsp; 3&nbsp;&nbsp;&nbsp; 使用MD5哈希的基于名称的版本（本标准）<br>
0 1 0 0&nbsp; 4 &nbsp;&nbsp; 基于随机数的版本（本标准）<br>
0 1 0 1&nbsp; 5 &nbsp;&nbsp; 使用SHA-1的基于名称的版本（本标准）</p>
</blockquote>
<p><strong> o 时间</strong> </p>
<p>时间是一个60位的整型值（除4位版本号外的前8字节），对应UTC（格林尼治时间1582年10月15日午夜始）的100ns时间间隔计数。</p>
<p>对于ver 4和5，该值分别对应一个随机数和一个全局唯一的名称。</p>
<p><strong> o 时钟序列</strong> </p>
<p>对基于时间的UUID版本，时间序列用于避免因时间向后设置或节点值改变可能造成的UUID重复，对基于名称或随机数的版本同样有用：目的都是为了防止UUID重复。</p>
<p>如果前一时钟序列已知，通过自增实现时钟序列值的改变；否则，通过密码学（伪）随机数设置新的时钟序列值。</p>
<p><strong> o 节点</strong> </p>
<p>对基于时间的UUID版本，节点由48位的单播MAC地址构成。对于没有MAC地址的系统，节点值为一个密码学（伪）随机数（为防止与MAC地址发生碰撞，需设置多播位）。</p>
<hr><strong> o 基于时间的UUID生成算法</strong>
<p>o 确定UTC时间（60位 Time）和时间序列值（14位 ClockSequence）；</p>
<p>o 设置TimeLow（对应Time的31-0位）；</p>
<p>o 设置TimeMid（对应Time的47-32位）；</p>
<p>o 设置VersionAndTimeHigh（4位版本号及Time的59-48位）；</p>
<p>o 设置VariantAndClockSeqHigh（变体位及对应ClockSequence的13-8位）；</p>
<p>o 设置ClockSeqLow（对应ClockSequence的7-0位）；</p>
<p>o 设置Node（对应48位MAC地址）。</p>
<p><strong> o 基于名称的UUID生成算法</strong> </p>
<p>o 针对相应的命名空间（如DNS、URL、OID等）分配一个UUID作为所有UUID的命名空间标识；</p>
<p>o 将名称转换为字节数列；</p>
<p>o 使用MD5或SHA-1算法对与名称关联的命名空间标识进行计算，产生16字节哈希结果；</p>
<p>o 设置TimeLow（对应哈希值的3-0字节）；</p>
<p>o 设置TimeMid（对应哈希值的5-4字节）；</p>
<p>o 设置VersionAndTimeHigh（对应哈希值的7-6字节），以相应版本号重写对应位（第9字节的高4位）；</p>
<p>o 设置VariantAndClockSeqHigh（对应哈希值的第8字节），重写变体对应位（第7字节的高2位，本标准对应值为10）；</p>
<p>o 设置ClockSeqLow（对应哈希值的第9字节）；</p>
<p>o 设置Node（对应哈希值的15-10字节）。</p>
<p>由
于MD5碰撞问题，MD5只用于向后兼容的UUID生成，不再被推荐使用。由于SHA-1哈希结果为160位（20字节），本算法中，需要将FIPS
PUB 180-2中的SHA-1算法的哈希值字节顺序反转（字节内顺序不变），UUID使用其15-0字节，19-16字节被丢弃。</p>
<p><strong> o 基于随机数的UUID生成算法</strong> </p>
<p>o 设置VariantAndClockSeqHigh的变体位值为10；</p>
<p>o 设置VersionAndTimeHigh的4位版本号；</p>
<p>o 设置剩余位为随机值。</p>
<p>本文中讨论的密码学随机数，主要根据系统可以提供的信息（内存、硬盘、句柄、程序运行的线程、进程、句柄、堆栈等），利用SHA-1等哈希算法得到。</p>
<p>其他关于密码学随机数的描述，我曾在<a id="dtto" title="具有密码学意义的PRNG（CSPRNG）" href="http://www.yulefox.com/20080403/mmorpg-security-prng.html/" target="_blank">这篇文章</a>中简单提到。</p>
<hr>具体算法实现可以参考文档和开源代码。
<br><img src ="http://www.cppblog.com/Fox/aggbug/102647.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Fox/" target="_blank">Fox</a> 2009-12-06 15:07 <a href="http://www.cppblog.com/Fox/archive/2009/12/06/uuid_algorithm_analysis.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>终论Fibonacci数</title><link>http://www.cppblog.com/Fox/archive/2008/11/06/66071.html</link><dc:creator>Fox</dc:creator><author>Fox</author><pubDate>Wed, 05 Nov 2008 16:34:00 GMT</pubDate><guid>http://www.cppblog.com/Fox/archive/2008/11/06/66071.html</guid><wfw:comment>http://www.cppblog.com/Fox/comments/66071.html</wfw:comment><comments>http://www.cppblog.com/Fox/archive/2008/11/06/66071.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/Fox/comments/commentRss/66071.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Fox/services/trackbacks/66071.html</trackback:ping><description><![CDATA[<p>现在每天的工作主要是为了满足项目需求和进度而不停的思考、敲键盘。有时候也确实需要抽点时间来思考思考那些看上去用不到的一些东西，又想起了<strong>Fibonacci数</strong>。</p> <p>之前曾经三次写过<strong>Fibonacci数</strong>：2007年4月的<a title="我的Fibonacci数列" href="http://www.yulefox.com/index.php/20070420/my-fibonacci-serial.html/" target="_blank">我的Fibonacci数列</a>，2007年12月的<a title="也说说级数求和(1+2+3&hellip;N)和其他" href="http://www.yulefox.com/index.php/20071221/simple-series-sum.html/" target="_blank">也说说级数求和(1+2+3…N)和其他</a>，2008年5月的<a href="http://www.yulefox.com/index.php/20080507/introduction-of-dynamic-programming.html/">动态规划算法</a>，但给出的都不是非常优的算法。</p> <p>上次回去把同学借的《编程之美》偷过来还没怎么看，晚上翻了一下，看到有讲<strong>Fibonacci数</strong>，想起来<a title="Donald E.Knuth" href="http://en.wikipedia.org/wiki/Donald_knuth" target="_blank" rel="tag">Knuth</a>的The Art of Computer Programming Vol.1也讲过，觉得有必要对<strong>Fibonacci数</strong>做个了断。</p> <p>诚如<a title="Donald E.Knuth" href="http://en.wikipedia.org/wiki/Donald_knuth" target="_blank" rel="tag">Knuth</a>在The Art of Computer Programming Vol.1所述，<a title="Fibonacci" href="http://en.wikipedia.org/wiki/Fibonacci" target="_blank" rel="tag">Fibonacci</a>是中世纪以来欧洲最伟大的数学家，他关于al-Khwarizmi的研究催生了算法（algorithm）一词。</p> <p><a href="http://www.yulefox.com/index.php/20081105/fibonacci-sequence-summarize.html/" target="_blank">阅读全文</a></p> <p><strong>看到这些，我又激动了，数学之美，不正是美在这些地方吗？我们不是要做数学家，但这并不妨碍我们站在门口向里张望……</strong></p><img src ="http://www.cppblog.com/Fox/aggbug/66071.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Fox/" target="_blank">Fox</a> 2008-11-06 00:34 <a href="http://www.cppblog.com/Fox/archive/2008/11/06/66071.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>动态规划算法</title><link>http://www.cppblog.com/Fox/archive/2008/05/07/Dynamic_programming.html</link><dc:creator>Fox</dc:creator><author>Fox</author><pubDate>Wed, 07 May 2008 12:43:00 GMT</pubDate><guid>http://www.cppblog.com/Fox/archive/2008/05/07/Dynamic_programming.html</guid><wfw:comment>http://www.cppblog.com/Fox/comments/49153.html</wfw:comment><comments>http://www.cppblog.com/Fox/archive/2008/05/07/Dynamic_programming.html#Feedback</comments><slash:comments>6</slash:comments><wfw:commentRss>http://www.cppblog.com/Fox/comments/commentRss/49153.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/Fox/services/trackbacks/49153.html</trackback:ping><description><![CDATA[<p>以前在学习非数值算法的时候，曾经了解过<a href="http://en.wikipedia.org/wiki/Dynamic_programming" target="_blank">动态规划算法（Dynamic programming）</a>，以下是对<a href="http://en.wikipedia.org/wiki/Main_Page" target="_blank">Wikipedia</a>上<a href="http://en.wikipedia.org/wiki/Dynamic_programming" target="_blank">动态规划</a>的翻译，图也是<a href="http://en.wikipedia.org/wiki/Main_Page" target="_blank">Wikipedia</a>上的，仓促行文，不到之处，请方家指正。</p> <p>这篇文章的术语实在是太多了，所以我在文中加入了少量注释，一律以<em><strong>粗斜体</strong></em>注明。</p> <p>本文的不足之处将<strong>随时修正</strong>，MIT的《<a href="http://mitpress.mit.edu/algorithms/" target="_blank">Introduction to Algorithms</a>》第15章是专门讲动态规划的。</p> <p>_____________________________________________________________</p> <p align="center"><strong><a href="http://en.wikipedia.org/wiki/Dynamic_programming" target="_blank">动态规划</a></strong></p> <p>在数学与计算机科学领域，<strong>动态规划</strong>用于解决那些可分解为<a href="http://en.wikipedia.org/wiki/Overlapping_subproblem" target="_blank">重复子问题</a>（overlapping subproblems，<em><strong>想想递归求阶乘吧</strong></em>）并具有<a href="http://en.wikipedia.org/wiki/Optimal_substructure" target="_blank">最优子结构</a>（optimal substructure，<strong><em>想想最短路径算法</em></strong>）（如下所述）的问题，动态规划比通常算法花费更少时间。  <p>上世纪40年代，<a href="http://en.wikipedia.org/wiki/Richard_Bellman" target="_blank">Richard Bellman</a>最早使用动态规划这一概念表述通过遍历寻找最优决策解问题的求解过程。1953年，Richard Bellman将动态规划<a href="http://www.wu-wien.ac.at/usr/h99c/h9951826/bellman_dynprog.pdf" target="_blank">赋予现代意义</a>，该领域被IEEE纳入系统分析和工程中。为纪念Bellman的贡献，动态规划的核心方程被命名为<a href="http://en.wikipedia.org/wiki/Bellman_equation" target="_blank">贝尔曼方程</a>，该方程以<a href="http://en.wikipedia.org/wiki/Recursion" target="_blank">递归</a>形式重申了一个优化问题。  <p>在“动态规划”（dynamic programming）一词中，programming与“计算机编程”（computer programming）中的programming并无关联，而是来自“<a href="http://en.wikipedia.org/wiki/Mathematical_programming" target="_blank">数学规划</a>”（mathematical programming），也称优化。因此，规划是指对生成活动的优化策略。举个例子，编制一场展览的日程可称为规划。 在此意义上，规划意味着找到一个可行的活动计划。  <ul> <li> <div align="left"><strong>概述</strong></div></li></ul> <p><img style="margin: 10px" src="http://upload.wikimedia.org/wikipedia/en/4/42/Shortest_path_optimal_substructure.png" align="left">  <p><strong></strong>&nbsp; <p><strong>图1</strong> 使用最优子结构寻找最短路径：直线表示边，波状线表示两顶点间的最短路径（路径中其他节点未显示）；粗线表示从起点到终点的最短路径。  <p><strong><em>不难看出，start到goal的最短路径由start的相邻节点到goal的最短路径及start到其相邻节点的成本决定。</em></strong>  <p><strong><em></em></strong>&nbsp; <p>&nbsp; <p>最优子结构即可用来寻找整个问题最优解的子问题的最优解。举例来说，寻找<a href="http://en.wikipedia.org/wiki/Graph_%28mathematics%29" target="_blank">图</a>上某顶点到终点的<a href="http://en.wikipedia.org/wiki/Shortest_path_problem" target="_blank">最短路径</a>，可先计算该顶点所有相邻顶点至终点的最短路径，然后以此来选择最佳整体路径，如<strong>图1</strong>所示。  <p>一般而言，最优子结构通过如下三个步骤解决问题：  <p>a) 将问题分解成较小的子问题；  <p>b) 通过递归使用这三个步骤求出子问题的最优解；  <p>c) 使用这些最优解构造初始问题的最优解。  <p>子问题的求解是通过不断划分为更小的子问题实现的，直至我们可以在常数时间内求解。  <p><img style="margin: 10px" src="http://upload.wikimedia.org/wikipedia/commons/thumb/0/06/Fibonacci_dynamic_programming.svg/108px-Fibonacci_dynamic_programming.svg.png" align="left">  <p><strong></strong>&nbsp; <p><strong></strong>&nbsp; <p><strong>图2</strong> Fibonacci序列的子问题示意图：使用<a href="http://en.wikipedia.org/wiki/Directed_acyclic_graph" target="_blank">有向无环图</a>（DAG, directed acyclic graph）而非<a href="http://en.wikipedia.org/wiki/Tree_structure" target="_blank">树</a>表示重复子问题的分解。  <p><strong><em>为什么是DAG而不是树呢？答案就是，如果是树的话，会有很多重复计算，下面有相关的解释。</em></strong>  <p>&nbsp; <p>&nbsp; <p>一个问题可划分为重复子问题是指通过相同的子问题可以解决不同的较大问题。例如，在Fibonacci序列中，F3 = F1 + F2和F4 = F2 + F3都包含计算F2。由于计算F5需要计算F3和F4，一个比较笨的计算F5的方法可能会重复计算F2两次甚至两次以上。这一点对所有重复子问题都适用：愚蠢的做法可能会为重复计算已经解决的最优子问题的解而浪费时间。  <p>为避免重复计算，可将已经得到的子问题的解保存起来，当我们要解决相同的子问题时，重用即可。该方法即所谓的<a href="http://en.wikipedia.org/wiki/Memoization" target="_blank">缓存</a>（memoization，而不是存储memorization，虽然这个词亦适合，<strong><em>姑且这么叫吧，这个单词太难翻译了，简直就是可意会不可言传，其意义是没计算过则计算，计算过则保存</em></strong>）。当我们确信将不会再需要某一解时，可以将其抛弃，以节省空间。在某些情况下，我们甚至可以提前计算出那些将来会用到的子问题的解。  <p>总括而言，动态规划利用：  <p>1) <a href="http://en.wikipedia.org/wiki/Overlapping_subproblem" target="_blank">重复子问题</a>  <p>2) <a href="http://en.wikipedia.org/wiki/Optimal_substructure" target="_blank">最优子结构</a>  <p>3) <a href="http://en.wikipedia.org/wiki/Memoization" target="_blank">缓存</a>  <p>动态规划通常采用以下两种方式中的一种两个办法：  <p><a href="http://en.wikipedia.org/wiki/Top-down" target="_blank">自顶向下</a>：将问题划分为若干子问题，求解这些子问题并保存结果以免重复计算。该方法将递归和缓存结合在一起。  <p><a href="http://en.wikipedia.org/wiki/Bottom-up" target="_blank">自下而上</a>：先行求解所有可能用到的子问题，然后用其构造更大问题的解。该方法在节省堆栈空间和减少函数调用数量上略有优势，但有时想找出给定问题的所有子问题并不那么直观。  <p>为了提高<a href="http://en.wikipedia.org/wiki/Call-by-name" target="_blank">按名传递</a>（call-by-name，这一机制与<a href="http://en.wikipedia.org/wiki/Call-by-need" target="_blank">按需传递</a>call-by-need相关，<strong><em>复习一下参数传递的各种规则吧，简单说一下，按名传递允许改变实参值</em></strong>）的效率，一些编程语言将函数的返回值“自动”缓存在函数的特定参数集合中。一些语言将这一特性尽可能简化（如<a href="http://en.wikipedia.org/wiki/Scheme_%28programming_language%29" target="_blank">Scheme</a>、<a href="http://en.wikipedia.org/wiki/Common_Lisp" target="_blank">Common Lisp</a>和<a href="http://en.wikipedia.org/wiki/Perl" target="_blank">Perl</a>），也有一些语言需要进行特殊扩展（如C++，<strong><em>C++中使用的是按值传递和按引用传递，因此C++中本无自动缓存机制，需自行实现，具体实现的一个例子是<a href="http://www.apl.jhu.edu/~paulmac/c++-memoization.html" target="_blank">Automated Memoization in C++</a></em></strong>）。无论如何，只有<a href="http://en.wikipedia.org/wiki/Referential_transparency_%28computer_science%29" target="_blank">指称透明</a>（referentially transparent，<strong><em>指称透明是指在程序中使用表达式、函数本身或以其值替换对程序结果没有任何影响</em></strong>）函数才具有这一特性。  <ul> <li><strong>例子</strong> </li></ul> <p><strong>1. Fibonacci序列</strong>  <p>寻找<a href="http://en.wikipedia.org/wiki/Fibonacci_sequence" target="_blank">Fibonacci序列</a>中第n个数，基于其数学定义的直接实现：  <p>&nbsp;&nbsp; function fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if n = 0 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if n = 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return fib(n-1) + fib(n-2)  <p>如果我们调用fib(5)，将产生一棵对于同一值重复计算多次的调用树：  <ol> <li>fib(5)  <li>fib(4) + fib(3)  <li>(fib(3) + fib(2)) + (fib(2) + fib(1))  <li>((fib(2) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1))  <li>(((fib(1) + fib(0)) + fib(1)) + (fib(1) + fib(0))) + ((fib(1) + fib(0)) + fib(1)) </li></ol> <p>特别是，fib(2)计算了3次。在更大规模的例子中，还有更多fib的值被重复计算，将消耗指数级时间。  <p>现在，假设我们有一个简单的<a href="http://en.wikipedia.org/wiki/Associative_array" target="_blank">映射</a>（map）对象<em>m</em>，为每一个计算过的fib及其返回值建立映射，修改上面的函数fib，使用并不断更新<em>m</em>。新的函数将只需<em>O</em>(<em>n</em>)的时间，而非指数时间：  <p>&nbsp;&nbsp; var m := map(0 → 1, 1 → 1)<br>&nbsp;&nbsp; function fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if map m does not contain key n<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m[n] := fib(n-1) + fib(n-2)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return m[n]  <p>这一保存已计算出的数值的技术即被称为<a href="http://en.wikipedia.org/wiki/Memoization" target="_blank">缓存</a>，这儿使用的是<strong>自顶向下</strong>的方法：先将问题划分为若干子问题，然后计算和存储值。  <p>在<strong>自下而上</strong>的方法中，我们先计算较小的fib，然后基于其计算更大的fib。这种方法也只花费线性（<em>O</em>(<em>n</em>)）时间，因为它包含一个<em>n</em>-1次的循环。然而，这一方法只需要常数（<em>O</em>(1)）的空间，相反，<strong>自顶向下</strong>的方法则需要O(<em>n</em>)的空间来储存映射关系。  <p>&nbsp;&nbsp; function fib(n)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var previousFib := 0, currentFib := 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if n = 0 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 0<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if n = 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; repeat n-1 times<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; var newFib := previousFib + currentFib<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; previousFib := currentFib<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; currentFib&nbsp; := newFib<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return currentFib  <p>在这两个例子，我们都只计算fib(2)一次，然后用它来计算fib(3)和fib(4)，而不是每次都重新计算。  <p><strong>2. 一种平衡的0-1矩阵</strong>  <p>考虑<em>n</em>*<em>n</em>矩阵的赋值问题：只能赋0和1，<em>n</em>为偶数，使每一行和列均含<em>n</em>/2个0及<em>n</em>/2个1。例如，当<em>n</em>=4时，两种可能的方案是：  <p>+ - - - - +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + - - - - +<br>| 0 1 0 1 |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 0 0 1 1 |<br>| 1 0 1 0 |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 0 0 1 1 |<br>| 0 1 0 1 |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 1 1 0 0 |<br>| 1 0 1 0 |&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; | 1 1 0 0 |<br>+ - - - - +&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + - - - - +  <p>问：对于给定<em>n</em>，共有多少种不同的赋值方案。  <p>至少有三种可能的算法来解决这一问题：<a href="http://en.wikipedia.org/wiki/Brute_force" target="_blank">穷举法</a>（brute force）、<a href="http://en.wikipedia.org/wiki/Backtracking" target="_blank">回溯法</a>（backtracking）及动态规划（dynamic programming）。穷举法列举所有赋值方案，并逐一找出满足平衡条件的方案。由于共有C(<em>n</em>, <em>n</em>/2)^<em>n</em>种方案（<strong><em>在一行中，含n/2个0及n/2个1的组合数为C(n,n/2)，相当于从n个位置中选取n/2个位置置0，剩下的自然是1</em></strong>），当<em>n</em>=6时，穷举法就已经几乎不可行了。回溯法先将矩阵中部分元素置为0或1，然后检查每一行和列中未被赋值的元素并赋值，使其满足每一行和列中0和1的数量均为<em>n</em>/2。回溯法比穷举法更加巧妙一些，但仍需遍历所有解才能确定解的数目，可以看到，当<em>n</em>=8时，该题解的数目已经高达116963796250。动态规划则无需遍历所有解便可确定解的数目（<strong><em>意思是划分子问题后，可有效避免若干子问题的重复计算</em></strong>）。  <p>通过动态规划求解该问题出乎意料的简单。考虑每一行恰含<em>n</em>/2个0和<em>n</em>/2个1的<em>k</em>*<em>n</em>(1&lt;=<em>k</em>&lt;=<em>n</em>)的子矩阵，函数f根据每一行的可能的赋值映射为一个向量，每个向量由<em>n</em>个整数对构成。向量每一列对应的一个整数对中的两个整数分别表示该列上该行以下已经放置的0和1的数量。该问题即转化为寻找f((<em>n</em>/2,<em>n</em>/2),(<em>n</em>/2,<em>n</em>/2),...,(<em>n</em>/2,<em>n</em>/2))（具有<em>n</em>个参数或者说是一个含<em>n</em>个元素的向量）的值。其子问题的构造过程如下：  <p>1) 最上面一行（<strong><em>第k行</em></strong>）具有C(<em>n</em>, <em>n</em>/2)种赋值；  <p>2) 根据最上面一行中每一列的赋值情况（为0或1），将其对应整数对中相应的元素值减1；  <p>3) 如果任一整数对中的任一元素为负，则该赋值非法，不能成为正确解；  <p>4) 否则，完成对<em>k</em>*<em>n</em>的子矩阵中最上面一行的赋值，取<em>k</em>=<em>k</em>-1，计算剩余的(<em>k</em>-1)*<em>n</em>的子矩阵的赋值；  <p>5) 基本情况是一个1*<em>n</em>的细小的子问题，此时，该子问题的解的数量为0或1，取决于其向量是否是<em>n</em>/2个(0, 1)和<em>n</em>/2个(1, 0)的排列。  <p>例如，在上面给出的两种方案中，向量序列为：  <p>((2, 2) (2, 2) (2, 2) (2, 2))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((2, 2) (2, 2) (2, 2) (2, 2))&nbsp;&nbsp;&nbsp;&nbsp; k = 4<br>&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1  <p><font color="#800000">((1, 2) (2, 1) (1, 2) (2, 1))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((1, 2) (1, 2) (2, 1) (2, 1))&nbsp;&nbsp;&nbsp;&nbsp; k = 3</font><br>&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1  <p>((1, 1) (1, 1) (1, 1) (1, 1))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((0, 2) (0, 2) (2, 0) (2, 0))&nbsp;&nbsp;&nbsp;&nbsp; k = 2<br>&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0  <p>((0, 1) (1, 0) (0, 1) (1, 0))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((0, 1) (0, 1) (1, 0) (1, 0))&nbsp;&nbsp;&nbsp;&nbsp; k = 1<br>&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0  <p>((0, 0) (0, 0) (0, 0) (0, 0))&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((0, 0) (0, 0), (0, 0) (0, 0))  <p><strong><font color="#800000"><em>动态规划在此的意义在于避免了相同f的重复计算，更进一步的，上面着色的两个f，虽然对应向量不同，但f的值是相同的，想想为什么吧</em>:D<em>。</em></font></strong>  <p>该问题解的数量（序列a058527在OEIS）是1, 2, 90, 297200, 116963796250, 6736218287430460752, ...  <p>下面的外部链接中包含回溯法的Perl源代码实现，以及动态规划法的MAPLE和C语言的实现。  <p><strong>3. 棋盘</strong>  <p>考虑<em>n</em>*<em>n</em>的棋盘及成本函数C(<em>i</em>,<em>j</em>)，该函数返回方格(<em>i</em>,<em>j</em>)相关的成本。以5*5的棋盘为例：  <p>5 | 6 7 4 7 8<br>4 | 7 6 1 1 4<br>3 | 3 5 7 8 2<br>2 | 2 6 7 0 2<br>1 | 7 3 5 6 1<br>- + - - - - -<br>&nbsp; | 1 2 3 4 5  <p>可以看到：C(1,3)=5  <p>从棋盘的任一方格的第一阶（即行）开始，寻找到达最后一阶的最短路径（使所有经过的方格的成本之和最小），假定只允许向左对角、右对角或垂直移动一格。  <p>5 |<br>4 |<br>3 |<br>2 |&nbsp;&nbsp; x x x<br>1 |&nbsp;&nbsp;&nbsp;&nbsp; o<br>- + - - - - -<br>&nbsp; | 1 2 3 4 5  <p>该问题展示了最优子结构。即整个问题的全局解依赖于子问题的解。定义函数q(<em>i</em>,<em>j</em>)，令：q(<em>i</em>,<em>j</em>)表示到达方格(<em>i</em>,<em>j</em>)的最低成本。  <p>如果我们可以求出第<em>n</em>阶所有方格的q(<em>i</em>,<em>j</em>)值，取其最小值并逆向该路径即可得到最短路径。  <p>记q(<em>i</em>,<em>j</em>)为方格(<em>i</em>,<em>j</em>)至其下三个方格（<strong><em>(i-1,j-1)、(i-1,j)、(i-1,j+1)</em></strong>）最低成本与c(<em>i</em>,<em>j</em>)之和，例如：  <p>5 |<br>4 |&nbsp;&nbsp;&nbsp;&nbsp; <em>A</em><br>3 |&nbsp;&nbsp; <em>B C D<br></em>2 |<br>1 |<br>- + - - - - -<br>&nbsp; | 1 2 3 4 5  <p>q(<em>A</em>) = min(q(<em>B</em>),q(<em>C</em>),q(<em>D</em>)) + c(<em>A</em>)  <p>定义q(<em>i</em>,<em>j</em>)的一般形式：  <p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |-&nbsp; inf.&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <em>j</em>&lt;1 or <em>j</em>&gt;n<br>q(<em>i</em>,<em>j</em>) = -+-&nbsp; c(<em>i</em>,<em>j</em>)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; i=1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; |-&nbsp; min(q(<em>i</em>-1,<em>j</em>-1),q(<em>i</em>-1,<em>j</em>),q(<em>i</em>-1,<em>j</em>+1))+c(<em>i</em>,<em>j</em>)&nbsp;&nbsp; otherwise.  <p>方程的第一行是为了保证递归可以退出（处理边界时只需调用一次递归函数）。第二行是第一阶的取值，作为计算的起点。第三行的递归是算法的重要组成部分，与例子<em>A</em>、<em>B</em>、<em>C</em>、<em>D</em>类似。从该定义我们可以直接给出计算q(<em>i</em>,<em>j</em>)的简单的递归代码。在下面的伪代码中，<em>n</em>表示棋盘的维数，C(<em>i</em>,<em>j</em>)是成本函数，min()返回一组数的最小值：  <p>function minCost(i, j)<br>&nbsp;&nbsp;&nbsp; if j &lt; 1 or j &gt; n<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return infinity<br>&nbsp;&nbsp;&nbsp; else if i = 1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return c(i,j)<br>&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; return min(minCost(i-1,j-1),minCost(i-1,j),minCost(i-1,j+1))+c(i,j)  <p>需要指出的是，minCost只计算路径成本，并不是最终的实际路径，二者相去不远。与Fibonacci数相似，由于花费大量时间重复计算相同的最短路径，这一方式慢的恐怖。不过，如果采用自下而上法，使用二维数组q[i,j]代替函数minCost，将使计算过程快得多。我们为什么要这样做呢？选择保存值显然比使用函数重复计算相同路径要简单的多。  <p>我们还需要知道实际路径。路径问题，我们可以通过另一个前任数组p[i,j]解决。这个数组用于描述路径，代码如下：  <p>function computeShortestPathArrays()<br>&nbsp;&nbsp;&nbsp;&nbsp; for x from 1 to n<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; q[1, x] := c(1, x)<br>&nbsp;&nbsp;&nbsp;&nbsp; for y from 1 to n<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; q[y, 0]&nbsp;&nbsp;&nbsp;&nbsp; := infinity<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; q[y, n + 1] := infinity<br>&nbsp;&nbsp;&nbsp;&nbsp; for y from 2 to n<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; for x from 1 to n<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m := min(q[y-1, x-1], q[y-1, x], q[y-1, x+1])<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; q[y, x] := m + c(y, x)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if m = q[y-1, x-1]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p[y, x] := -1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else if m = q[y-1, x]<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p[y, x] :=&nbsp; 0<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; p[y, x] :=&nbsp; 1  <p>剩下的求最小值和输出就比较简单了：  <p>function computeShortestPath()<br>&nbsp;&nbsp;&nbsp;&nbsp; computeShortestPathArrays()<br>&nbsp;&nbsp;&nbsp;&nbsp; minIndex := 1<br>&nbsp;&nbsp;&nbsp;&nbsp; min := q[n, 1] <br>&nbsp;&nbsp;&nbsp;&nbsp; for i from 2 to n <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if q[n, i] &lt; min<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; minIndex := i<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; min := q[n, i]<br>&nbsp;&nbsp;&nbsp;&nbsp; printPath(n, minIndex)  <p>function printPath(y, x)<br>&nbsp;&nbsp;&nbsp;&nbsp; print(x)<br>&nbsp;&nbsp;&nbsp;&nbsp; print("&lt;-")<br>&nbsp;&nbsp;&nbsp;&nbsp; if y = 2<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; print(x + p[y, x])<br>&nbsp;&nbsp;&nbsp;&nbsp; else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; printPath(y-1, x + p[y, x])  <p><strong>4. 序列比对 </strong> <p><a href="http://en.wikipedia.org/wiki/Sequence_alignment" target="_blank">序列比对</a>是动态规划的一个重要应用。序列比对问题通常是使用编辑操作（替换、插入、删除一个要素等）进行序列转换。每次操作对应不同成本，目标是找到编辑序列的最低成本。  <p>可以很自然地想到使用递归解决这个问题，序列<em>A</em>到<em>B</em>的最优编辑通过以下措施之一实现：  <p>插入<em>B</em>的第一个字符，对<em>A</em>和<em>B</em>的剩余序列进行最优比对；  <p>删去<em>A</em>的第一个字符，对<em>A</em>和<em>B</em>进行最优比对；  <p>用<em>B</em>的第一个字符替换<em>A</em>的第一个字符，对<em>A</em>的剩余序列和<em>B</em>进行最优比对。  <p>局部比对可在矩阵中列表表示，单元(<em>i</em>,<em>j</em>)表示A[1..<em>i</em>]到b[1..<em>j</em>]最优比对的成本。单元(<em>i</em>,<em>j</em>)的成本计算可通过累加相邻单元的操作成本并选择最优解实现。至于序列比对的不同实现算法，参见<a href="http://en.wikipedia.org/wiki/Smith-Waterman" target="_blank">Smith-Waterman</a>和<a href="http://en.wikipedia.org/wiki/Needleman-Wunsch" target="_blank">Needleman-Wunsch</a>。  <p><strong><em>对序列比对的话题并不熟悉，更多的话也无从谈起，有熟悉的朋友倒是可以介绍一下。</em></strong>  <ul> <li><strong>应用动态规划的算法</strong> </li></ul> <p>1) 许多<a href="http://en.wikipedia.org/wiki/String_%28computer_science%29" target="_blank">字符串</a>操作算法如<a href="http://en.wikipedia.org/wiki/Longest_common_subsequence_problem" target="_blank">最长公共子列</a>、<a href="http://en.wikipedia.org/wiki/Longest_increasing_subsequence_problem" target="_blank">最长递增子列</a>、<a href="http://en.wikipedia.org/wiki/Longest_common_substring_problem" target="_blank">最长公共字串</a>； </p> <p>2) 将动态规划用于<a href="http://en.wikipedia.org/wiki/Undirected_graph" target="_blank">图</a>的树分解，可以有效解决有界<a href="http://en.wikipedia.org/wiki/Treewidth" target="_blank">树宽图</a>的<a href="http://en.wikipedia.org/wiki/Tree_decomposition" target="_blank">生成树</a>等许多与图相关的算法问题；  <p>3) 决定是否及如何可以通过某一特定<a href="http://en.wikipedia.org/wiki/Context-free_grammar" target="_blank">上下文无关文法</a>产生给定字符串的<a href="http://en.wikipedia.org/wiki/CYK_algorithm" target="_blank">Cocke-Younger-Kasami</a> (CYK)算法；  <p>4) <a href="http://en.wikipedia.org/wiki/Computer_chess" target="_blank">计算机国际象棋</a>中<a href="http://en.wikipedia.org/wiki/Transposition_table" target="_blank">转换表</a>和<a href="http://en.wikipedia.org/wiki/Refutation_table" target="_blank">驳斥表</a>的使用；  <p>5) <a href="http://en.wikipedia.org/wiki/Viterbi_algorithm" target="_blank">Viterbi算法</a>（用于<a href="http://en.wikipedia.org/wiki/Hidden_Markov_model" target="_blank">隐式马尔可夫模型</a>）；  <p>6) <a href="http://en.wikipedia.org/wiki/Earley_algorithm" target="_blank">Earley算法</a>（一类<a href="http://en.wikipedia.org/wiki/Chart_parser" target="_blank">图表分析器</a>）；  <p>7) <a href="http://en.wikipedia.org/wiki/Needleman-Wunsch_algorithm" target="_blank">Needleman-Wunsch</a>及其他<a href="http://en.wikipedia.org/wiki/Bioinformatics" target="_blank">生物信息学</a>中使用的算法，包括<a href="http://en.wikipedia.org/wiki/Sequence_alignment" target="_blank">序列比对</a>、<a href="http://en.wikipedia.org/wiki/Structural_alignment" target="_blank">结构比对</a>、<a href="http://en.wikipedia.org/wiki/RNA_structure" target="_blank">RNA结构</a>预测；  <p>8) <a href="http://en.wikipedia.org/wiki/Levenshtein_distance" target="_blank">Levenshtein距离</a>（编辑距离）；  <p>9) <a href="http://en.wikipedia.org/wiki/Floyd-Warshall_algorithm" target="_blank">弗洛伊德最短路径</a>算法；  <p>10) <a href="http://en.wikipedia.org/wiki/Chain_matrix_multiplication" target="_blank">连锁矩阵乘法</a>次序优化；  <p>11) <a href="http://en.wikipedia.org/wiki/Subset_sum_problem" target="_blank">子集求和</a>、<a href="http://en.wikipedia.org/wiki/Knapsack_problem" target="_blank">背包问题</a>和<a href="http://en.wikipedia.org/wiki/Partition_problem" target="_blank">分治问题</a>的伪多项式时间算法；  <p>12) 计算两个时间序列全局距离的<a href="http://en.wikipedia.org/wiki/Dynamic_time_warping" target="_blank">动态时间规整</a>算法；  <p>13) 关系型数据库的查询优化的<a href="http://en.wikipedia.org/wiki/Selinger" target="_blank">Selinger</a>（又名<a href="http://en.wikipedia.org/wiki/System_R" target="_blank">System R</a>）算法；  <p>14) 评价B样条曲线的<a href="http://en.wikipedia.org/wiki/De_Boor_algorithm" target="_blank">De Boor算法</a>；  <p>15) 用于解决板球运动中断问题的<a href="http://en.wikipedia.org/wiki/Duckworth-Lewis_method" target="_blank">Duckworth-Lewis</a>方法；  <p>16) 价值迭代法求解<a href="http://en.wikipedia.org/wiki/Markov_decision_process" target="_blank">马尔可夫决策过程</a>；  <p>17) 一些图形图像边缘以下的选择方法，如“磁铁”选择工具在<a href="http://en.wikipedia.org/wiki/Photoshop" target="_blank">Photoshop</a>；  <p>18) <a href="http://en.wikipedia.org/wiki/Interval_scheduling" target="_blank">间隔调度</a>；  <p>19) <a href="http://en.wikipedia.org/wiki/Word_wrap" target="_blank">自动换行</a>；  <p>20) <a href="http://en.wikipedia.org/wiki/Travelling_salesman_problem" target="_blank">巡回旅行商问题</a>（<strong><em>又称邮差问题或货担郎问题</em></strong>）；  <p>21) <a href="http://en.wikipedia.org/wiki/Segmented_least_squares" target="_blank">分段最小二乘法</a>；  <p>22) <a href="http://en.wikipedia.org/wiki/Music_Information_Retrieval" target="_blank">音乐信息检索</a>跟踪。  <p><strong><em>对于这些算法应用，大多未曾接触，甚至术语翻译的都有问题，鉴于本文主要在于介绍动态规划，所以仓促之中，未及查证。</em></strong>  <ul> <li><strong>相关</strong> </li></ul> <p>1) <a href="http://en.wikipedia.org/wiki/Bellman_equation" target="_blank">贝尔曼方程</a>  <p>2) <a href="http://en.wikipedia.org/wiki/Markov_decision_process" target="_blank">马尔可夫决策过程</a>  <p>3) <a href="http://en.wikipedia.org/wiki/Greedy_algorithm" target="_blank">贪心算法</a> </p> <ul> <li><strong>参考</strong> </li></ul> <li>Adda, Jerome, and Cooper, Russell, 2003. <em><a href="http://www.eco.utexas.edu/~cooper/dynprog/dynprog1.html">Dynamic Economics.</a></em> MIT Press. An accessible introduction to dynamic programming in economics. The link contains sample programs.  <li>Richard Bellman, 1957, <em>Dynamic Programming</em>, Princeton University Press. Dover paperback edition (2003), <a href="http://en.wikipedia.org/wiki/Special:BookSources/0486428095">ISBN 0486428095</a>.  <li>Bertsekas, D. P., 2000. <em>Dynamic Programming and Optimal Control, Vols. 1 &amp; 2</em>, 2nd ed. Athena Scientific. <a href="http://en.wikipedia.org/wiki/Special:BookSources/1886529094">ISBN 1-886529-09-4</a>.  <li><a href="http://en.wikipedia.org/wiki/Thomas_H._Cormen">Thomas H. Cormen</a>, <a href="http://en.wikipedia.org/wiki/Charles_E._Leiserson">Charles E. Leiserson</a>, <a href="http://en.wikipedia.org/wiki/Ronald_L._Rivest">Ronald L. Rivest</a>, and <a href="http://en.wikipedia.org/wiki/Clifford_Stein">Clifford Stein</a>, 2001. <em><a href="http://en.wikipedia.org/wiki/Introduction_to_Algorithms">Introduction to Algorithms</a></em>, 2nd ed. MIT Press &amp; McGraw-Hill. <a href="http://en.wikipedia.org/wiki/Special:BookSources/0262032937">ISBN 0-262-03293-7</a>. Especially pp. 323–69.  <li>Giegerich, R., Meyer, C., and Steffen, P., 2004, "<a href="http://bibiserv.techfak.uni-bielefeld.de/adp/ps/GIE-MEY-STE-2004.pdf">A Discipline of Dynamic Programming over Sequence Data,</a>" <em>Science of Computer Programming 51</em>: 215-263.  <li><a href="http://en.wikipedia.org/wiki/Nancy_Stokey">Nancy Stokey</a>, and <a href="http://en.wikipedia.org/wiki/Robert_E._Lucas">Robert E. Lucas</a>, with <a href="http://en.wikipedia.org/wiki/Edward_Prescott">Edward Prescott</a>, 1989. <em>Recursive Methods in Economic Dynamics</em>. Harvard Univ. Press.  <li>S. P. Meyn, 2007. <a href="http://decision.csl.uiuc.edu/~meyn/pages/CTCN/CTCN.html">Control Techniques for Complex Networks</a>, Cambridge University Press, 2007.  <ul> <li><strong>外部链接</strong> </li></ul> <ul> <li><a href="http://www.dyna.org/">Dyna</a>, a declarative programming language for dynamic programming algorithms  <li>Wagner, David B., 1995, "<a href="http://citeseer.ist.psu.edu/268391.html">Dynamic Programming.</a>" An introductory article on dynamic programming in <a href="http://en.wikipedia.org/wiki/Mathematica">Mathematica</a>.  <li><a href="http://www.cse.ohio-state.edu/~gurari/course/cis680/cis680Ch21.html">Ohio State University: CIS 680: class notes on dynamic programming</a>, by Eitan M. Gurari  <li><a href="http://mat.gsia.cmu.edu/classes/dynamic/dynamic.html">A Tutorial on Dynamic programming</a>  <li><a href="http://ocw.mit.edu/OcwWeb/Electrical-Engineering-and-Computer-Science/6-046JFall-2005/LectureNotes/index.htm">MIT course on algorithms</a> - Includes a video lecture on DP along with lecture notes -- See lecture 15.  <li><a href="http://www.csse.monash.edu.au/~lloyd/tildeAlgDS/Dynamic">More DP Notes</a>  <li>King, Ian, 2002 (1987), "<a href="http://www.business.auckland.ac.nz/Departments/econ/workingpapers/full/Text230.pdf">A Simple Introduction to Dynamic Programming in Macroeconomic Models.</a>" An introduction to dynamic programming as an important tool in economic theory.  <li><a href="http://www.topcoder.com/tc?module=Static&amp;d1=tutorials&amp;d2=dynProg">Dynamic Programming: from novice to advanced</a> A TopCoder.com article by Dumitru on Dynamic Programming  <li><a href="http://bibiserv.techfak.uni-bielefeld.de/adp/">Algebraic Dynamic Programming</a> - a formalized framework for dynamic programming, including an <a href="http://bibiserv.techfak.uni-bielefeld.de/dpcourse">entry-level course</a> to DP, University of Bielefeld  <li>Dreyfus, Stuart, "<a href="http://www.eng.tau.ac.il/~ami/cd/or50/1526-5463-2002-50-01-0048.pdf">Richard Bellman on the birth of Dynamic Programming.</a>"  <li><a href="http://www.avatar.se/lectures/molbioinfo2001/dynprog/dynamic.html">Dynamic programming tutorial</a>  <li><a href="http://20bits.com/2007/05/08/introduction-to-dynamic-programming/">An Introduction to Dynamic Programming</a> </li></ul> <p>_____________________________________________________________</p> <p>关于动态规划，这只是一篇译文，后面将根据实际问题具体写点动态规划的应用。</p></li><img src ="http://www.cppblog.com/Fox/aggbug/49153.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/Fox/" target="_blank">Fox</a> 2008-05-07 20:43 <a href="http://www.cppblog.com/Fox/archive/2008/05/07/Dynamic_programming.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>