<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>刘未鹏 &#124; Mind Hacks &#187; 算法</title>
	<atom:link href="http://mindhacks.cn/tags/%e7%ae%97%e6%b3%95/feed/" rel="self" type="application/rss+xml" />
	<link>http://mindhacks.cn</link>
	<description>思维改变生活</description>
	<lastBuildDate>Sun, 20 Nov 2011 13:31:48 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.0.1</generator>
		<item>
		<title>知其所以然（三）：为什么算法这么难？</title>
		<link>http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/</link>
		<comments>http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/#comments</comments>
		<pubDate>Sat, 09 Jul 2011 16:24:32 +0000</pubDate>
		<dc:creator>刘未鹏</dc:creator>
				<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/</guid>
		<description><![CDATA[广大码农同学们大多都有个共识，认为算法是个硬骨头，很难啃，悲剧的是啃完了还未必有用——除了面试的时候。实际工程中一般都是用现成的模块，一般只需了解算法的目的和时空复杂度即可。

不过话说回来，面试的时候面算法，包括面项目中几乎不大可能用到的算法，其实并不能说是毫无道理的。算法往往是对学习和理解能力的一块试金石，难的都能掌握，往往容易的事情不在话下。志于高者得于中。反之则不成立。另一方面，虽说教科书算法大多数都是那些即便用到也是直接拿模块用的，但不幸的是，我们这群搬砖头的有时候还非得做些发明家的事情：要么是得把算法当白盒加以改进以满足手头的特定需求；要么干脆就是要发明轮子。所以，虽说面试的算法本身未必用得到，但熟悉各种算法的人通常更可能熟悉算法的思想，从而更可能具备这里说的两种能力。

那么，为什么说算法很难呢？这个问题只有两种可能的原因：

算法本身就很难。也就是说，算法这个东西对于人类的大脑来说本身就是个困难的事儿。 
讲得太烂。 
下面会说明，算法之所以被绝大多数人认为很难，以上两个原因兼具。

我们说算法难的时候，有两种情况：一种是学算法难。第二种是设计算法难。对于前者，大多数人（至少我当年如此）学习算法几乎是在背算法，就跟背菜谱似的（“Cookbook”是深受广大码农喜爱的一类书），然而算法和菜谱的区别在于，算法包含的细节复杂度是菜谱的无数倍，算法的问题描述千变万化，逻辑过程百转千回，往往看得人愁肠百结，而相较之下任何菜谱涉及到的基本元素也就那么些（所以程序员肯定都具有成为好厨师的潜力:D）注意，即便你看了算法的证明，某种程度上还是“背”（为什么这么说，后面会详述）。我自己遇到新算法基本是会看证明的，但是发现没多久还是会忘掉，这是死记硬背的标准症状。如果你也啃过算法书，我相信很大可能性你会有同感：为什么当时明明懂了，但没多久就忘掉了呢？为什么当时明明非常理解其证明，但没过多久想要自己去证明时却发现怎么都没法补上证明中缺失的一环呢？]]></description>
			<content:encoded><![CDATA[<p><font color="#a5a5a5">不知不觉《知其所以然》系列竟然也写到第三篇了，虽然前面两篇也说了不少，但是总觉得还有东西没有说“透”，或者说没有说“好”。所以这篇试图从不同的角度用更好的例子来继续深入阐述。（感谢<a href="http://www.douban.com/people/silwile/">silwile</a>对本文的review和意见）</font></p>
<hr />
<p>广大码农同学们大多都有个共识，认为算法是个硬骨头，很难啃，悲剧的是啃完了还未必有用——除了面试的时候。实际工程中一般都是用现成的模块，一般只需了解算法的目的和时空复杂度即可。</p>
<p>不过话说回来，面试的时候面算法，包括面项目中几乎不大可能用到的算法，其实并不能说是毫无道理的。<strong>算法往往是对学习和理解能力的一块试金石</strong>，难的都能掌握，往往容易的事情不在话下。志于高者得于中。反之则不成立。另一方面，虽说教科书算法大多数都是那些即便用到也是直接拿模块用的，但不幸的是，我们这群搬砖头的有时候还非得做些发明家的事情：要么是得把算法当白盒加以改进以满足手头的特定需求；要么干脆就是要发明轮子。所以，虽说面试的算法本身未必用得到，但熟悉各种算法的人通常更可能熟悉算法的思想，从而更可能具备这里说的两种能力。</p>
<p>那么，为什么说算法很难呢？这个问题只有两种可能的原因：</p>
<ol>
<li>算法本身就很难。也就是说，算法这个东西对于人类的大脑来说本身就是个困难的事儿。 </li>
<li>讲得太烂。 </li>
</ol>
<p>下面会说明，算法之所以被绝大多数人认为很难，以上两个原因兼具。</p>
<p>我们说算法难的时候，有两种情况：<strong>一种是学算法难。第二种是设计算法难</strong>。对于前者，大多数人（至少我当年如此）学习算法几乎是在背算法，就跟背菜谱似的（“Cookbook”是深受广大码农喜爱的一类书），然而算法和菜谱的区别在于，算法包含的细节复杂度是菜谱的无数倍，算法的问题描述千变万化，逻辑过程百转千回，往往看得人愁肠百结，而相较之下任何菜谱涉及到的基本元素也就那么些（所以程序员肯定都具有成为好厨师的潜力:D）注意，即便你看了算法的证明，某种程度上还是“背”（为什么这么说，后面会详述）。我自己遇到新算法基本是会看证明的，但是发现没多久还是会忘掉，这是死记硬背的标准症状。如果你也啃过算法书，我相信很大可能性你会有同感：<em>为什么当时明明懂了，但没多久就忘掉了呢？为什么当时明明非常理解其证明，但没过多久想要自己去证明时却发现怎么都没法补上证明中缺失的一环呢？</em></p>
<p>初中学习几何证明的时候，你会不会傻到去背一个定理的证明？不会。你只会背结论。为什么？一方面，因为证明过程包含大量的细节。另一方面，证明的过程环环相扣，往往只需要注意其中关键的一两步，便能够自行推导出来。算法逻辑描述就好比定理，算法的证明的过程就好比定理的证明过程。但不幸的是，与数学里面大量简洁的基本结论不同，算法这个“结论”可不是那么好背的，许多时候，<strong>算法本身的逻辑就几乎包含了与其证明过程等同的信息量</strong>，甚至算法逻辑本身就是证明过程（随便翻开一本经典的算法书，看几个经典的教科书算法，你会发现算法逻辑和算法证明的联系有多紧密）。于是我们又回到刚才那个问题：你会去背数学证明么？<strong>既然没人会傻到去背整个证明，又为什么要生硬地去背算法呢</strong>？</p>
<p>那么，不背就不背，去理解算法的证明如何？理解了算法的证明过程，便更有可能记住算法的逻辑细节，理解记忆嘛。然而，仍然不幸的是，绝大多数算法书在这方面做的实在糟糕，证明倒是给全了，逻辑也倒是挺严谨的，可是似乎没有作者能真正还原算法发明者本身如何得到算法以及算法证明的思维过程，按理说，证明的过程应该反映了这个思维过程，但是在下文关于霍夫曼编码的例子中你会看到，其实饱受赞誉的<a href="http://www.amazon.com/Introduction-Algorithms-International-Thomas-Cormen/dp/0262533057">CLRS</a>和<a href="http://www.amazon.com/Algorithms-Sanjoy-Dasgupta/dp/0073523402">《Algorithms》</a><em>不仅没能还原这个过程，反而掩盖了这个过程</em>。</p>
<p>必须说明的是，没有哪位作者是故意这样做的，但任何人在讲解一个自己已经理解了的东西的时候，往往会无意识地对自己的讲解进行“线性化”，例如证明题，如果你回忆一下高中做平面几何证明题的经历，就会意识到，其实证明的过程是一个充满了试错，联想，反推，特例，修改问题条件，穷举等等一干“非线性”思维的，混乱不堪的过程，而并不像写在课本上那样——引理1，引理2，定理1，定理2，一口气直到最终结论。这样的证明过程也许容易理解，但绝对不容易记忆。过几天你就会忘记其中一个或几个引理，其中的一步或几步关键的手法，然后当你想要回过头来自己试着去证明的时候，就会发现卡在某个关键的地方，为什么会这样？因为证明当中并没有告诉你为什么作者当时会想到证明算法需要那么一个引理或手法，所以，<strong>虽说看完证明之后，对算法这个结论而言你是知其所以然了，但对于算法的证明过程你却还没知其所以然</strong>。在我们大脑的记忆系统当中，<strong>新的知识必须要和既有的知识建立联系，才容易被回忆起来</strong>（<a href="http://mindhacks.cn/2009/03/28/effective-learning-and-memorization/">《如何有效地学习与记忆》</a>）<strong>，联系越多，越容易回忆，而一个天外飞仙似地引理，和我们既有的知识没有半毛钱联系，没娘的孩子没人疼，自然容易被遗忘。</strong>（为什么还原思维过程如此困难呢？我曾经在<a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/">知其所以然（一）</a>里详述）</p>
<p>正因为绝大多数算法书上悲剧的算法证明过程，很多人发现证明本身也不好记，于是宁可选择直接记结论。当年我在数学系，考试会考证明过程，但似乎计算机系的考试考算法证明过程就是荒谬的？作为“工程”性质的程序设计，似乎更注重使用和结果。但是如果是你需要在项目中自己设计一个算法呢？这种时候最起码需要做的就是证明算法的正确性吧。我们面试的时候往往都会遇到一些算法设计问题，我总是会让应聘者去证明算法的正确性，因为即便是<b>一个“看上去”正确的算法，真正需要证明起来往往发现并不是那么容易</b>。</p>
<p>所以说，绝大多数算法书在作为培养算法设计者的角度来说是失败的，比数学教育更失败。大多数人学完了初中平面几何都会做证明题（数学书不会要求你记住几何所有的定理），但很多人看完了一本算法书还是一团浆糊，不会证明一些起码的算法，我们背了一坨又一坨结论，非但这些结论许多根本用不上，就连用上的那些也不会证明。为什么会出现这样的差异？因为数学教育的理想目的是为了让你成为能够发现新定理的科学家，而码农系的算法教育的目的却更现实，是为了让你成为能够使用算法做事情的工程师。然而，事情真的如此简单么？如果真是这样的话干脆连算法结论都不要背了，只要知道算法做的是什么事情，时空复杂度各是多少即可。</p>
<p>如果说以上提到的算法难度（讲解和记忆的难度）属于<a href="http://en.wikipedia.org/wiki/Accidental_complexity">Accidental Complexity</a>的话，算法的另一个难处便是Essential Complexity了：算法设计。还是拿数学证明来类比（如果你看过《Introduction to Algorithms：A Creative Approach》就知道算法和数学证明是多么类似。），与单单只需证明相比，<strong>设计算法的难处在于，定理和证明都需要你去探索，尤其是前者</strong>——你需要去自行发现关键的那（几）个定理，跟证明已知结论相比（已经确定知道结论是正确的了，你只需要用逻辑来连接结论和条件），<strong>这件事情的复杂度往往又难上一个数量级。</strong></p>
<p>一个有趣的事实是，<b>算法的探索过程往往蕴含算法的证明过程</b>，理想的算法书应该通过还原算法的探索过程，从而让读者不仅能够自行推导出证明过程，同时还能够具备探索新算法的能力。之所以这么说，皆因为我是个懒人，懒人总梦想学点东西能够实现以下两个目的：</p>
<ol>
<li><strong>一劳永逸</strong>：程序员都知道“<a href="http://en.wikipedia.org/wiki/Write_once,_run_anywhere">一次编写到处运行</a>”的好处，多省事啊。学了就忘，忘了又得学，翻来覆去浪费生命。为什么不能看了一遍就再也不会忘掉呢？到底是教的不好，还是学得不好？ </li>
<li><strong>事半功倍</strong>：事实上，程序员不仅讲究一次编写到处运行，更讲究“一次编写到处使用”（也就是俗称的“复用”）。如果学一个算法所得到的经验可以到处使用，学一当十，推而广之，时间的利用效率便会大大提高。究竟怎样学习，才能够使得经验的外推（extrapolate）效率达到最大呢？ </li>
</ol>
<p>想要做到这两点就必须<b>尽量从知识树的“根节点”入手</b>，虽然这是一个美梦，例如数学界寻找“根节点”的<a href="http://mindhacks.cn/2008/04/18/learning-from-polya/">美梦由来已久</a>（《跟波利亚学解题》的“一点历史”小节），但哥德尔一个证明就<a href="http://mindhacks.cn/2006/10/15/cantor-godel-turing-an-eternal-golden-diagonal/">让美梦成了泡影</a>（《永恒的金色对角线》））；但是，这并不阻止我们去寻找更高层的节点——<a href="http://mindhacks.cn/2008/04/18/learning-from-polya/">更具普适性的解题原则和方法</a>。所以，理想的算法书或者算法讲解应该是从最具一般性的思维法则开始，顺理成章地推导出算法，这个过程应该尽量还原一个”普通人“思考的过程，而不是让人看了之后觉得”这怎么可能想到呢？</p>
<p>以本文上篇提到的霍夫曼编码为例，第一遍看霍夫曼编码的时候是在本科，只看了算法描述，觉得挺直观的，过了两年，忘了，因为不知道为什么要把两个节点的频率加在一起看做单个节点——一件事情不知道“为什么”就会记不牢，知道了“为什么”的话便给这件事情提供了必然性。不知道“为什么”这件事情便可此可彼，<b>我们的大脑对于可此可彼的事情经常会弄混，它更容易记住有理有据的事情</b>（<em>从信息论的角度来说，一件必然的事情概率为1，信息量为0，而一件可此可彼的事情信息量则是大于0的</em>）。第二遍看是在工作之后，终于知道要看证明了，拿出著名的《Algorithms》来看，边看边点头，觉得讲得真好，一看就理解了为什么要那样来构造最优编码树。可是没多久，又给忘了！这次忘了倒不是忘了要把两个节点的频率加起来算一个，而是忘了为什么要这么做，因为当时没有弄清霍夫曼为什么能够想到为什么应该那样来构造最优编码树。结果<em>只知其一不知其二</em>。</p>
<p>必须说明的是，如果只关心算法的结论（即算法逻辑），那么理解算法的证明就够了，光背算法逻辑难记住，理解了证明会容易记忆得多。但如果也想不忘算法的证明，那么不仅要理解证明，还要理解证明背后的思维，也就是<em>为什么背后的为什么</em>。后者一般很难在书和资料上找到，唯有自己多加揣摩。为什么要费这个神？只要不会忘记结论不就结了吗？取决于你想做什么，如果你想真正弄清算法设计背后的思想，不去揣摩算法原作者是怎么想出来的是不行的。</p>
<p>回到霍夫曼编码问题，我们首先看一看《Algorithms》上是怎么讲的：</p>
<p>首先它给出了一棵编码树的cost function：</p>
<p>cost of tree = Σ freq(i) * depth(i)</p>
<p>这个cost function很直白，就是把编码的定义复述了一遍。但是接下来就说了：</p>
<p><em>There is another way to write this cost function that is very helpful.</em> Although we are only given frequencies for the leaves, we can define the frequency of any internal node to be the sum of the frequencies of its descendant leaves; this is, after all, the number of times the internal node is visited during encoding or decoding&#8230;</p>
<p>接着就按照这个思路把cost function转换了一下：</p>
<p>The cost of a tree is the sum of the frequencies of all leaves and internal nodes, except the root.</p>
<p>然后就开始得出算法逻辑了：</p>
<p>The <em>first formulation</em> of the cost function tells us that the <em>two symbols with the smallest frequencies must be at the bottom of the optimal tree</em>, as children of the lowest internal node (this internal node has two children since the tree is full). Otherwise, swapping these two symbols with whatever is lowest in the tree would improve the encoding.</p>
<p>This suggests that we start constructing the tree greedily: find the two symbols with the smallest frequencies, say i and j, and make them children of a new node, which then has frequency fi + fj. To keep the notation simple, let&#8217;s just assume these are f1 and f2. By the <em>second formulation</em> of the cost function, any tree in which f1 and f2 are sibling-leaves has cost f1 + f2 plus the cost for a tree with n &#8211; 1 leaves of frequencies (f1 + f2), f3, f4, .., fn. <em>The latter problem is just a smaller version of the one we started with</em>.</p>
<p>读到这里我想大多数人有两种反应：</p>
<ol>
<li>觉得理所当然。 </li>
<li>觉得恍然大悟。 </li>
</ol>
<p>因为我当时也是这么觉得的。可是后来当我发现自己无法从头证明一遍的时候，我知道肯定是理解的不够深刻。如果理解的够深刻，那么基本上是不会忘掉的。</p>
<p>如果看完霍夫曼编码这样一个简短证明你觉得顺理成章，一切都挺显然，<em>那就坏了</em>，即便是看上去最基本的性质也往往实际上没那么显然。“逢山开路，遇水架桥”在我们今天看来是无比显然的事实，但是试想在没有桥的远古时代，一个原始人走到一条湍急的河流前，他会怎么想，他又能有什么法子呢？这是个他从来没有遇见过的问题。如果后来有一天，他路过另外一条小溪，看到小溪上有一截被闪电劈断的枯树，于是他踏着树干走过了小溪，并意识到“树横过河面”可以达到“过河”这个目的，这就将条件和目的建立了直接的联系（事实上，是自然界展示了这个联系，我们的原始人只是记住了这个联系）。后来他又路过那条河流，他寻思如何达到“过河”这个目的的时候，忽然意识到在他的记忆中已经遇到过需要达成同样目的的时候了，那个时候的条件是“树横过河面”，于是问题便归结为如何满足这个“树横过河面”的条件，而后一个问题就简单多了。（事实上波利亚在他的著作《How to Solve it》中举的正是这么个例子）</p>
<p>为什么那么多的算法书，就看不到有一本讲得好的？因为我们求解问题过程中的思维步骤太容易被自己当作“显然”的了，但除了我们天生就会的认知模式（联系，类比），没有什么是应该觉得显然的，试错是我们天生就会的思维法则么？是的，但是可供尝试的方案究竟又是怎么来的呢？就拿上面的例子来说，一个从没有见过枯树干架在小溪上的原始人，怎么知道用树架桥是一种可选的方案呢？俗话说巧妇难为无米之炊啊。我们大脑的神经系统会的是将目的和条件联系起来，第一次原始人遇到小溪过不去，大脑中留下了一个未实现的目的，后来见到小溪上的树干，忽然意识到树干是实现这个目的的条件，两者便联系起来了，因此问题就规约为如何架树干了。</p>
<p>回到《Algorithms》中的证明上，这个看似简洁明了的证明其实有几处非常不显然的地方，甚至不严谨的地方，这些地方也正是你过段时间之后试图自己证明的话会发现卡住的地方：</p>
<ol>
<li>作者轻飘飘地就给出了cost function的另外一种关键的描述，而对于如何发现这种描述却只是一语带过：&quot;<em>There is another way to write this cost function that is very helpful.. </em>we can define the frequency of any internal node to be the sum of the frequencies of its descendant leaves“这其实就是我常常痛恨的“<em>我们考虑</em>&#8230;”，这里作者其实就是在说”让<em>我们考虑</em>下面这样一种<em>奇妙</em>的转换“，可是怎么来的却不说。但必须承认，《Algorithms》的作者还是算厚道的，因为后面他又稍微解释了一下：“this is, after all, the number of times the internal node is visited during encoding or decoding&#8230;”这个解释就有点让人恍然大悟了，但是千万别忘了，这种恍然大悟是一种错觉，你还是没明白为什么他会想到这一点。这就像是作者对你说“<em>仔细观察</em>问题条件，我们<em>容易发现</em>这样一种奇妙的性质..”，怎么个“仔细”法？凭什么我自己“观察”半天就是发现不了呢？霍夫曼本人难道也是死死盯着问题<a href="http://en.wikipedia.org/wiki/Huffman_tree#History">“观察”了一学期</a>然后就“发现”了么？我们有理由相信霍夫曼肯定尝试了各种各样的方法，作出了各种各样的努力，否则当年Shannon都没搞定的这个问题花了他一学期，难道他在这个学期里面大脑就一片空白（或者所有的尝试全都是完全不相干的徒劳），然后到学期末尾忽然“灵光一现”吗？ </li>
<li>如果“仔细观察”:)，我们会发现两个cost function表达中frequency的概念有微妙的差异，在第一个cost function中，只有叶子节点有frequency，而这个frequency必须和叶子节点的深度相乘。而在第二个cost function中，内部节点也具有了frequency，可是所有节点的“frequency”忽然全都不跟深度相乘了。frequency的不同含义令人困惑。 </li>
<li>作者提到：第一个cost function告诉我们频率最低的两个节点必然处于最优编码树的底端，作为最低内部节点的两个子节点。这是一个不严谨的说法，从前文给出的条件和性质，只能推导出编码树的最底层必然能找到频率最低的两个节点，但<em>它们未必一定要是兄弟节点</em>，如果树的最底层不止能容纳两个节点的话它们就可以有不同的父节点。“我们不妨考虑”这样一个例子：对A,B,C,D四个字母进行编码，假设它们的频率分别是1， 1， 2， 2。这个时候我们可以构造如下图所示的两棵树，两棵树的cost都是12，都是最优的。但其中一棵树中，两个频率最低的节点并非兄弟。       <br /><a href="http://mindhacks.cn/wp-content/uploads/2011/07/tree2.jpg"><img style="background-image: none; border-right-width: 0px; margin: 5px 0px 10px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="tree2" border="0" alt="tree2" src="http://mindhacks.cn/wp-content/uploads/2011/07/tree2_thumb.jpg" width="434" height="246" /></a> </li>
</ol>
<p>为什么要提到上面这几点不显然和不严谨的地方，因为只要当你看到算法书上出现不显然和不严谨的地方，基本上就意味着作者其实跳过了关键的思维步骤。</p>
<p>不幸的是《Algorithms》这本书里面讲霍夫曼编码已经算是讲的好的了，如果你翻开著名的CLRS，看一看当中是怎么证明的，你就知道我说的什么意思了。有时候这些证明是如此的企图追求formal和严谨，一上来就定义符号一大摞，让人看了就想吐。</p>
<p>说了这么多，<em>有没有可能把霍夫曼编码讲的更好呢</em>？前面说过，霍夫曼编码我记了又忘，忘了又记，好几次了，有一次终于烦了，心想如果要自己去证明，会怎么去证，那个时候我已经忘了《Algorithms》里面怎么讲的了。所以我得从头来起，首先，对于算法问题，有一个一般性原则是，<strong>先看一看解空间的构成</strong>。尤其是对于搜索问题（最优化问题可以看做搜索问题的一个特例），这一点尤其重要。霍夫曼编码的可能的编码树是有穷的，如果穷举所有的编码树，然后找到那棵代价最小的，这种方法至少是可行的，有了可行的方法（即便是穷举）至少让我们内心感到踏实。</p>
<p>接下来便是提高搜寻效率的问题。而提高搜寻效率的关键（同样也是一个一般性原则），便是<strong>尽量去寻找问题条件能够推导出来的性质，</strong><strong>然后利用这些性质去避免不必要的搜寻</strong>，只要你学过二分搜索就应该理解这个一般性原则：二分搜索的效率之所以高于“穷搜”（O(n)），便是因为它利用了问题中的性质（有序）来避免了不必要的搜寻。有时候这个性质甚至可以直接将时间降为O(1)，例如在一个有序数组中寻找出现次数大于n/2的数（假设该数存在），利用“该数一定出现在数组正中间”这个性质，我们直接就避免了所有的计算。</p>
<p>不过，话虽如此，有时候这些性质并不是那么“显然”的，需要对问题进行深入的折腾才能有可能发现。第三个一般原则：如果你要搜寻的元素是某个满足特定条件的元素（例如寻找最优解的时候，“最优”的定义就是这个“特定条件”），那么可以“<strong>倒过来推</strong>”（数学证明常用手法，结论当条件使），即假设你已经找到了你要找的元素，那么能得出哪些结论，每一个结论都是最优解的一个必要条件，而<em>每一个必要条件都能够帮助你避免不必要的搜寻</em>，因为你<em>只要发现某个候选解不满足某个必要条件，就可以立即将其丢弃</em>，前面提到的寻找出现次数大于n/2的例子是一个极端情况，我们得出的必要条件导致我们可以直接丢弃除中点元素之外的一切其他元素，再例如如果有人叫你寻找有序数组中最小元素，你会毫不犹豫地把该数组头尾元素中较小的那个给他，因为你知道“如果那个最小元素存在，那么它<em>必然</em>位于头尾”——这个必要条件直接允许你丢弃掉n-2个候选解。</p>
<p>回到霍夫曼编码问题，按照这个原则，我们会去<em>假设已经得到了最优编码树</em>，那么我们能够发现关于它的什么性质呢？这里又要提到另一个适用于很多最优化问题的原则（前面提到的原则适用于一般性搜索问题），<strong>所谓最优解，就是说比其他所有解都要更好</strong>，虽然这句话听上去像是废话，但是它的一个直接推论——<strong>比与它邻近的所有候选解都要好</strong>——就是一个非常有用的，不是废话的性质了。学过微积分的都知道，光滑函数的最值点必然是大（小）于其邻域内的所有点的，然后再根据这个就自然推出该点的一阶导数（切线斜率）必然为0的性质，这个性质（必要条件）让我们直接省掉了去整个区间内搜索的麻烦，从而可以直接锁定有限几个候选解。那么，既然我们说最优霍夫曼树一定比它“附近”的树更好，我们就想看看，怎么来找到它附近的树。我们知道要从一个点到它附近，往往是对这个点进行一些调整，例如N+1是到达附近的另一个整数。霍夫曼树是一棵树，所以对这棵树的所有的一次“改动”（或“折腾”）都能够到达与它的“改动”距离为1的点（是不是想起“编辑距离”这个概念），怎么改动呢？最符合直觉的（虽然并不是唯一的）改动便是把叶子节点进行互换。</p>
<p>于是我们得到一个重要的推论：</p>
<ul>
<li><em>在最优霍夫曼树中，无论互换哪两个叶子节点，得到的树都变得更“差”。（严格来说是不会变得更“好”，因为最优树未必唯一）</em> </li>
</ul>
<p>这个性质看上去有点像废话，值得费这么多事么？值得。因为虽然前文说了很多，但都是大多数人大脑里面既有的，一般性的法则，前面说过，如果我们能够从我们已经掌握的一般法则出发来推导出问题的解，那么记忆负担是最小的，因为这里面用到的所有法则我们都很清楚，也知道怎么一步步往下走。</p>
<p>上面这个性质究竟意味着什么呢？如果你假设这两个叶子节点的频率为f1和f2，深度为d1和d2，互换它们的时候，其他叶子节点的cost保持不变，令为常量C，那么互换前总cost为C+f1d1+f2d2，互换后为C+f1d2+f2d1，既然互换之后的树一定更”差“那么就是说f1d1+f2d2 &lt; f1d2 + f2d1，简单变换一下就得到结论：f1(d1-d2)&lt;f2(d1-d2)，也就是说如果d1&lt;d2，那么f1必然&gt;f2，如果d1&gt;d2，那么f1必然&lt;f2。换言之就是<em>叶子节点的深度越高，频率必须越低</em>，否则就不可能是最优霍夫曼树。那么，之前我们觉得不那么显然的结论便呼之欲出了：频率最低的叶子节点必然位于树的最底层，频率最高的叶子节点必然位于树的最高层。</p>
<p>有了这个结论之后，我们便能够对最优霍夫曼树的构建走出确定性的一步，即，将频率最低的两个叶子节点放在最底层。别小看这一步，这一步已经排除了大量的可能性。这里，我们容易一开始天真地觉得最底层只有这两个叶子节点，于是它们拥有共同父节点，<em>这样一来霍夫曼树的整个拼图便已经拼好了一个小小的角落</em>。</p>
<p>然后我们会发现，要是它们不是兄弟怎么办呢？这里提到另一个一般原则——<strong>归约</strong>。不是兄弟的情况能否归约为是兄弟的情况？反正我们要求的是<em>一个</em>最优解，而不是<em>所有的</em>最优解，我们只需证明，如果当这两个最低频率的叶子不是兄弟的时候的确存在着某棵最优霍夫曼树，那么通过交换它们各自的兄弟，从而让这两个叶子团聚之后，修改后的树仍然是最优的就可以了。事实情况也的确如此，证明非常直接——既然这里涉及到的所有4个节点都在最底层同一个高度上，那么互相交换的时候不会改变他们任何一个人的深度值，所以总cost不会改变。</p>
<p>但是接下来我们犯了难，整个树的一个小小的樱桃状的局部是确定下来了，接下来怎么办呢？一个最自然的思路就是考虑第三小的叶子，因为前面说了，元素频率越低就越位于树的底部嘛。第三小的叶子有两种可能的归属，一是跟最小的两个叶子同样位于最底层（这不会违反我们前面得到的推论），这个时候第三小的叶子的兄弟叶子肯定是第四小的叶子，如下图：</p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2011/07/tree3.jpg"><img style="background-image: none; border-right-width: 0px; margin: 5px 0px 10px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="tree3" border="0" alt="tree3" src="http://mindhacks.cn/wp-content/uploads/2011/07/tree3_thumb.jpg" width="244" height="106" /></a></p>
<p>另一种归属就是往上一层去（注意，一旦第三小的叶子往上去了一层，那么剩下的所有叶子都必须至少在这个层以上），往上一层去了之后，它的兄弟是谁呢？不妨将它和刚才第一第二叶子的父节点结为兄弟（前面证明过，同层之前节点互换不会改变编码的cost），如下图：</p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2011/07/tree5.jpg"><img style="background-image: none; border-right-width: 0px; margin: 5px 0px 10px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="tree5" border="0" alt="tree5" src="http://mindhacks.cn/wp-content/uploads/2011/07/tree5_thumb.jpg" width="181" height="244" /></a></p>
<p>可是现在问题出现了：虽然第一步构建（最小的两个叶子）是确定的，但是到了第二步摆在我们面前的就有两个选择了，到底选择哪个呢？一个办法就是把两种选择都记下来，然后继续往下走。可是别小看两种选择，接下去每一步都有两种选择的话就变成指数复杂度了。所以现在我们便有了动机<em>回头看一看</em>，<em>看问题中是否有什么没有发现的性质能够帮助我们再排除掉其中一个选择</em>。理想情况下如果每一步都是必然的，确定的，那么N步我们就可以构建出整棵树，这是我们希望看到的，抱着这个良好的愿望，我们仔细观察上面两种构型，一个自然而然的问题是：这两种构型都有潜质成为最优解吗？如果我们能够证明其中一种构型不能成为最优解那该多好？就省事多了嘛。这里引入另一个一般性的解题法则：<strong>特例</strong>。我们的<strong>大脑喜欢具体的东西，在特例中折腾和观察会方便的多</strong>。</p>
<p>上面这个{1, 2, 3, 4}的例子就是个很好的特例，如图（注：图中节点旁的数字一概为<em>频率值，</em>而非编号）：</p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2011/07/tree31.jpg"><img style="background-image: none; border-right-width: 0px; margin: 5px 0px 10px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="tree3" border="0" alt="tree3" src="http://mindhacks.cn/wp-content/uploads/2011/07/tree3_thumb1.jpg" width="277" height="126" /></a></p>
<p>多加折腾一番<em>也许</em>我们不难发现，如果将1，2及其父节点跟叶子4进行交换（注意：<em>交换的时候1，2也被一同带走了</em>，因为反正1，2两个节点已确定是好兄弟永远不会分家了，折腾的时候只能作为一个整体移动，所以这里也可以说是<strong>交换子树</strong>），那么树的编码将会变得更优，因为这样一次交换会将1和2的深度+1，意味着整棵树的代价+3，而同时会将叶子4的深度-1，也就是说整棵树的代价-4，总体上整棵树的代价就是+3-4=-1（注意，在计算的时候我们只需考虑被交换的局部，因为树的其他部分的代价保持不变）。如下图：</p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2011/07/tree4.jpg"><img style="background-image: none; border-right-width: 0px; margin: 5px 0px 10px; padding-left: 0px; padding-right: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px; padding-top: 0px" title="tree4" border="0" alt="tree4" src="http://mindhacks.cn/wp-content/uploads/2011/07/tree4_thumb.jpg" width="244" height="227" /></a></p>
<p>这个交换启发了我们，其实前面一开始说的交换两个叶子节点<em>可以推广为交换内部节点和叶子节点</em>，然后很快我们就会意识到其实可以推广到交换任意两个节点。（注意，<em>当我们说交换内部节点的时候，其实是连同该内部节点作为局部根节点的整个子树都交换过去</em>）于是前面我们的推论就可以推广为：</p>
<ul>
<li><em>在最优霍夫曼树中，无论互换哪两个节点，得到的树都变得更“差”（交换内部节点则是连同该内部节点作为局部根的子树一同带走）</em> </li>
</ul>
<p>这个推论很容易理解，只不过是多增加了一种“编辑”最优霍夫曼树的方法罢了（记住<em>最优霍夫曼树无论怎么“编辑”都不会变得更“好”，包括“交换子树”这种“编辑”</em>），我们前面没有想到这种“编辑”方法是因为它不那么显然，而且当时我们已经想到一种最直接的“编辑”方法了，即交换叶子，就容易顺着那个思路一直走下去，直到我们发现必须寻找新的性质，才回过头来看看有没有其他法子。</p>
<p>当然，并不排除一开始就想到这种推广的可能性，问题求解的过程并不是这么线性的，如果我们习惯了推而广之的思维，也许一下就能想到这个推广来。类似的，也不排除从另一种思路出发想到这种推广的可能性。所以这里只是可能的思维轨迹中的一种，重点在于其中并没有某处忽然出现一个不知从哪里冒出来的，神启一般的结论。</p>
<p>刚才提到，构造最优树的第二步是考虑第三小的叶子，但也有另一种常见的思维：考虑到第一步（即选取频率最小的两个叶子）所做的事情是从N个叶子中选择两个黏在一起作为兄弟，那么也许对于一些人来说自然而然的第二步就是试图继续选取两个节点黏在一起作为兄弟（注意这里不仅可以选择叶子，也可以选择已经生成的内部节点），然后依次类推来拼完整棵树。按照这一思路，第二步的选项仍然还是集中在第三小的叶子上，因为这个选择要么是让第三第四小的叶子结拜为兄弟，要么是让最小两个叶子的父节点和第三小的叶子结拜。</p>
<p>回到刚才我们的推论：在最优霍夫曼树中，无论互换哪两个节点，得到的树都变得更“差”（交换内部节点则是连同该内部节点作为局部根的子树一同带走） 。根据这个推论我们容易计算出，<em>在最优霍夫曼树当中，两个内部节点n1和n2，如果n1比n2更深，那么n1下面的所有叶子的频率之和必然要小于n2下面所有叶子的频率之和</em>。如果交换的是一个内部节点和一个叶子节点，则道理是类似的。这个性质的证明和上面的类似，就不赘述了。</p>
<p>这个性质暗示了一个重要的推广结论：<em>如果我们把每个内部节点的所有叶子的频率之和标在它旁边，那么整棵树的每个节点便都有了一个数值，这个数值遵循统一的规律：即越往深层越小</em>。这就意味着，我们刚才的二选一困境有办法了！当我们将最小的两个叶子f1和f2合并的时候，生成了一个新的节点M，这个节点有一个数字（为两个叶子的频率之和f1+f2），根据上面的推论，<em>这个数字f1+f2跟所有频率一同，遵循最小的在最底层的原则</em>，所以我们下一步必须在剩下的那些互相之间关系待确定的节点（叶子节点和内部节点）之中，即{(f1 + f2), f3, f4}里面选择最小的两个数字结合成兄弟（由于f1和f2这两个节点已经铁板钉钉结为整体了，所以从集合里面可以看做移除）。到这里，我们就发现递归已经出现了，接下去的过程对于绝大多数人应该就真的很显然了。</p>
<p>以上的解释，比《Algorithms》更简短吗？显然不是。反而要长得多（其实真正的思维过程比这要更长，因为中间还会涉及各种不成功的尝试）。但是它比《Algorithms》当中的版本更不容易被忘记，因为其中关键的思维拐点并不是毫无来由的，而是从你已经熟知的，或者说虽然不知道，但容易理解的一般性解题法则出发自然推导出来的，所以你基本上不需要记忆什么东西，因为你需要记的已经在你脑海中了。</p>
<p>在上面的证明过程中，还有一个不像看上去那么显然的事情：在我们寻找最优霍夫曼树的时候，我们曾经试图去比较假想的最优树和它的“临近”的树，从而去探索最优树的性质。但是，究竟什么是临近的树？在前面的讲解中，我们说如果交换A和B这两个叶子节点，便得到一颗不同的树，可以看做和原树的“编辑距离”为1的树。但是，真的这么显然么？难道除了交换叶子的位置，就没有其他办法去“折腾”这棵树了？后来我们看到，可以<em>交换子树</em>而不仅仅是叶子，而交换子树让我们得到了至关重要的推论。此外，如果不是交换，而是像AVL树那样“旋转”呢？说到底，二叉树是一个离散的东西，并不像连续值那样，天生就有“距离”这个概念，如果我们离散而孤立地去看待所有的树，那么没有什么临近不临近的，临近本是一个距离的概念，除非我们定义树和树之间的距离函数，才能说临近与否，而距离函数怎么定义才是“显然”的呢？</p>
<p>还有，其实以上只是试图给出最优霍夫曼树的<em>证明</em>的一个更自然的过程，而<em>当年霍夫曼面临这个问题的时候根本还没有人想到要用二叉树呢！更不要说在二叉树的前提之下进行证明了</em>。根据wikipedia的介绍，霍夫曼同学（当年还在读Ph.D，所以的确是“同学”，而这个问题是坑爹的导师Robert M. Fano<a href="http://www.huffmancoding.com/my-family/my-uncle">给他们作为大作业的</a>，Fano自己和Shannon合作给出了一个suboptimal的编码方案，为得不到optimal的方案而寝食难安，情急之下便死马当活马医扔给他的学生们了）当年为这个问题憔悴了一个学期，最后就快到deadline的时候“忽然”想到二叉树这个等价模型，然后在这个模型下三下五除二就搞定了一篇流芳千古的论文，超越了其导师。</p>
<p>最后说两个有趣的现象：也许很多人会觉得，越是大师来写入门教科书越是好，其实很多时候并非如此，尤其是在算法设计和数学领域，往往越是在其中浸淫久了越是难写出贴近初学者的书，因为大量对初学者来说一点都不显然的事情在他看来已经是“不假思索”了，成了他的内隐记忆，尤其是当他想要和你解释一个复杂的东西的时候你就会发现他会常常逻辑跳跃，满嘴跑术语，根本没有意识到别人对有些术语和隐含的逻辑根本没有像他那样的理解。</p>
<p><strong>最适合将一个东西讲给别人听的时候并不是等懂了很多年以后，而是刚刚弄懂的时候</strong>，这个时候从不懂到懂的差别记忆还非常鲜明，能够清清楚楚地记得到底是哪些关键的地方是最折磨人的，也最能够站在不懂者的角度来思考问题。像波利亚这样，成了大师还能够站在不懂者角度去换位思考的，可以说是凤毛麟角。所以说前Amazon CAO（首席算法官）的<a href="http://www.amazon.com/Introduction-Algorithms-Creative-Udi-Manber/dp/0201120372/">《Introduction to Algorithms: a Creative Approach》</a>绝对是本罕见的好算法书）</p>
<p><a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/">知其所以然（一）</a>里面曾经提到，要弄清来龙去脉，最好去看看原始作者是怎么想的，可是正如上文所说，即便是最初的发明者，在讲述的时候也会有意无意地“线性化”，我就去查看了霍夫曼最初的论文，那叫一个费解，不信你可以自己看看(<a href="http://compression.ru/download/articles/huff/huffman_1952_minimum-redundancy-codes.pdf">PDF</a>)。</p>
<p>可以归约为搜索算法的问题（非常多）一般来说相对还是有一些头绪的，因为搜索空间一般还比较容易界定，难点在于要从问题的条件中推导出用于节省搜索的性质。而策略设计问题则完全是另一个世界，因为策略的设计空间貌似是可列无穷的，常常让人感觉无从下手，摸不着头绪，许多让人挠头的智力问题就有这个特点（例如著名的100个囚徒和1个灯泡的房间就让很多人有这种感觉），策略设计问题也有一些较通用的法则，以后再说。</p>
<p>怎么才能在学算法的时候学到背后的东西呢？有以下几点很重要：</p>
<ol>
<li><strong>不要觉得每个步骤都很显然</strong>，每个nontrivial的算法背后都有一段艰辛的探索经历，觉得显然的话必然是一种幻觉。<strong>Stay foolish</strong>，才能发现某些环节其实并不是那么显然的。 </li>
<li>检验是否真正理解的最佳方法就是<strong>过一段时间之后，自己试着证明一次</strong>。如果真正理解了的话，你的证明便会比较顺畅。如果当时没有真正理解，那么凡是那些你当时觉得显然但其实不显然的地方，都会成为你证明里面缺失的环节。 </li>
<li>对于一个算法，<strong>多寻找各种来源的资料</strong>，也许能够找到一个讲的比较深刻的。我在<a href="http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/">《数学之美番外篇：快排为什么那么快》</a>和<a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/">《知其所以然（一）》</a>里面都举到了这样的例子。 </li>
<li><strong>多试着去抽象背后的一般性法则</strong>，即便后来发现抽象得是错的，也比不去抽象要好。<a href="http://mindhacks.cn/2009/03/28/effective-learning-and-memorization/">抽象是推广的基础</a>。只有抽象出更深层的法则，才能让你事半功倍，触类旁通，否则一个萝卜永远是一个坑。（注意，其实我们的下意识是会进行一定程度的抽象的，例如前面提到的原始人的例子，小溪和小河（或者小沟）细节上是不同的，但本质上是一样的，我们的大脑会自动进行这种简单抽象，提出事物的共性。正因此，即便你不去有意识地总结一般规律，只要你看的足够多，练的足够多，必然就会越来越谙熟。） </li>
</ol>
<p>最后留个问题：虽然按照上文的方式来构造霍夫曼树一定能够得到<em>一个</em>最优树，但是怎么证明一定能得到呢？乍一看这个问题似乎很多余，因为证明很简单：我们拼装整棵树的每一步都没得选，而且每一步都必然拼凑出最优树的一个小小局部，如果最终还没有得到最优树的话，只能说最优树是不存在的了，然而最优树是一定存在的，因为所有树的集合是有穷的，有穷集必有最值，因此证毕。这个证明固然是没问题的，但它其实是一个间接证明，换句话说，我们在构建树的过程中的逻辑是这样的：“之所以我们选择粘结n1和n2，是因为其他粘法必然违反最优树的两个性质。所以我们别无选择。”但是，这并没有说，我们选择了粘结n1和n2，一定就<em>符合</em>了最优树的性质。（也就是说“其他做法都是错”并不能推出“这种做法必然对”，这就像是你在一大堆豆子当中寻找一个特殊的豆子，你拿起一个，看看不是，扔掉，又拿起一个，还不是，扔掉，排除到最后只剩一个豆子了，假设你又知道这个特殊的豆子必然存在，那么这个时候你根本不用看就知道这个豆子一定就是你要找的）那么，你能否直接证明，拼装最优树的过程每一步都<em>符合</em>最优树的性质呢？</p>
<hr />
<p>P.S.</p>
<p>[1] 《逃出你的肖申克》和《BetterExplained》是我喜欢的两个系列，还会继续写，我有很多问题，也在Evernote里面记了不少零碎的思考和资料，但只有当我觉得理解的足够深入，系统，以及手头有足够的有意思和有说服力的例子的时候，我才会把整条线串起来成文，所以这事慢慢来不着急，反正这个博客也不会关掉。</p>
<p>[2] 工作之后可用业余时间急剧减少，已经陆续基本把GReader砍掉了，时间再往前推，砍掉邮件列表，再往前是Twitter，再往前是BBS。现在基本就只剩邮件了。越来越发现当时间有限的时候，看书比看网要有效得多，也不会那么信息焦虑，网络上的那些消息当中真正重要的会自己来找你，不用每天去刷屏。不过有个例外，我过一阵子就会去逛一下Amazon的个性化推荐项目。如果你已经工作，苦于时间有限，我建议你这么做。最近看过的几本值得好好推荐的书有：《Number Sense》，《Reading in the Brain》，《The Vision Revolution》，《The Tell-Tale Brain》，《Kluge》。</p>
<p>[3] 顺便吐槽国内出版社引进Pop Science类书籍的效率和质量，就我观察，台湾引进Pop Science类书籍需要延迟两年左右，大陆则从三四年到无限期不等（某种程度上，一个国家的出版方的认识水平，决定了这个国家大众的认识水平。你去看下我在豆瓣的书单就知道有多少好书与国内读者失之交臂了），例如《Number Sense》这本好书，到现在还没有引进，99年出版的书啊。《Kluge》更是译为《乱乱脑》这种坑爹的书名，封面搞得跟少儿读物一样。《Reading in the Brain》引入的算较快的，但也延迟了一年半了，而且翻译质量也不是很上乘（算是不功不过吧），说到这里要赞中信出版社，最近一年引入了很多给力的Pop Science畅销书，眼光还算不错。最近在Amazon上搜一些好的发展心理学的书，通过Amazon的推荐引擎看到了<a href="http://www.amazon.com/Pink-Brain-Blue-Differences-Troublesome/dp/0618393110">《Pink Brain，Blue Brain》</a>，这本受到因研究大脑记忆的分子机制而获诺奖的Eric Kandel盛赞的科普09年就出了，到现在国内影子都见不着，还好在卓越上买到了原版。虽然基本还没开始看，但可以郑重推荐给初为父母的同学们:)</p>
<h3  class="related_post_title">你可能也会喜欢以下文章</h3><ul class="related_post"><li><a href="http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/" title="知其所以然（续）">知其所以然（续）</a> (42)</li><li><a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/" title="知其所以然（以算法学习为例）">知其所以然（以算法学习为例）</a> (36)</li><li><a href="http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/" title="数学之美番外篇：快排为什么那样快">数学之美番外篇：快排为什么那样快</a> (25)</li><li><a href="http://mindhacks.cn/2008/04/18/learning-from-polya/" title="跟波利亚学解题(rev#3)">跟波利亚学解题(rev#3)</a> (23)</li><li><a href="http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/" title="数学之美番外篇：进化论中的概率论">数学之美番外篇：进化论中的概率论</a> (13)</li></ul><hr />
<h3>订阅 Mind Hacks</h3>
<a title="用Google Reader订阅" href="http://fusion.google.com/add?feedurl=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_google.gif" style="border:0" alt="订阅到 | Google" /></a> 
<a title="用鲜果订阅" href="http://www.xianguo.com/subscribe.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_xianguo.gif" style="border:0" alt="订阅到 | 鲜果" /></a>
<br/> 
<a title="用抓虾订阅" href="http://www.zhuaxia.com/add_channel.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_zhuaxia.gif" style="border:0" alt="订阅到 | 抓虾" /></a> 
<a title="用有道订阅" href="http://reader.youdao.com/b.do?keyfrom=mindhacks&url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_yodao1.gif" style="border:0" alt="订阅到 | 有道" /></a> 
<hr />
<h3>我是你的信息过滤器</h3>
想了解作者最近在关注什么，欢迎 Follow <a href="http://weibo.com/pongba">刘未鹏pongba@微博</a>
<br/>
程序员朋友请到作者发起的 <a href="https://groups.google.com/group/pongba">TopLanguage</a> (<a href="http://mindhacks.cn/about-toplanguage/">about</a>) 社群逛逛，定有收获 :)
<br/>
想了解作者在阅读哪些书，请到 <a href="http://www.douban.com/people/pongba/">pongba@豆瓣</a>，或者直接访问以下四个豆列：<a href="http://www.douban.com/doulist/46003/">[只读经典]思维改变生活</a> | <a href="http://www.douban.com/doulist/127649/">[只读经典]思考的技术与艺术</a> | <a href="http://www.douban.com/doulist/197706/">决策与判断</a> | <a href="http://www.douban.com/doulist/176513/">机器学习与人工智能书籍资源导引</a> 。
<hr />
<p><small>
本文由 刘未鹏 发布在 <a href="http://mindhacks.cn">刘未鹏 | Mind Hacks</a>, 2011. | <a href="http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/#commenting">57 条评论</a> | 标签: <a href="http://mindhacks.cn/tags/%e7%ae%97%e6%b3%95/" rel="tag">算法</a>
<br/>
转载请注明作者，出处，以及<a href="http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/">原始超链接</a>: http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/
</small></p>]]></content:encoded>
			<wfw:commentRss>http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/feed/</wfw:commentRss>
		<slash:comments>57</slash:comments>
		</item>
		<item>
		<title>知其所以然（续）</title>
		<link>http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/</link>
		<comments>http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/#comments</comments>
		<pubDate>Sun, 14 Nov 2010 09:41:00 +0000</pubDate>
		<dc:creator>刘未鹏</dc:creator>
				<category><![CDATA[学习方法]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://mindhacks.cn/2010/11/14/%e7%9f%a5%e5%85%b6%e6%89%80%e4%bb%a5%e7%84%b6%ef%bc%88%e7%bb%ad%ef%bc%89/</guid>
		<description><![CDATA[我有一个习惯，看定理必看证明。一个你不明白其证明的定理在我看来比不知道这个定理还要糟糕，因它给你造成一种懂了的错觉。在没有明白背后的证明之前，任何一个定理对你来说都是等价的——等价于背乘法口诀（只不过有的长一点有的短一点）。一个原本美妙的定理，把其证明扔掉就是真正的买椟还珠，暴殄天物。 

从现实意义来说，去理解一个定理的证明会带来巨大的好处，首当其冲的好处就是你很难再忘掉它。这一点其实很容易解释——在理解一个定理的证明之前，定理对你而言是一堆没有内在联系的词句，而在理解了证明之后，定理就归约为证明它所需的条件加上逻辑...这是一个树状的知识结构，越往上层走，需要记忆的节点就越少。]]></description>
			<content:encoded><![CDATA[<p>查了一下，上篇<a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/">知其所以然（以学习算法为例）</a>是08年7月写的，现在已经是10年11月，过去了两年零4个月，这说明了三件事情：1，一个问题其实你可以一直放在脑子里面，利用<a href="http://mindhacks.cn/2009/12/20/dark-time/">暗时间</a>对其软泡硬磨，时间足够久你总会有一点新的感悟，问题其实就像那句老话说的那样，不怕贼偷就怕贼惦记，聚精会神的思考一天，也许比不上惦记一个星期（据说<a href="http://blog.csdn.net/pongba/archive/2008/07/24/2705151.aspx">数学家庞加莱就特别会惦记问题</a>）。2，事实上，当你感觉懂了的时候，你至少得反问自己一句，真的懂了吗？当你确信自己真的懂了的时候，你至少得讲给别人听，别人听懂了吗？考察你自己是否真懂了的一个很好的依据是，你是否有一种“哦，原来是这样啊，这下再也不可能忘记了”的感觉。3，我其实没有忘记这个博客。如我之前说的，<a href="http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/">记录只是学习和思考的副作用</a>，只要还在学习和思考，就必然会有新的记录。 </p>
<p>我有一个习惯，看定理必看证明。一个你不明白其证明的定理在我看来比不知道这个定理还要糟糕，因它给你造成一种懂了的错觉。<strong>在没有明白背后的证明之前，任何一个定理对你来说都是等价的——等价于背乘法口诀</strong>（只不过有的长一点有的短一点）。一个原本美妙的定理，把其证明扔掉就是真正的买椟还珠，暴殄天物。 </p>
<p>从现实意义来说，<strong>去理解一个定理的证明会带来巨大的好处，首当其冲的好处就是你很难再忘掉它</strong>。这一点其实很容易解释——在理解一个定理的证明之前，定理对你而言是一堆没有内在联系的词句，而在理解了证明之后，定理就<strong>归约为</strong>证明它所需的条件加上逻辑，“逻辑”本来就存在于你的大脑里面，而证明的过程中除了公理和用到的常见定理（往往没几条）之外，宽泛地说，需要你去记的，一般来说也只有一个或两个关键的insights，也就是我们常说的证明中的神来之笔，比如几何证明里面的某条看上去莫名其妙的辅助线，一旦你知道了这条辅助线，那么整个证明就毫无难处，那么该定理的信息量便直接缩减为一条辅助线的信息量；虽然看上去这一步信息并没有缩减多少，但是如果你考虑到类似的辅助线不仅会用在这个特定的定理上，往往会在很多地方用到。很多关键的证明手法是通用的。那么其实你就是把所有以这个辅助线为关键证明手法的定理的集合的信息量归约为了这条辅助线。如果你进而甚至能够理解了作这条辅助线的思想精髓，那就更牛逼了，因为解决问题的思路更具有一般性，理解了寻找正确的辅助线的思路，你就根本不需要去记得某条特定辅助线的作法，你就把所有以作一条或几条辅助线为证明核心的定理的集合的信息量归约为了这个“寻找辅助线的思路”。 </p>
<p><strong>这是一个树状的知识结构，越往上层走，需要记忆的节点就越少</strong>。所谓触类旁通者，其实便是因为他擅长去理解解法背后的更具一般性的东西。所以我还有一个习惯，就是看到美妙的证明和解法总是会去一遍又一遍的去反复揣摩，试图理解想出这个证明的人到底是怎么想出来的，有没有什么一般性的方法可循，很多时候，在这样揣摩的过程中，你会理解到更深刻的东西，对问题性质更深刻的认识，对解决问题的思路更深刻的认识，这些认识不仅对于你理解当前这个定理或问题有极大的帮助，同时也有助于你解决以后会遇到的表面不同但本质一样的问题。 </p>
<p>与看定理必看证明类似，看一个问题的解法，必然要看解法所诞生的过程，背后是否隐藏着更具一般性的解决问题的思路和原则。否则一个解法就只是一个问题的解法，跟背口诀一样。即便记住了也无法推广，即便当时记住了也容易遗忘。 </p>
<p>举个经典的例子：每本算法书都会讲动态规划，每本讲动态规划的书都会讲背包问题，每次讲背包问题都会讲可重复背包和01背包，我们就拿《Algorithms》这本<a href="http://book.douban.com/review/1325850/">还算不错</a>的算法书对背包问题的讲解来说吧，重复背包问题的递归公式是这样的： </p>
<p>K(W) = max { K(W-Wi) + Vi : Wi &lt;= W } </p>
<p>这个公式的理解倒是很简单：为了把问题降阶，我们在最终的最优解里面去掉一个元素，对这个元素的可能性进行讨论，它必然是任何Vi之一（前提是Wi &lt;= W，否则就装不下），而在去掉这个元素之后，剩下的元素肯定构成问题 K(W-Wi) 的最优解，于是递归关系出现了。 </p>
<p>此外也可以这样来理解：要拿一组最优元素，那么总得开始一个个拿吧，对第一个拿的元素进行讨论，而问题的最优解等于讨论的各个分支的最优解中的最优者；如果拿掉Vi之后，剩下来要怎么拿才能最优呢？这就是一个 K(W-Wi) 的问题了。 </p>
<p>01背包问题就大不一样了——每个物品都只有一件，拿掉之后就不能再拿了。我们不妨看看重复背包问题的解法是不是能用到01背包上呢？还是讨论第一个拿的元素，设被拿掉的是第i个元素，问题就归结为把剩下的物品（注意，可拿的物品少了一件）最优地装入容量为 W-Wi 的包里，所以，问题的参数便变成了两个，一个是背包剩余容量 W-Wi，另一个是剩余可拿的物品集合 S\{i} （表示去掉i之后的子集），显而易见第二个参数是物品集合的各种可能的子集，那么其可能性个数就是 2^n ，这就导致子问题的个数是 2^n， 由于要依次计算每个子问题，那么算法复杂度显然也是 2^n ，是不可接受的。 </p>
<p>那么，《Algorithms》上又是怎么来讲解01背包问题的解法的呢？以下是原文： </p>
<p>Our earlier subproblems now become completely useless. We must therefore refine our concept of a subproblem to carry additional information about the items being used. We add a second parameter, 0 &lt;= j &lt;= n: K(W, j) = maximum value achievable using a knapsack of capacity w and items 1..j: The answer we seek is K(W, n). </p>
<p>首先作者说了，之前重复背包问题的解法在这里完全废掉了，所以我们必须重新定义子问题，并且子问题的条件必须要包含目前拿剩下的物品。以上这些都还不错，关键是接下来就让人吐血了。作者接着说道，<strong>我们</strong>给子问题加上一个新的参数j&#8230; </p>
<p>凭什么啊？ </p>
<p>还是让我们回顾一下<a href="http://images.google.com/images?hl=en&amp;source=imghp&amp;biw=1024&amp;bih=620&amp;q=then+a+miracle+occurs&amp;gbv=2&amp;aq=f&amp;aqi=g3g-m6&amp;aql=&amp;oq=&amp;gs_rfai=">这样一幅经典的漫画</a>吧： </p>
<p><img style="margin: 5px 0px 10px" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image003.gif" /> </p>
<p>“我们给子问题加上一个参数j”，这就像你在看数学证明时看到无比邪恶的“<strong>我们考虑</strong>…“一样，一看到这样的句子，你就知道，这个问题的证明远远不像看上去那么简单，之所以你一路看下去理解上全无困难，那完全是因为作者直接把最重要的一个insight告诉你了，举个很简单的例子，证明素数无最大，谁都会第一时间想到去反证：假设存在一个最大的素数P，那么找到比P大的素数就是证明中最关键的一步，怎么找的？一般书上是不会说的，你会看到书上这样说：假设P是最大的素数，那么我们考虑P’ = 小于等于P的所有素数的乘积+1。那么P’一来显然大于P，二来不能被小于它的所有素数整除，那么P&#8217;就成了大于P的素数。 </p>
<p>如果你经常注意反证法，你会发现一个有趣的现象，反证法里面经常会有这样一句“我们考虑”，而“我们考虑”后面几乎肯定接着一个天外飞仙一般的insight。素数无最大这个古老的证明里面的“我们考虑”尚算是比较有迹可循的（我们想要构造一个更大的素数，而素数的等价定义就是“不能被小于它的所有素数整除，为了达到这个目的，构造的方法就较明显了）。但是有非常非常多的证明，其中关键的一步就跟嗑药磕出来做梦做出来走路跌跟头跌出来的一样（不信去翻一翻《<a href="http://www.amazon.com/Proofs-BOOK-Martin-Aigner/dp/3540404600">Proofs from THE Book</a>》），让你完全不知道他怎么想到的。 </p>
<p>话说回来，虽然有很多数学证明的关键步骤是很难逆向工程的（因为很多时候想出那个关键步骤的本人其实也是尝试了各种方法，撞了无数堵墙，在寻求证法的尝试空间中作了N次回溯才“妙手偶得”，与其说是妙手偶得，不如说是绞尽脑汁），但并非全无章法可循，否则陶哲轩也不会写出《<a href="http://www.amazon.com/Solving-Mathematical-Problems-Personal-Perspective/dp/0199205604">Solving Mathematical Problems</a>》这样的著作来，而求解问题也就成了真正的Black Art了。</p>
<p>算法的解法则比精妙的数学证明稍加更容易逆向工程一点。只要你有耐心仔细地去琢磨算法的关键步骤和本质，总能从中窥探到一些更general的思想和思路来。 </p>
<p>此外，很多经典问题，算法书上的讲法虽然时时令我们失望，但如果去网上一搜，则通常会发现更优秀的解释来。比如<a href="http://www.google.com/webhp?hl=en#sclient=psy&amp;hl=en&amp;site=webhp&amp;source=hp&amp;q=%E8%83%8C%E5%8C%85%E9%97%AE%E9%A2%98+%E8%A7%A3%E7%A9%BA%E9%97%B4&amp;aq=f&amp;aqi=&amp;aql=&amp;oq=&amp;gs_rfai=&amp;pbx=1&amp;fp=ab43752bbefd1914">背包问题就是如此</a>。</p>
<p>简单地说，如果你对于每个问题都能真正弄清以下这几个问题的答案，那么可以肯定的是，你的理解，记忆，以及学习的效率都会得到质的提高： </p>
<ul>
<li>为什么这种解法是对的？ </li>
<li>为什么那种解法是错的？ </li>
<li>为什么这种解法不是最优的？ </li>
<li>证明为什么没有更优的解法。 </li>
</ul>
<p>回到人民群众喜闻乐见的经典例子：背包问题。为什么01背包问题的正确（高效）算法是正确（高效）的。表面的解释是，因为01背包问题的子问题定义是 K(W, j)，其两个维度相乘的可能性一共有nW种，也就是说一共要计算nW个子问题，而计算每个子问题的复杂度是O(1)的。</p>
<p>但是如果仅仅满足于这样的解释，可以说是隔靴搔痒，并没有触及到本质。算法本质上可以看做是在一个解空间当中的搜索问题，所以要分析一个算法的好坏，首先弄清它的解空间的结构，然后分析它是怎么来探索这个解空间的。</p>
<p>弄清解空间的是第一步，例如排序算法，其解空间可以看做是所有可能的下标排列组合，其中有且仅有一个排列是正确的排序排列（简单起见假设元素各不相同）。那么一个算法在探索这个解空间方面的行为就决定了它的效率高低，最简单的，如果一个算法每次只能检查解空间中的一个点，那么这个算法的复杂度就是解空间的大小。对排序算法而言也就是n!。从这个角度来看，我们就会很容易的发现，所有基于比较的排序算法，其复杂度为什么是以O(nlogn)为下界的，因为一次比较操作最多有两个结果，a&gt;b或a&lt;b，既然只有两种结果，那么最多只能将解空间进行2分，如果每次都能完美的2分，那么找到那个唯一点最终需要的步骤就是log(n!) = O(nlogn)。如此就不难理解<a href="http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/">什么基于比较的排序算法的复杂度最好不过如此了</a>。</p>
<p>回到01背包问题，01背包问题的解空间其实也是类似的。一次选取就是一个01数组，其中每个元素代表其所对应的物品要不要选取。很显然，这个解空间的大小是2^n。在01背包的算法里面，每当我们解出K(W, j)（需要O(W)次计算）之后，解空间就会被折半（排除掉1/2的可能性），一共如此做n次，就能得到最终解。由于每次折半的代价是O(W)，便不难理解为什么算法复杂度是O(nW)了。</p>
<p>那么，为什么每次计算出K(W,j)就能使解空间折半呢？那就需要来看看这个算法是如何探索解空间的，算法探索解空间的方式在其递归公式里面：</p>
<p>K(W, j) = max { K(W, j-1), K(W-Wj, j &#8211; 1)&#160; + Vj }</p>
<p>也就是说，首先看你要不要选取第一个物品，有两种可能性（两个分支），每个分支都是一个更低阶的子问题，即在其中的任意一个分支下都要决定要不要选取第二个物品（又是两个分支），如此下递归去，可以构建出一棵有2^n方个叶子节点的树，每条从根结点到叶子节点的路径“01..101”就对应一个解，其中每个分叉代表“选”或“不选”当前的物品。</p>
<p>建立在对这个解空间的理解上，我们再来看为什么01背包问题的正确解法能做到O(nW)。（首先你最好将这棵树画在纸上，其中每个节点都是一个子问题K(W,j)，每条分叉都是0或1。）当我们计算出所有的K(W, 1)（需要O(W)次操作）之后，我们容易注意到，所有离叶子节点的距离为1的内部节点K(W, 2)到叶子节点的两个分支都必然只能取其一了，也就是说，有一半的叶子节点被排除掉了（对解空间折半）。当我们进而计算出K(W,2)之后，同样的道理，我们容易看到，到叶子节点距离为2的内部节点的两个分支也只能取其一了，这就进而再次将解空间折半。由于每次折半需要O(W)的复杂度，所以就不难理解算法的总复杂度为O(nW)了。另一种理解的方法是，当我们计算出K(W,j)的时候，从内部节点K(W,j)到根节点的唯一路径便确定了。经过O(nW)次计算，从根节点到那个唯一解（叶子节点）的路径便完全确定了。</p>
<p><strong>知道怎么做是从正确（高效）解法得到的，而知道为什么必须得那样做则往往是从错误（低效）的解法当中得到的。</strong></p>
<p>然而遗憾的是，绝大多数算法书或教程都只顾一上来就告诉你正确的做法是什么，对于一些常见的错误解法，或者常见的低效解法，却根本不加分析。经验告诉我们，理解错误的做法为什么错误同样甚至更为重要，往往是在理解了错误的解法为什么错误之后，我们才能深刻的体会到为什么正确的解法是如此正确。</p>
<p>还是拿经典的背包问题来作例子，你几乎看不到哪本书会告诉你一个典型的低效解法为什么低效的深刻原因。我们都知道动态规划的核心在于子问题的划分，同样的问题，不同的划分办法得到的复杂度完全不一样。前面已经提到了，重复背包问题的思路在01背包问题上会带来指数级的复杂度，但是为什么呢？如果你满足于说：因为如果拿重复背包问题的思路来解01背包问题，那么子问题定义的第二个维度（物品的子集）（见前文）是指数级的，那么要计算所有子问题，当然是指数级的。那么你只是看到这个问题的表象。</p>
<p>如果从对解空间的探索方式来说，可以容易看出这个现象的本质，我们回顾一下01背包问题的正确（高效）算法：</p>
<p>K(W, j) = max { K(W, j-1), K(W-Wj, j &#8211; 1)&#160; + Vj }</p>
<p>这个算法讨论的是两种情况，“要”或者“不要”选取第j个物品，这两种情况所对应的解空间是完全不交的，这就有效地将解空间划分为了不重复的两个部分。</p>
<p>而再来看利用重复背包问题思路的解法：</p>
<p>K(W, S) = max { K(W-Wi, S\{i}) + Vi : Wi &lt;= W } </p>
<p>这里讨论的是首先拿掉哪一个物品，还是那句话，讨论的每一个分支都对应了算法对解空间的一个切分，我们容易看出，在“先拿物品i”和”先拿物品j“这两个分支里面，存在大量的重复，因为先拿物品i再拿j，和先拿物品j再拿i对应的是完全一样的一组选取。事实上，如果你将这个递归公式画成树状结构，会发现有n!个叶子节点。n!是什么概念？01背包问题的解空间大小本质上就只有2^n次方，穷举也不过O(2^n)的复杂度，结果这样一切分却变成了n!，可见这种对解空间的切分方法的冗余度是多么高了。你不妨看看，每一次计算K(W, S)子问题能对解空间排查多少呢？是否能像前面正确的算法那样，每次都能有效排查一半情况？理解了这一点之后，我们便注意到在划分解空间，也就是定义子问题的时候的一个原则，就是在建立递归公式的时候，尽量将解空间进行不交的切分。同时我们便有了趁手的工具去分析一个动态规划的解法的效率。</p>
<p>最后再举一个例子：算法书上几乎必讲的霍夫曼树。你所看的算法书在讲霍夫曼树的时候给了证明吗？讲过霍夫曼树的<a href="http://en.wikipedia.org/wiki/Huffman_tree#History">历史八卦</a>吗？也许你看了霍夫曼树的构造方法之后觉得：“哦，这样啊，显然”。但是你可曾想到，在最优编码这个问题上，连<a href="http://en.wikipedia.org/wiki/Shannon-Fano_coding">香农本人之前给出的解法</a>都只是suboptimal的，而且霍夫曼本人在得到这个算法之前也是绞尽脑汁几近放弃。<strong>如果你10分钟就“理解”了，那么百分之百只是背了课文而已。</strong></p>
<h3  class="related_post_title">你可能也会喜欢以下文章</h3><ul class="related_post"><li><a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/" title="知其所以然（以算法学习为例）">知其所以然（以算法学习为例）</a> (36)</li><li><a href="http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/" title="知其所以然（三）：为什么算法这么难？">知其所以然（三）：为什么算法这么难？</a> (57)</li><li><a href="http://mindhacks.cn/2009/12/20/dark-time/" title="暗时间">暗时间</a> (143)</li><li><a href="http://mindhacks.cn/2009/10/05/im-a-tiny-bird-book-review/" title="不是书评 ：《我是一只IT小小鸟》">不是书评 ：《我是一只IT小小鸟》</a> (57)</li><li><a href="http://mindhacks.cn/2009/07/06/why-you-should-do-it-yourself/" title="[BetterExplained]遇到问题为什么应该自己动手">[BetterExplained]遇到问题为什么应该自己动手</a> (62)</li><li><a href="http://mindhacks.cn/2009/05/17/seven-years-in-nju/" title="我在南大的七年">我在南大的七年</a> (149)</li><li><a href="http://mindhacks.cn/2009/03/28/effective-learning-and-memorization/" title="[BetterExplained]如何有效地记忆与学习">[BetterExplained]如何有效地记忆与学习</a> (104)</li><li><a href="http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/" title="[BetterExplained]为什么你应该（从现在开始就）写博客">[BetterExplained]为什么你应该（从现在开始就）写博客</a> (206)</li><li><a href="http://mindhacks.cn/2009/02/09/writing-is-better-thinking/" title="[BetterExplained]书写是为了更好的思考">[BetterExplained]书写是为了更好的思考</a> (96)</li><li><a href="http://mindhacks.cn/2009/01/14/make-yourself-irreplacable/" title="什么才是你的不可替代性和核心竞争力">什么才是你的不可替代性和核心竞争力</a> (25)</li></ul><hr />
<h3>订阅 Mind Hacks</h3>
<a title="用Google Reader订阅" href="http://fusion.google.com/add?feedurl=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_google.gif" style="border:0" alt="订阅到 | Google" /></a> 
<a title="用鲜果订阅" href="http://www.xianguo.com/subscribe.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_xianguo.gif" style="border:0" alt="订阅到 | 鲜果" /></a>
<br/> 
<a title="用抓虾订阅" href="http://www.zhuaxia.com/add_channel.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_zhuaxia.gif" style="border:0" alt="订阅到 | 抓虾" /></a> 
<a title="用有道订阅" href="http://reader.youdao.com/b.do?keyfrom=mindhacks&url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_yodao1.gif" style="border:0" alt="订阅到 | 有道" /></a> 
<hr />
<h3>我是你的信息过滤器</h3>
想了解作者最近在关注什么，欢迎 Follow <a href="http://weibo.com/pongba">刘未鹏pongba@微博</a>
<br/>
程序员朋友请到作者发起的 <a href="https://groups.google.com/group/pongba">TopLanguage</a> (<a href="http://mindhacks.cn/about-toplanguage/">about</a>) 社群逛逛，定有收获 :)
<br/>
想了解作者在阅读哪些书，请到 <a href="http://www.douban.com/people/pongba/">pongba@豆瓣</a>，或者直接访问以下四个豆列：<a href="http://www.douban.com/doulist/46003/">[只读经典]思维改变生活</a> | <a href="http://www.douban.com/doulist/127649/">[只读经典]思考的技术与艺术</a> | <a href="http://www.douban.com/doulist/197706/">决策与判断</a> | <a href="http://www.douban.com/doulist/176513/">机器学习与人工智能书籍资源导引</a> 。
<hr />
<p><small>
本文由 刘未鹏 发布在 <a href="http://mindhacks.cn">刘未鹏 | Mind Hacks</a>, 2010. | <a href="http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/#commenting">42 条评论</a> | 标签: <a href="http://mindhacks.cn/tags/%e5%ad%a6%e4%b9%a0%e6%96%b9%e6%b3%95/" rel="tag">学习方法</a>, <a href="http://mindhacks.cn/tags/%e7%ae%97%e6%b3%95/" rel="tag">算法</a>
<br/>
转载请注明作者，出处，以及<a href="http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/">原始超链接</a>: http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/
</small></p>]]></content:encoded>
			<wfw:commentRss>http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/feed/</wfw:commentRss>
		<slash:comments>42</slash:comments>
		</item>
		<item>
		<title>知其所以然（以算法学习为例）</title>
		<link>http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/</link>
		<comments>http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/#comments</comments>
		<pubDate>Mon, 07 Jul 2008 13:05:00 +0000</pubDate>
		<dc:creator>刘未鹏</dc:creator>
				<category><![CDATA[学习方法]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/</guid>
		<description><![CDATA[其实下文的绝大部分内容对所有学习都是同理的。只不过最近在正儿巴经地学算法，而后者又不是好啃的骨头，所以平时思考总结得就自然要比学其它东西要多一些。

问题：目前几乎所有的算法书的讲解方式都是欧几里德式的、瀑布式的、自上而下的、每一个推导步骤都是精准制导直接面向目标的。由因到果，定义、引理、定理、证明一样不少，井井有条一丝不乱毫无赘肉。而实际上，这完全把人类大脑创造发明的步骤给反过来了。看起来是阳关大道，实际上车马不通。]]></description>
			<content:encoded><![CDATA[<p><strong>Updated(2008-7-24)</strong><strong>：更新见正文部分，有标注。</strong></p>
<p>其实下文的绝大部分内容对所有学习都是同理的。只不过最近在<a href="http://blog.csdn.net/pongba/archive/2008/06/05/2513263.aspx">正儿巴经地</a>学算法，而后者又不是好啃的骨头，所以平时思考总结得就自然要比学其它东西要多一些。</p>
<p>问题：目前几乎所有的算法书的讲解方式都是欧几里德式的、瀑布式的、自上而下的、每一个推导步骤都是精准制导直接面向目标的。由因到果，定义、引理、定理、证明一样不少，井井有条一丝不乱毫无赘肉。而实际上，这完全把人类大脑创造发明的步骤给反过来了。看起来是阳关大道，实际上车马不通。</p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image001.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="clip_image001" border="0" alt="clip_image001" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image001-thumb.jpg" width="244" height="166" /></a></p>
<p>而对读者来说，这就<strong>等于直接告诉你答案&amp;做法了</strong>，然后<strong>让你去验证</strong>这个答案&amp;做法是可行&amp;成立的。而关于答案&amp;做法到底是怎么来的，从问题到答案之间经历了怎样的思维过程。却鲜有书能够很好的阐释。就我有限的阅（算法）书经验，除了波利亚的《怎样解题》还算合格之外（也并非最理想），其它的（包括有名的《算法导论》、《如何解题：现代启发式方法》、《Algorithms》、《编程珠玑》，甚至TAOCP——公平地说由于高老大对算法领域历史了解得非常通透，所以许多地方能够从原始脉络来讲述一个问题，譬如令人印象深刻的从竞赛树到堆的讲解就寥寥一页纸道出了堆这个数据结构的本质来，而像刚才列的几本有名的书却都没有做到），在思维的讲述上都算不上合格<strong>（当然不是说这些书没有价值，作为知识性的参考书籍，它们将知识整理出系统结构，极大的便利了知识的掌握，就像《什么是数学》所做的工作一样）</strong>，为什么我这么说呢，因为我发现每每需要寻找对一个算法的解释的时候，翻开这些书，总是直接就看到关于算法逻辑的描述，却看不到整个算法的诞生过程背后的思想。</p>
<p><strong>我们要的不是相对论，而是诞生相对论的那个大脑。我们要的不是金蛋，而是下金蛋的那只鸡。</strong></p>
<p><strong><i>Update(2008-7-24):</i></strong><em> </em><em>收到不少同学的批评，想来这个开头对一些著作的语气过重了，实际上，注意，</em><strong><i>我完全不否认这些著作的价值，我自己也在通过阅读它们来学习算法，并且有很多收获</i></strong><em>。这篇文章更多的只是建议</em><strong><i>除了阅读这些著作之外还需要做的功课</i></strong><em>。此外，对于这类知识讲述（欧几里德）方式的批判西方（尤其是在数学领域）早就有了，早在欧拉和庞加莱的时候，他们俩就极其强调思维的传授，欧拉认为如果不能传授思维，那数学教学是没意义的。而庞加莱本人则更是对数学思维有极大的兴趣和研究（</em><strong><i>我前阵子在讨论组上还转载了一篇庞加莱的著名演讲，就是说这个的，参见</i></strong><a href="http://groups.google.com/group/pongba/browse_frm/thread/3dfc84e0506486cc"><strong><i>这里</i></strong></a><em>）。我只是在说目前的算法书没有做到思维讲述的层面，因此建议阅读这些书之余应该寻找算法的原始出处，应该寻根究底，多做一些功课，知道算法到底是怎么诞生的，并且我说明了为什么应该知其所以然，有哪些好处（见下文），我还给了几个例子譬如红黑树作者讲红黑树的，g9讲后缀树的，以及Knuth讲heap的。唉，其实挺正统的观点，授人以渔，不管是东方西方都有类似的古老谚语。而我只是从认知科学的角度加了点解释，windstorm称之为“解释文”。而已。可惜被开头的语气搞砸了，算了，既发了也就不改了。</em></p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image002.gif"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="clip_image002" border="0" alt="clip_image002" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image002-thumb.gif" width="179" height="191" /></a></p>
<p>为什么会这样，其实是有原因的。</p>
<p>我们在思考一个问题的过程中有两种思维形式：</p>
<ul>
<li><strong>联想</strong>：这种思维某种程度上可以说是“混乱”的（虽然从一个更根本的层面上说是有规则的），所谓混乱是指很多时候并不确定联想到的做法最终是否可行，这些联想也许只是基于题目中的某个词语、语法结构、问题的某个切片、一些零星局部的信息。这个过程是试探性的。最后也许有很大一部分被证明是不可行的。很多时候我们解决问题用的都是这种思维，简言之就是首先枚举你关于这个问题能够想到的所有你学过的知识，然后一一往上套看看能否解决手头的问题。这种思维方式受限于人脑联想能力本身的局限性。我在<a href="http://blog.csdn.net/pongba/archive/2008/04/18/2302905.aspx">《跟波利亚学解题》</a>中就提到了几个例子。联想本身需要记忆提取的线索，所以受到记忆提取线索的制约，如果线索不足，那怎么也联想不起来。而提取线索的建立又取决于当初保存记忆的时候的加工方法（<a href="http://www.douban.com/subject/1315575/">《找寻逝去的自我》</a>里面有阐述），同时，面对一个问题，你能够从中抽取出来的联想线索又取决于你对问题的认识层度/抽象深度，表浅的线索很可能是无关的，导致无效的联想&amp;试错（<a href="http://www.douban.com/subject/2845839/">《Psychology of Problem Solving》</a>里面有阐述）。总之，联想这个过程充满了错误的可能。</li>
<li><strong>演绎</strong><strong>&amp;</strong><strong>归纳</strong>：演绎&amp;归纳是另一种思维形式。它们远比联想有根据。其中演绎是严格的，必然的。归纳也是有一定根据的。在面对一个问题的时候，我们有意无意的对问题中的各个条件进行着演绎；譬如福尔摩斯著名的“狗叫”推理——狗+生人=&gt;吠叫 &amp; 昨晚狗没有叫 =&gt; 那个人是熟人。就是一个典型的对问题的各个条件进行演绎的推理过程。还有就是通过对一些特殊形式的观察来进行归纳，试图总结问题中的规律。然而，不幸的是，面对复杂的问题，演绎&amp;归纳也并不总是“直奔”问题的解决方案的。人的思维毕竟只能一下子看到有限的几步逻辑结论，一条逻辑演绎路径是否直奔答案，不走到最后往往是不知道的，只要答案还未出现，我们大脑中的逻辑演绎之树的末端就始终隐藏在黑暗之中。而当最终答案出现了之后，我们会发现，这棵演绎之树的很多分支实际上都并不通往答案。所以，虽然演绎&amp;归纳是一种“必然”的推理，然而却并不“必然”引向问题的结论，它也是试错的，只不过比联想要更为靠谱一些。</li>
</ul>
<p>既然认识到，<strong>人类解决问题的两大思维方式实际上都是有很大的试错成分的</strong>（好听一点叫“探索”），那么就不难意识到，对一个问题的思考过程实际上是相当错综复杂的，而且<strong>充满了无效分支</strong>——在思考的过程中我们也会不断的对分支进行评估，做适当的剪枝——因此当我们找到问题的解之后，<strong>一来思维的漫长繁杂的过程已经在大脑里面淡化得差不多了</strong>，只有那些引向最终结论的过程会被加“高亮”——我们在思考的过程中本就会不断的抛弃无效的思路，只留下最有希望的思路。简而言之就是最后证明没用或者早先我们就不抱希望的一些想法就被从工作记忆中扔掉了。<strong>二来，思考过程是我们的空气和水，而“<a href="http://blog.csdn.net/pongba/archive/2008/01/04/2025830.aspx">鱼是最后一个感觉到水的</a>”</strong>，我们感觉不到思维法则本身的存在，我们只是不知不觉运用它。<strong>三来，由于我们的目标是问题的解</strong>，解才是我们为之兴奋和狂喜的东西，而不是求解的过程，<strong>过程只是过程，目的才是目的</strong>。这就像一个寻宝者，在漫长曲折的寻宝历程之后，在找到宝藏的时候，他会对宝藏感到狂喜（记得阿基米德的“找到了！”吗？）而迫不及待地要展示出来，而漫长的思考本身却成了注脚。我们是有目的的动物，目的达到了，其它的就相对不那么重要了。最后，对于传授知识的人，也许还有<strong>其四：感到介绍思维过程是不相干的</strong>，毕竟思维过程并不是算法问题的解，算法问题的解才是算法问题的解。然而不幸的是，忽视到达解的那个过程实际上却变成了舍本逐末。我们看到的是寥寥数行精妙绝伦的算法，然后仰天长叹自己想不出来啊想不出来。为什么想不出来，因为你不知道那短短数行算法背后经历的事怎样漫长的思考过程，如果问题求解是一部侦探小说，<strong>那么算法只是结局而已，而<a href="http://blog.csdn.net/pongba/archive/2008/05/07/2412144.aspx">思考过程</a>才是情节</strong>。</p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image003.gif"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="clip_image003" border="0" alt="clip_image003" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image003-thumb.gif" width="214" height="244" /></a></p>
<p>既然如此，也就难怪古往今来算法牛人们算法牛，但却没有几个能真正在讲述的时候还原自己的思维过程的（那个“ 渔”字），手把手的教学生走一遍推理的思路，就可以让学生获得思维过程的训练。金出武雄在<a href="http://www.douban.com/subject/1867455/">《像外行一样思考，像专家一样实践》</a>中说<strong>写论文应该写得像侦探小说一样</strong>，我很赞同。欧几里德式的介绍，除了提供枯燥的知识之外，并没有提供帮助人获得知识的东西——思维（关于对数学书籍的欧几里德式写法的批评其实也是由来已久了，并且有人呼吁了好几种其它的<a href="http://en.wikipedia.org/wiki/Mathematics_education#Methods">教学方法</a>）。<strong>从这方面，我们所尊敬的一些“圣经”级书籍在传道授业上还不如侦探小说</strong>，前者是罗列一大堆知识，后者则是阐述获得知识的过程——推理&amp;联想。</p>
<p>然而，我们都是人，人类该有的思维形式，我们难道不是都有吗。既然如此，思维本身又有什么需要一遍遍教的呢？</p>
<p>并非如此。</p>
<p>讲述思维过程而非结果有几个极其重要的价值：</p>
<ul>
<li><strong>内隐化</strong>：思维法则其实也是知识（只不过它是元知识——是帮助我们获得新知识的知识）；是<a href="http://en.wikipedia.org/wiki/Implicit_memory">内隐的记忆</a>。我们在思考的过程中觉察不到思维法则的作用，它们却在幕后实实在在的左右着我们的思维轨迹。要将思维方法内隐化，<a href="http://blog.csdn.net/pongba/archive/2008/06/05/2513263.aspx">需要不断练习</a>，就像需要不断练习才能无意识状态下就能骑自行车一样。</li>
<li><strong>跨情境运用</strong>：思维法则也是知识记忆，是问题解决策略。既然是记忆，就受到提取线索的制约，这就是为什么当波利亚<a href="http://www.douban.com/subject/1456890/">告诉你</a>要“注意未知数”之后你还是不能真正在所有需要你“注意未知数”的地方都能提醒自己“注意未知数”。很多时候未知数是很隐蔽的，未知数并不会总是头顶一个大帽子上面写着“我是未知数”。所以很多时候缺乏对这个策略的“提醒”线索，这也是为什么<strong>你学会了在解决数学问题的时候“</strong><strong>注意未知数”</strong><strong>却不一定能在解决现实生活中的问题中时刻都能“</strong><strong>注意你的未知数”</strong>（<a href="http://www.douban.com/subject/1135754/">《你的灯亮着吗？》</a>整本书的价值便在于此），因为解数学题和解决生活中问题的场景不一样，不同的环境线索，在你大脑中激发的记忆也不一样。就连问题求解中，不同的问题之间的细小差别也可能导致思维轨迹很大的不同，有时你的注意力会被一个无关线索激发的联想吸引开去，忘记如“注意你的未知数”这样的重要法则。而一本从思维角度来讲问题求解的书则可以一遍遍将你置于不同的问题场景下然后在该提醒你的时候提醒你，让你醒悟到“<strong>哦，原来</strong><em><b>这个</b></em><strong>时候也应该想到</strong><em><b>这个</b></em><strong>啊。</strong>”，做多了这样的思维演习你就会逐渐从中领悟到某种共性，并将一些思维习惯得到强化，于是终于能够在需要运用某策略的时候能适时的想起来了。</li>
<li><strong>对问题解的更多</strong><strong><a href="http://blog.csdn.net/pongba/archive/2008/06/05/2513263.aspx">记忆提取线索</a></strong>：我们平时学习算法时几乎仅止于“理解”，别人把一个方案放在你面前，你去验证一下，心说“哦，不错，这个的确可以工作”。然后就没了。稍微简单一点的算法还好，复杂一点的对于记忆的负担是很大的，这就是为什么有时候我们看到一个绝妙的解法，这个解法看上去不知道从哪里来的，但经过我们的理解，却发现是对的，我们感叹，真巧妙，结果一些天之后，别人问起这个问题，我们说：“<strong>唉，那是个多么巧妙的算法啊，但是我只记得它巧妙，却不记得它到底是怎样的了。</strong>” 为什么？因为在不知其所以然的情况下，算法只是一堆离散的机械步骤，缺少背后的思想的支撑，这些步骤之间就没有一个<strong>本质层面上的关联</strong>（先知亚里士多德早就指出：学习即联接）。所以就跟背历史书也没多大区别。然而，<strong>知道了算法是怎样一步步被推导出来的，我们就一下拥有了大量的记忆提取线索：对算法发现过程中的任何一个关键步骤（尤其是本质）的回忆都可能使我们能够自己动手推导出剩余的内容</strong>。<strong>譬如</strong>你知道堆(heap)是怎样由朴素的决策树演化而来的，它又是为了解决什么问题的，你即便忘记了具体的细节，也可以自己推导出来。<strong>譬如</strong>你知道KMP算法的本质在于消除回溯，至于如何消除回溯却并不是那么难以推导的，所以即便忘了也可以借助于大脑的逻辑演绎能力再现出来。<strong>譬如</strong>你知道Tarjan算法其实只是从后序遍历经过两个优化调整而来的（其中并査集的使用其实只是优化手段——为了能够迅速判断祖先节点是谁——而非算法本质——当然，算法设计的主要任务本来就是通过问题条件中蕴含的知识来“消除冗余计算”和“避免不必要计算”，所以你也可以说并査集的使用是关乎本质的，只不过，知道了为什么需要引入并査集，就会强烈地感觉到一切是顺理成章的了），那这个出了名的绕人的算法也就不那么难以理解和记忆了。<strong>譬如</strong>你知道<a href="http://blog.csdn.net/pongba/archive/2008/06/13/2544933.aspx">排序的本质</a>，就能够对什么是最优排序，为什么它是最优排序有深刻的认识。四两拨千斤。</li>
</ul>
<p><a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image004.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; border-top: 0px; border-right: 0px" title="clip_image004" border="0" alt="clip_image004" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image004-thumb.jpg" width="244" height="124" /></a></p>
<ul>
<li><strong>包含了多得多的知识</strong>：<strong>记一个算法，就只有一个算法</strong>。一个萝卜一个坑。<strong>就好比背99</strong><strong>乘法表只能解决乘法问题一样</strong>。而记<strong>背后的思想，却有助于解决一类问题</strong>。思想所处的抽象层面往往比到处都是实现细节的算法本身要低，越是低的抽象层次，越是本质，涵盖范围越是广泛。数学的发展本身就体现了这个过程，抽象代数就是非常好的例子。<strong>算法诞生过程中的思路往往包含了比实际算法更本质得多的知识</strong>，实际算法乃至算法的某个特定语言的实现包含了太多表面的不相干知识，它们会阻碍对本质的理解。</li>
<li><strong>重在分析推理，而不是联想</strong>：学了一大通算法和数据结构之后的一个<strong>副作用</strong>就是，看到一个问题之后，脑袋里立即不管三七二十一冒出一堆可能相干的数据结构和算法来。联想是强大的<a href="http://www.douban.com/doulist/127649/">思维捷径</a>，在任何时候都会<a href="http://en.wikipedia.org/wiki/Attention#Neural_correlates_of_attention">抢占</a>大脑的<a href="http://en.wikipedia.org/wiki/Working_memory">工作记忆</a>，由不得你控制——比如我问你“如何寻找区间的最大值”，首先进入你的意识的肯定就是学过的那个算法，甚至算法的实现细节都一一跳了出来，也许最先跳出来的还是算法实现中某个最容易弄错的边界细节，或是某个比较tricky的实现技巧！然而这些其实根本不反映一个算法的本质，结果想来想去总是停留在问题的表层。而另一方面，重在思维的传授则可以让人养成从问题本质入手，逐步分析推理的习惯，而不是直接生搬硬套。<strong>当然，完全不可否认，联想本身也是极其重要的思维方法，甚至可以说是人类思维<a href="http://en.wikipedia.org/wiki/Hebbian_learning">最重要的</a></strong><strong>特征</strong>。很多时候我们并不知道问题的本质是什么，就需要靠联想、类比来领路探索。只不过，养成优先从问题的本质入手进行考察的好习惯绝对是有更大的好处的。</li>
</ul>
<p>那到底什么样的才算是授人以渔的呢？波利亚的《如何解题》绝对算是一本，他的《数学的发现》也值得一看。具体到算法书，那就不是光看text book就足够的了，为了深入理解一个算法的来龙去脉前因后果，从一个算法中领悟尽量深刻的东西，则需要做到三件事情：</p>
<ul>
<li><strong>寻找该算法的原始出处</strong>：TAOCP作为一个资料库是绝对优秀的，基础的算法只要你能想到的，几乎都可以在上面找到原始出处。查到原始出处之后（譬如一篇paper），就可以去网上搜来看了。因为最初的作者往往对一个方案的诞生过程最为了解。比如经典数据结构中的红黑树是出了名的令人费解的结构之一，但它的作者Sedgewick<a href="http://groups.google.com/group/pongba/browse_thread/thread/3513a21065faba68">一张PPT，给你讲得通通透透</a>，比算法导论上的讲法强上数倍。</li>
<li><strong>原始的出处其实也未必就都推心置腹地和你讲得那么到位</strong>：前面说过，算法设计出来了之后人们几乎是不会去回顾整个的思维过程细节的，只把直指目标的那些东西写出来。结果就又是一篇欧几里德式的文章了。于是你就迷失在一大堆“定义”、“引理”、“定理”之中了。这种文章看上去整个写得井井有条，其实是把发明的过程整个给颠倒过来了，我一直就想，如果作者们能够将整个的思路过程写出来，哪怕文字多上十倍，我也绝对会比看那一堆定义定理要容易理解得多。话说回来，怎么办？可以再去网上找找，牛人讲得<a href="http://blog.csdn.net/g9yuayon/archive/2008/06/21/2574781.aspx">未必比经典教材上的差</a>。那倘若实在找不出好的介绍呢，就只能自己<strong>揣摩</strong>了。<strong>揣摩的重要性，是怎么说都不为过的</strong>。揣摩的<strong>一些指导性的问题有</strong>：为什么要这样（为什么这是好的）？为什么不是那样（有其它做法吗？有更好的做法吗？）？这样做是最好的吗？（为什么？能证明吗？）这个做法跟其它的什么做法有本质联系吗？<em>这个</em>跟<em>这个</em>的区别是什么？<strong>问题的本质是什么</strong>？<strong>这个做法的本质又是什么</strong>？到底本质上是什么东西导致了这个做法如此..？与这个问题类似的还有其它问题吗？（同样或类似的做法也适用吗？）等等。</li>
<li><strong>不仅学习别人的思路，整理自己的思路也是极其重要的</strong>：详见<a href="http://blog.csdn.net/pongba/archive/2008/04/18/2302905.aspx">《跟波利亚学解题》</a>的“4. 一个好习惯”和“7. 总结的意义”。</li>
</ul>
<p>前一段时间我们讨论组上有不少例子，见<a href="http://groups.google.com/group/pongba/web/toplang-problemsolvingseries">这里</a>，或<a href="http://del.icio.us/pongbablog/%E8%A7%A3%E9%A2%98">这里</a>。</p>
<h3  class="related_post_title">你可能也会喜欢以下文章</h3><ul class="related_post"><li><a href="http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/" title="知其所以然（续）">知其所以然（续）</a> (42)</li><li><a href="http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/" title="知其所以然（三）：为什么算法这么难？">知其所以然（三）：为什么算法这么难？</a> (57)</li><li><a href="http://mindhacks.cn/2009/12/20/dark-time/" title="暗时间">暗时间</a> (143)</li><li><a href="http://mindhacks.cn/2009/10/05/im-a-tiny-bird-book-review/" title="不是书评 ：《我是一只IT小小鸟》">不是书评 ：《我是一只IT小小鸟》</a> (57)</li><li><a href="http://mindhacks.cn/2009/07/06/why-you-should-do-it-yourself/" title="[BetterExplained]遇到问题为什么应该自己动手">[BetterExplained]遇到问题为什么应该自己动手</a> (62)</li><li><a href="http://mindhacks.cn/2009/05/17/seven-years-in-nju/" title="我在南大的七年">我在南大的七年</a> (149)</li><li><a href="http://mindhacks.cn/2009/03/28/effective-learning-and-memorization/" title="[BetterExplained]如何有效地记忆与学习">[BetterExplained]如何有效地记忆与学习</a> (104)</li><li><a href="http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/" title="[BetterExplained]为什么你应该（从现在开始就）写博客">[BetterExplained]为什么你应该（从现在开始就）写博客</a> (206)</li><li><a href="http://mindhacks.cn/2009/02/09/writing-is-better-thinking/" title="[BetterExplained]书写是为了更好的思考">[BetterExplained]书写是为了更好的思考</a> (96)</li><li><a href="http://mindhacks.cn/2009/01/14/make-yourself-irreplacable/" title="什么才是你的不可替代性和核心竞争力">什么才是你的不可替代性和核心竞争力</a> (25)</li></ul><hr />
<h3>订阅 Mind Hacks</h3>
<a title="用Google Reader订阅" href="http://fusion.google.com/add?feedurl=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_google.gif" style="border:0" alt="订阅到 | Google" /></a> 
<a title="用鲜果订阅" href="http://www.xianguo.com/subscribe.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_xianguo.gif" style="border:0" alt="订阅到 | 鲜果" /></a>
<br/> 
<a title="用抓虾订阅" href="http://www.zhuaxia.com/add_channel.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_zhuaxia.gif" style="border:0" alt="订阅到 | 抓虾" /></a> 
<a title="用有道订阅" href="http://reader.youdao.com/b.do?keyfrom=mindhacks&url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_yodao1.gif" style="border:0" alt="订阅到 | 有道" /></a> 
<hr />
<h3>我是你的信息过滤器</h3>
想了解作者最近在关注什么，欢迎 Follow <a href="http://weibo.com/pongba">刘未鹏pongba@微博</a>
<br/>
程序员朋友请到作者发起的 <a href="https://groups.google.com/group/pongba">TopLanguage</a> (<a href="http://mindhacks.cn/about-toplanguage/">about</a>) 社群逛逛，定有收获 :)
<br/>
想了解作者在阅读哪些书，请到 <a href="http://www.douban.com/people/pongba/">pongba@豆瓣</a>，或者直接访问以下四个豆列：<a href="http://www.douban.com/doulist/46003/">[只读经典]思维改变生活</a> | <a href="http://www.douban.com/doulist/127649/">[只读经典]思考的技术与艺术</a> | <a href="http://www.douban.com/doulist/197706/">决策与判断</a> | <a href="http://www.douban.com/doulist/176513/">机器学习与人工智能书籍资源导引</a> 。
<hr />
<p><small>
本文由 刘未鹏 发布在 <a href="http://mindhacks.cn">刘未鹏 | Mind Hacks</a>, 2008. | <a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/#commenting">36 条评论</a> | 标签: <a href="http://mindhacks.cn/tags/%e5%ad%a6%e4%b9%a0%e6%96%b9%e6%b3%95/" rel="tag">学习方法</a>, <a href="http://mindhacks.cn/tags/%e7%ae%97%e6%b3%95/" rel="tag">算法</a>
<br/>
转载请注明作者，出处，以及<a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/">原始超链接</a>: http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/
</small></p>]]></content:encoded>
			<wfw:commentRss>http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/feed/</wfw:commentRss>
		<slash:comments>36</slash:comments>
		</item>
		<item>
		<title>数学之美番外篇：快排为什么那样快</title>
		<link>http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/</link>
		<comments>http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/#comments</comments>
		<pubDate>Fri, 13 Jun 2008 11:53:00 +0000</pubDate>
		<dc:creator>刘未鹏</dc:creator>
				<category><![CDATA[数学]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[计算机科学]]></category>

		<guid isPermaLink="false">http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/</guid>
		<description><![CDATA[知道这个理论是在TopLanguage上的一次讨论，先是g9转了David MacKay的一篇文章，然后引发了牛人们的一场关于信息论的讨论。Anyway，正如g9很久以前在Blog里面所说的： 

有时无知是福。俺看到一点新鲜的科普也能觉得造化神奇。刚才读Gerald Jay Sussman（SICP作者）的文章，Building Robust Systems – an essay，竟然心如小鹿乱撞，手心湿润，仿佛第一次握住初恋情人温柔的手。

而看到MacKay的这篇文章我也有这种感觉——以前模糊的东西忽然有了深刻的解释，一切顿时变得明白无比。原来看问题的角度或层面能够带来这么大的变化。再一次印证了越是深刻的原理往往越是简单和强大。所以说，土鳖也有土鳖的幸福:P 

这篇文章相当于MacKay原文的白话文版。MacKay在原文中用到了信息论的知识，后者在我看来并不是必须的，尽管计算的时候方便，但与本质无关。所以我用大白话解释了一通。]]></description>
			<content:encoded><![CDATA[<p><strong>目录</strong></p>
<p>0. 前言</p>
<p>1. 猜数字</p>
<p>2. 称球</p>
<p>3. 排序</p>
<p>&#160;&#160;&#160; 3.1 为什么堆排比快排慢</p>
<p>&#160;&#160;&#160; 3.2 为什么快排其实也不是那么快</p>
<p>&#160;&#160;&#160; 3.3 基排又为什么那么快呢</p>
<p>4. 信息论！信息论？</p>
<p>5. 小结</p>
<p><strong>0. </strong><strong>前言</strong></p>
<p>知道这个理论是在<a href="http://groups.google.com/group/pongba">TopLanguage</a>上的一次讨论，先是g9<a href="http://groups.google.com/group/pongba/msg/f95aa12feb4dfd67">转了David MacKay的一篇文章</a>，然后引发了牛人们的<a href="http://groups.google.com/group/pongba/browse_frm/thread/28ac39e0222becf2">一场关于信息论的讨论</a>。Anyway，正如g9很久以前在<a href="http://blog.csdn.net/g9yuayon">Blog</a>里面所<a href="http://blog.csdn.net/g9yuayon/archive/2007/04/22/1574518.aspx">说</a>的： </p>
<blockquote><p>有时无知是福。俺看到一点新鲜的科普也能觉得造化神奇。刚才读Gerald Jay Sussman（<a href="http://mitpress.mit.edu/sicp/">SICP</a>作者）的文章，<a href="http://swiss.csail.mit.edu/classes/symbolic/spring07/readings/robust-systems.pdf">Building Robust Systems – an essay</a>，竟然心如小鹿乱撞，手心湿润，仿佛第一次握住初恋情人温柔的手。</p>
</blockquote>
<p>而看到<a href="http://users.aims.ac.za/~mackay/">MacKay</a>的这篇文章我也有这种感觉——以前模糊的东西忽然有了深刻的解释，一切顿时变得明白无比。原来看问题的角度或层面能够带来这么大的变化。再一次印证了越是深刻的原理往往越是简单和强大。所以说，土鳖也有土鳖的幸福:P </p>
<p>这篇文章相当于MacKay<a href="http://users.aims.ac.za/~mackay/sorting/sorting.html">原文</a>的白话文版。MacKay在原文中用到了信息论的知识，后者在我看来并不是必须的，尽管计算的时候方便，但与本质无关。所以我用大白话解释了一通。 </p>
<p><strong>1. </strong><strong>猜数字 </strong></p>
<p>我们先来玩一个猜数字游戏：我心里默念一个1~64之间的数，你来猜（你只能问答案是“是”或“否”的问题）。为了保证不论在什么情况下都能以尽量少的次数猜中，你应该采取什么策略呢？很显然，二分。先是猜是不是位于1~32之间，排除掉一半可能性，然后对区间继续二分。这种策略能够保证无论数字怎么跟你捉迷藏，都能在log_2{n}次以内猜中。用算法的术语来说就是它的下界是最好的。 </p>
<p>我们再来回顾一下这个游戏所蕴含的本质：为什么这种策略具有最优下界？答案也很简单，这个策略是平衡的。反之如果策略不是平衡的，比如问是不是在1~10之间，那么一旦发现不是在1~10之间的话就会剩下比N/2更多的可能性需要去考察了。 </p>
<p><a href="http://blog.youxu.info/">徐宥</a>在讨论中提到，这种策略的本质可以概括成“让未知世界无机可乘”。它是没有“弱点的”，<strong>答案的任何一个分支都是等概率的</strong>。反之，一旦某个分支蕴含的可能性更多，当情况落到那个分支上的时候你就郁闷了。比如猜数字游戏最糟糕的策略就是一个一个的猜：是1吗？是2吗？&#8230; 因为这种猜法最差的情况下需要64次才能猜对，下界非常糟糕。二分搜索为什么好，就是因为它每次都将可能性排除一半并且<strong>无论如何</strong>都能排除一半（它是最糟情况下表现最好的）。 </p>
<p><strong>2. </strong><strong>称球 </strong></p>
<p>12个小球，其中有一个是坏球。有一架天平。需要你用最少的称次数来确定哪个小球是坏的并且它到底是轻还是重。 </p>
<p>这个问题是一道流传已久的智力题。网络上也有很多讲解，还有泛化到N个球的情况下的严格证明。也有零星的一些地方提到从信息论的角度来看待最优解法。本来我一直认为这道题目除了试错之外没有其它高妙的思路了，只能一个个方法试，并尽量从结果中寻找信息，然后看看哪种方案最少。 </p>
<p>然而，实际上它的确有其它的思路，一个更本质的思路，而且根本用不着信息论这么拗口的知识。 </p>
<p>我们先回顾一下猜数字游戏。为了保证任何情况下以最少次数猜中，我们的策略是每次都排除恰好一半的可能性。类比到称球问题上：坏球可能是12个球中的任意一个，这就是12种可能性；而其中每种可能性下坏球可能轻也可能重。于是“坏球是哪个球，是轻是重”这个问题的答案就有12×2=24种可能性。现在我们用天平来称球，就等同于对这24种可能性发问，由于天平的输出结果有三种“平衡、左倾、右倾”，这就相当于我们的问题有三个答案，即可以将所有的可能性切成三份，根据猜数字游戏的启发，我们应当尽量让这三个分支概率均等，即平均切分所有的可能性为三等份。如此一来的话一次称量就可以将答案的可能性缩减为原来的1/3，三次就能缩减为1/27。而总共才有24种可能性，所以理论上是完全可以3次称出来的。 </p>
<p>如何称的指导原则有了，构造一个称的策略就不是什么太困难的事情了。首先不妨解释一下为什么最直观的称法不是最优的——6、6称：在6、6称的时候，天平平衡的可能性是0。刚才说了，最优策略应该使得天平三种状态的概率均等，这样才能三等分答案的所有可能性。 </p>
<p>为了更清楚的看待这个问题，我们不妨假设有6个球，来考虑一下3、3称和2、2称的区别： </p>
<p>在未称之前，一共有12种可能性：1轻、1重、2轻、2重、&#8230;、6轻、6重。现在将1、2、3号放在左边，4、5、6放在右边3、3称了之后，不失一般性假设天平左倾，那么小球的可能性就变成了原来的一半（6种）：1重、2重、3重、4轻、5轻、6轻。即这种称法能排除一半可能性。 </p>
<p>现在再来看2、2称法，即1、2放左边，3、4放右边，剩下的5、6不称，放一边。假设结果是天平平衡，那么可能性剩下——4种：5重、5轻、6重、6轻。假设天平左倾，可能性也剩下4种：1重、2重、3轻、4轻。右倾和左倾的情况类似。总之，这种称法，不管天平结果如何，情况都被我们缩小到了原来的三分之一！我们充分利用了“天平的结果状态可能有三种”这个条件来三等分所有可能性，而不是二等分。 </p>
<p>说到这里，剩下的事情就实在很简单了：第二步称法，只要记着这样一个指导思想——你选择的称法必须使得当天平平衡的时候答案剩下的可能性和天平左倾（右倾）的时候答案剩下的可能性一样多。实际上，这等同于你得选择一种称法，使得天平输出三种结果的概率是均等的，因为天平输出某个结果的概率就等同于所有支持这个结果（左倾、右倾、平衡）的答案可能性的和，并且答案的每个可能性都是等概率的。 </p>
<p>MacKay在他的书《Information Theory: Inference and Learning Algorithms》（<a href="http://users.aims.ac.za/~mackay/itila/book.html">作者开放免费电子书</a>）里面4.1节专门讲了这个称球问题，还画了一张不错的图，我就照抄了： </p>
<p><a href="http://mindhacks.cn/wp-content/uploads/2009/02/23131201.jpg"><img style="border-right-width: 0px; display: inline; border-top-width: 0px; border-bottom-width: 0px; border-left-width: 0px" title="2313120" border="0" alt="2313120" src="http://mindhacks.cn/wp-content/uploads/2009/02/2313120-thumb1.jpg" width="476" height="480" /></a> </p>
<p>图中“1+”是指“1号小球为重”这一可能性。一开始一共有24种可能性。4、4称了之后不管哪种情况（分支），剩下来的可能性总是4种。这是一个完美的三分。然后对每个分支构造第二次称法，这里你只要稍加演算就可以发现，分支1上的第二次称法，即“1、2、6对3、4、5”这种称法，天平输出三种结果的可能性是均等的（严格来说是几乎均等）。这就是为什么这个称法能够在最坏的情况下也能表现最好的原因，没有哪个分支是它的弱点，它必然能将情况缩小到原来的1/3。 </p>
<p><strong>3. </strong><strong>排序 </strong></p>
<p>用前面的看问题视角，排序的本质可以这样来表述：一组未排序的N个数字，它们一共有N!种重排，其中只有一种排列是满足题意的（譬如从大到小排列）。换句话说，排序问题的可能性一共有N!种。任何基于比较的排序的基本操作单元都是“比较a和b”，这就相当于猜数字游戏里面的一个问句，显然这个问句的答案只能是“是”或“否”，一个只有两种输出的问题最多只能将可能性空间切成两半，根据上面的思路，最佳切法就是切成1/2和1/2。也就是说，我们希望在比较了a和b的大小关系之后，如果发现a&lt;b的话剩下的排列可能性就变成N!/2，如果发现a&gt;b也是剩下N!/2种可能性。由于假设每种排列的概率是均等的，所以这也就意味着支持a&lt;b的排列一共有N!/2个，支持a&gt;b的也是N!/2个，换言之，a&lt;b的概率等于a&gt;b的概率。 </p>
<p>我们希望每次在比较a和b的时候，a&lt;b和a&gt;b的概率是均等的，这样我们就能保证无论如何都能将可能性缩小为原来的一半了！最优下界。 </p>
<p>一个直接的推论是，如果每次都像上面这样的完美比较，那么N个元素的N!种可能排列只需要log_2{N!}就排查玩了，而log_2{N!}近似于NlogN。这正是快排的复杂度。 </p>
<p><strong>3.1 </strong><strong>为什么堆排比快排慢 </strong></p>
<p>回顾一下堆排的过程： </p>
<p>1. 建立最大堆（堆顶的元素大于其两个儿子，两个儿子又分别大于它们各自下属的两个儿子&#8230; 以此类推） </p>
<p>2. 将堆顶的元素和最后一个元素对调（相当于将堆顶元素（最大值）拿走，然后将堆底的那个元素补上它的空缺），然后让那最后一个元素从顶上往下滑到恰当的位置（重新使堆最大化）。 </p>
<p>3. 重复第2步。 </p>
<p>这里的关键问题就在于第2步，堆底的元素肯定很小，将它拿到堆顶和原本属于最大元素的两个子节点比较，它比它们大的可能性是微乎其微的。实际上它肯定小于其中的一个儿子。而大于另一个儿子的可能性非常小。于是，这一次比较的结果就是概率不均等的，根据前面的分析，概率不均等的比较是不明智的，因为它并不能保证在糟糕情况下也能将问题的可能性削减到原本的1/2。可以想像一种极端情况，如果a肯定小于b，那么比较a和b就会什么信息也得不到——原本剩下多少可能性还是剩下多少可能性。 </p>
<p>在堆排里面有大量这种近乎无效的比较，因为被拿到堆顶的那个元素几乎肯定是很小的，而靠近堆顶的元素又几乎肯定是很大的，将一个很小的数和一个很大的数比较，结果几乎肯定是“小于”的，这就意味着问题的可能性只被排除掉了很小一部分。 </p>
<p>这就是为什么堆排比较慢（堆排虽然和快排一样复杂度都是O(NlogN)但堆排复杂度的常系数更大）。 </p>
<p>MacKay也提供了一个修改版的堆排：每次不是将堆底的元素拿到上面去，而是直接比较堆顶（最大）元素的两个儿子，即选出次大的元素。由于这两个儿子之间的大小关系是很不确定的，两者都很大，说不好哪个更大哪个更小，所以这次比较的两个结果就是概率均等的了。具体参考<a href="http://users.aims.ac.za/~mackay/sorting/sorting.html">这里</a>。 </p>
<p><strong>3.2 </strong><strong>为什么快排其实也不是那么快 </strong></p>
<p>我们考虑快排的过程：随机选择一个元素做“轴元素”，将所有大于轴元素的移到左边，其余移到右边。根据这个过程，快排的第一次比较就是将一个元素和轴元素比较，这个时候显而易见的是，“大于”和“小于”的可能性各占一半。这是一次漂亮的比较。 </p>
<p>然而，快排的第二次比较就不那么高明了：我们不妨令轴元素为pivot，第一次比较结果是a1&lt;pivot，那么可以证明第二次比较a2也小于pivot的可能性是2/3！这容易证明：如果a2&gt;pivot的话，那么a1，a2，pivot这三个元素之间的关系就完全确定了——a1&lt;pivot&lt;a2，剩下来的元素排列的可能性我们不妨记为P（不需要具体算出来）。而如果a2&lt;pivot呢？那么a1和a2的关系就仍然是不确定的，也就是说，这个分支里面含有两种情况：a1&lt;a2&lt;pivot，以及a2&lt;a1&lt;pivot。对于其中任一种情况，剩下的元素排列的可能性都是P，于是这个分支里面剩下的排列可能性就是2P。所以当a2&lt;pivot的时候，还剩下2/3的可能性需要排查。 </p>
<p>再进一步，如果第二步比较果真发现a2&lt;pivot的话，第三步比较就更不妙了，模仿上面的推理，a3&lt;pivot的概率将会是3/4！ </p>
<p>这就是快排也不那么快的原因，因为它也没有做到每次比较都能将剩下的可能性砍掉一半。 </p>
<p><strong>3.3 </strong><strong>鸡排为什么又那么快呢？ </strong></p>
<p>传统的解释是：<a href="http://en.wikipedia.org/wiki/Radix_sort">基排</a>不是基于比较的，所以不具有后者的局限性。话是没错，但其实还可以将它和基于比较的排序做一个类比。 </p>
<p>基排的过程也许是源于我们理顺一副牌的过程：如果你有N（N&lt;=13）张牌，乱序，如何理顺呢？我们假象桌上有十三个位置，然后我们将手里的牌一张一张放出去，如果是3，就放在位置3上，如果是J，就放在位置11上，放完了之后从位置1到位置13收集所有的牌（没有牌的位置上不收集任何牌）。 </p>
<p>我们可以这样来理解基排高效的本质原因：假设前i张牌都已经放到了它们对应的位置上，第i+1张牌放出去的时候，实际上就相当于“一下子”就确立了它和前i张牌的大小关系，用O(1)的操作就将这张牌正确地插入到了前i张牌中的正确位置上，这个效果就相当于插入排序的第i轮原本需要比较O(i)次的，现在只需要O(1)了。 </p>
<p>但是，为什么基排能够达到这个效果呢？上面只是解释了过程，解释了过程不代表解释了本质。 </p>
<p>当i张牌放到位之后，放置第i+1张牌的时候有多少种可能性？大约i+1种，因为前i张牌将13个位置分割成了i+1个区间——第i+1张牌可以落在任意一个区间。所以放置第i+1张牌就好比是询问这样一个问题：“这张牌落在哪个区间呢？”而这个问题的答案有i+1种可能性？所以它就将剩下来的可能性均分成了i+1份（换句话说，砍掉了i/i+1的可能性！）。再看看基于比较的排序吧：由于每次比较只有两种结果，所以最多只能将剩下的可能性砍掉一半。 </p>
<p>这就是为什么基排要快得多。而所有基于比较的排序都逃脱不了NlogN的宿命。 </p>
<p><strong>4. </strong><strong>信息论！信息论？ </strong></p>
<p>本来呢，MacKay写那篇文章是想用信息论来解释为什么堆排慢，以及为什么快排也慢的。MacKay在他的文章中的解释是，只有提出每种答案的概率都均等的问题，才能获得最大信息量。然而，仔细一想，其实这里信息论并不是因，而是果。这里不需要用信息论就完全能够解释，而且更明白。信息论只是对这个解释的一个形式化。当然，信息论在其它地方还是有应用的。但这里其实用不着信息论这么重量级的东西（也许具体计算一些数据的时候是需要的），而是只需要一种看问题的本质视角：将排序问题看成和猜数字一样，是通过问问题来缩小/排除（narrow down）结果的可能性区间，这样一来，就会发现，“最好的问题”就是那些能够均分所有可能性的问题，因为那样的话不管问题的答案如何，都能排除掉k-1/k（k为问题的答案有多少种输出——猜数字里面是2，称球里面是3）种可能性，而不均衡的问题总会有一个或一些答案分支排除掉的可能性要小于k-1/k。于是策略的下界就被拖累了。 </p>
<p><strong>5. </strong><strong>小结 </strong></p>
<p>这的确是“小结”，因为两点： </p>
<p>1. 这个问题可以有信息论的理论解释，而信息论则是一个相当大的领域了。</p>
<p>2. 文中提到的这种看问题的视角除了用于排序、称球，还能够运用到哪些问题上（比如搜索）。</p>
<p><strong>Update(06/13/2008)</strong> : <a href="http://blog.youxu.info/">徐宥</a>在讨论中<a href="http://groups.google.com/group/pongba/msg/07493e329ed920ff">继续提到</a>：</p>
<blockquote><p>另外，这几天我重新把TAOCP 第三卷(第二版)翻出来看了看 Knuth 怎么说这个问题的, 发现真是牛大了： </p>
<p>先说性能： </p>
<p>pp148, section 5.2.3 说： </p>
<p>When N = 1000, the approximate average runiing time on MIX are      <br />160000u for heapsort       <br />130000u for shellsort       <br />80000u&#160; for quicksort </p>
<p>这里,&#160; Knuth 同学发现一般情况下 heapsort 表现很不好. 于是，在下文他就说，习题18 (pp156, 难度21) </p>
<p>(R.W.Floyd) During the selection phase of heapsort, the key K tends to      <br />be quite small, so that nearly all the comparisons in step H6 find       <br />K&lt;K_j. Show how to modify the algorithm so that K is not compared with       <br />K_j in the main loop of the computation, thereby nearly cutting the       <br />average number of comparisons in half. </p>
<p>答案里面的方法和DMK的方法是一样的。(我觉得DMK是看了这个论文或者TAoCP的) 这里说 by half，就正好和快排差不多了。 </p>
<p>再说信息论分析： </p>
<p>在5.3.1 (pp181) 高爷爷就说, “排序问题可以看成是一个树上的鸟儿排排站的问题. (还特地画了一棵树), 下一段就说, 其实这个也      <br />有等价说法, 就是信息论, 我们从称球问题说起&#8230;” </p>
<p>然后后面一直讲信息论和最小比较排序&#8230;</p>
</blockquote>
<p>高爷爷真不愧是姓高的，囧rz..</p>
<h3  class="related_post_title">你可能也会喜欢以下文章</h3><ul class="related_post"><li><a href="http://mindhacks.cn/2008/09/21/the-magical-bayesian-method/" title="数学之美番外篇：平凡而又神奇的贝叶斯方法">数学之美番外篇：平凡而又神奇的贝叶斯方法</a> (51)</li><li><a href="http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/" title="数学之美番外篇：进化论中的概率论">数学之美番外篇：进化论中的概率论</a> (13)</li><li><a href="http://mindhacks.cn/2006/10/15/cantor-godel-turing-an-eternal-golden-diagonal/" title="康托尔、哥德尔、图灵&mdash;&mdash;永恒的金色对角线(rev#2)">康托尔、哥德尔、图灵&mdash;&mdash;永恒的金色对角线(rev#2)</a> (34)</li><li><a href="http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/" title="知其所以然（三）：为什么算法这么难？">知其所以然（三）：为什么算法这么难？</a> (57)</li><li><a href="http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/" title="知其所以然（续）">知其所以然（续）</a> (42)</li><li><a href="http://mindhacks.cn/2008/09/11/machine-learning-and-ai-resources/" title="机器学习与人工智能学习资源导引">机器学习与人工智能学习资源导引</a> (10)</li><li><a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/" title="知其所以然（以算法学习为例）">知其所以然（以算法学习为例）</a> (36)</li><li><a href="http://mindhacks.cn/2008/04/18/learning-from-polya/" title="跟波利亚学解题(rev#3)">跟波利亚学解题(rev#3)</a> (23)</li></ul><hr />
<h3>订阅 Mind Hacks</h3>
<a title="用Google Reader订阅" href="http://fusion.google.com/add?feedurl=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_google.gif" style="border:0" alt="订阅到 | Google" /></a> 
<a title="用鲜果订阅" href="http://www.xianguo.com/subscribe.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_xianguo.gif" style="border:0" alt="订阅到 | 鲜果" /></a>
<br/> 
<a title="用抓虾订阅" href="http://www.zhuaxia.com/add_channel.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_zhuaxia.gif" style="border:0" alt="订阅到 | 抓虾" /></a> 
<a title="用有道订阅" href="http://reader.youdao.com/b.do?keyfrom=mindhacks&url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_yodao1.gif" style="border:0" alt="订阅到 | 有道" /></a> 
<hr />
<h3>我是你的信息过滤器</h3>
想了解作者最近在关注什么，欢迎 Follow <a href="http://weibo.com/pongba">刘未鹏pongba@微博</a>
<br/>
程序员朋友请到作者发起的 <a href="https://groups.google.com/group/pongba">TopLanguage</a> (<a href="http://mindhacks.cn/about-toplanguage/">about</a>) 社群逛逛，定有收获 :)
<br/>
想了解作者在阅读哪些书，请到 <a href="http://www.douban.com/people/pongba/">pongba@豆瓣</a>，或者直接访问以下四个豆列：<a href="http://www.douban.com/doulist/46003/">[只读经典]思维改变生活</a> | <a href="http://www.douban.com/doulist/127649/">[只读经典]思考的技术与艺术</a> | <a href="http://www.douban.com/doulist/197706/">决策与判断</a> | <a href="http://www.douban.com/doulist/176513/">机器学习与人工智能书籍资源导引</a> 。
<hr />
<p><small>
本文由 刘未鹏 发布在 <a href="http://mindhacks.cn">刘未鹏 | Mind Hacks</a>, 2008. | <a href="http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/#commenting">25 条评论</a> | 标签: <a href="http://mindhacks.cn/tags/%e6%95%b0%e5%ad%a6/" rel="tag">数学</a>, <a href="http://mindhacks.cn/tags/%e7%ae%97%e6%b3%95/" rel="tag">算法</a>, <a href="http://mindhacks.cn/tags/%e8%ae%a1%e7%ae%97%e6%9c%ba%e7%a7%91%e5%ad%a6/" rel="tag">计算机科学</a>
<br/>
转载请注明作者，出处，以及<a href="http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/">原始超链接</a>: http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/
</small></p>]]></content:encoded>
			<wfw:commentRss>http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/feed/</wfw:commentRss>
		<slash:comments>25</slash:comments>
		</item>
		<item>
		<title>跟波利亚学解题(rev#3)</title>
		<link>http://mindhacks.cn/2008/04/18/learning-from-polya/</link>
		<comments>http://mindhacks.cn/2008/04/18/learning-from-polya/#comments</comments>
		<pubDate>Fri, 18 Apr 2008 13:37:00 +0000</pubDate>
		<dc:creator>刘未鹏</dc:creator>
				<category><![CDATA[思维改变生活]]></category>
		<category><![CDATA[算法]]></category>

		<guid isPermaLink="false">http://mindhacks.cn/2008/04/18/learning-from-polya/</guid>
		<description><![CDATA[波利亚在他著名的《How To Solve It》中讲了这么一个有趣的心理学实验：

用一个缺了一条边的正方形围栏围住一只动物（狗、黑猩猩、母鸡、人类婴儿），在围栏的另一侧放上一个被试很想要的物体（对动物来说是食物，对人类婴儿来说是有趣的玩具），然后观察他们各自的行为。发现，狗在扒着围栏吠了几声发现无法通过的时候，不久便学会了从围栏的缺口的那一边绕出去，母鸡则朝着围栏一个劲的扑腾，不会想到绕弯子。此外，人类婴儿很快就学会了绕过障碍；而黑猩猩也学得很快（黑猩猩是和人类最近的灵长类亲属）。这个实验有力的证明了，动物解决问题的能力是进化而来的、天生的、硬编码在大脑的神经元网络里面的。]]></description>
			<content:encoded><![CDATA[<p><b>一些故事</b></p>
<p><a href="http://en.wikipedia.org/wiki/Polya">波利亚</a>在他著名的<a href="http://www.douban.com/subject/1456890/">《How To Solve It》</a>中讲了这么一个有趣的心理学实验：</p>
<blockquote><p>用一个缺了一条边的正方形围栏围住一只动物（狗、黑猩猩、母鸡、人类婴儿），在围栏的另一侧放上一个被试很想要的物体（对动物来说是食物，对人类婴儿来说是有趣的玩具），然后观察他们各自的行为。发现，狗在扒着围栏吠了几声发现无法通过的时候，不久便学会了从围栏的缺口的那一边绕出去，母鸡则朝着围栏一个劲的扑腾，不会想到绕弯子。此外，人类婴儿很快就学会了绕过障碍；而黑猩猩也学得很快（黑猩猩是和人类最近的灵长类亲属）。这个实验有力的证明了，动物解决问题的能力是进化而来的、天生的、硬编码在大脑的神经元网络里面的。<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image0011.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image001" border="0" alt="clip_image001" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image001-thumb1.jpg" width="102" height="154" /></a></p>
</blockquote>
<p>事实上，不仅解决问题方面是如此，人类整个认知系统中绝大部分功能从本质上都是硬编码的，能在后天习得的只是“程度”的不同，而不是“本质”的不同。<a href="http://www.douban.com/subject/1712350/">《动机心理学》</a>中有一个令人印象深刻的一个例子： </p>
<blockquote><p>先给小鼠喝某种甜味水（称为“可口水”），然后用X射线促使其产生反胃感，能使小鼠形成对这种味道的水的厌恶和回避（经典条件反射）。但如果不是在水里面加味道，而是在它喝水的时候伴随强光刺激（即让它喝“光噪水”），然后同样刺激其反胃，却无法使它养成对“光噪水”的厌恶。另一方面，如果不是促使其反胃（身体不适），而是用电击惩罚，则它无法形成对“可口水”的厌恶，而是形成对“光噪水“的厌恶。显然，小鼠对事件之间的关联的归因也具有着某种硬编码好了的倾向。在这个例子中，老鼠的大脑里面硬编码了“将身体不适（内部事件）归因于食物而不是闪光”、”将电击（外部事件）归因于闪光而非食物” 这种逻辑。</p>
</blockquote>
<p>而人类也有类似的归因倾向。金出武雄在<a href="http://www.douban.com/subject/1867455/">《像外行一样思考，像专家一样实践》</a>中也提到，他认为人类的直觉实际上也是计算，捷径式的计<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image002.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image002" border="0" alt="clip_image002" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image002-thumb.jpg" width="101" height="155" /></a>算，只不过由于我们目前还不了解人类大脑内神经元的全部结构（或者说“感性”的物质基础）这才把“感性”当成人类所特有的；金出武雄的这种观点跟心理学中的认知捷径不谋而合。实际上，越是高等的动物，大脑中用于处理特定问题的硬编码神经元回路就越是多和复杂。例如，达尔文早在<a href="http://www.douban.com/subject/1049670/">《人类和动物的情绪表达》</a>中就先知先觉的提出了动物情绪的适应价值；<a href="http://www.douban.com/subject/1128662/">《Mean Genes》</a>列出了用于解决生存繁衍问题的特定认知倾向；<a href="http://www.douban.com/subject/1193621/">《决策与判断》</a>里面则列出了人类在解决更具一般性的决策问题中的一些系统性的、可预测的认知偏差；而<a href="http://www.douban.com/subject/2990015/">《Predictably Irrational》</a>更是把这个认识提高到方法论的层面，主张人类的非理性实际上是完全可预知的。事实上，所有这些观点都建立在一个基本事实的基础上，即人类大脑中的千亿神经元是由在漫长的进化过程中被塑造出来的分工明确的、<a href="http://en.wikipedia.org/wiki/Ad_hoc">ad hoc</a>的一组子系统构成的。</p>
<p>越是高等的动物，解题能力越高，猩猩能够进行某种顿悟，在脑子里就构想出通过堆放墙角的箱子来帮助获取高高吊着的香蕉；而出于进化之树 顶端的人类则具有非比寻常的大脑，在人类整个进化的过程中，解决问题的能力一直在进化，所以说人脑中的神经元最重要的部分是为了解题而存在的也不为过。不同的人只是在解题能力程度上不同，并没有本质上能与不能的差异。</p>
<p>波利亚在《How To Solve It》中另外还举了下面这个例子：</p>
<p>一个原始人站在一条小溪前，他想要越过这条小溪，但溪水经过昨天一夜，已经涨了上来；因此他面临一个问题：如何越过这条小溪。他联想起以前曾经从一棵倒下并横在河上的树木上走过去，于是他的问题变成了如何找到这样一颗倒下并横在溪流上的树木。他环顾四周，发现溪流上没有这样的横着的树木，但他发现周围倒是有不少生长着的树木；于是问题再次变成了：如何使这些树木躺到溪流上。</p>
<p>在这个想像的故事中我们看到了一个问题是如何被一步步<a href="http://en.wikipedia.org/wiki/Reduction_%28complexity%29">归约</a>的：首先，原始人通过对一个已知的类似问题的联想认识到一个重要的性质：如<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image003.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image003" border="0" alt="clip_image003" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image003-thumb.jpg" width="102" height="154" /></a>果有一棵树横在河上，我就可以借助这棵树过河。这就将一个无法直接解决的问题转化为了一个新的、已知的、并容易解决的问题。值得注意的是这里“联想”是极其重要的一个环节，联想可以将手上的问题与已知的类似问题联系起来，并从后者中吸取能够利用的方法。联想也能够将与问题有关的定理或性质从大脑的知识系统中提取出来。基本上，如果一个联想能够得到某个性质，而这个性质能够或者将问题往上归约一层，或者将条件往下推导一层，这个联想就是有用的。事实上，如果你仔细注意以下解题的过程，你也许会发现，所有的启发式思维方法（<a href="http://en.wikipedia.org/wiki/Heuristics">heuristics</a>）实质上都是为了联想服务的，而联想则是为了从我们大脑的知识系统中提取出有价值的性质或定理，从而补上从条件到结论、从已知到未知之间缺失的链环。</p>
<p><b>一段历史</b></p>
<p>实际上，人类自从进入理性文明以来，不仅在不断的解题，还在不断的对自身的解题方法进行反省和总结。在这条路上，有一个真正光荣与辉煌的梦想，那就是发现人类解题的所有<a href="http://en.wikipedia.org/wiki/Heuristic">一般性法则</a>，并借此建造出一台能够解决人类能够解决的所有问题的<a href="http://en.wikipedia.org/wiki/General_Problem_Solver">一般解题机</a>。与物理中的建造永动机不一样，这个梦想并非遥不可及的，自从古希腊哲学家对人类心智的反省思考以来，许多著名的数学和哲学家为此建造了阶梯，<a href="http://en.wikipedia.org/wiki/Pappus_of_Alexandria">Pappus</a>，亚历山大学派最后一位伟大的几何学家，就曾在他恢弘的八卷本《数学汇编》中描述了其中的一种法则，他将它称为“分析与综合”，大意如下：</p>
<p>首先我们把需要求解的问题本身当成条件，从它推导出结论，再从这个结论推导出更多的结论，直到某一个点上我们发现已经出现了真正已知的条件。这个过程称为分析。有了这条路径，我们便可以从已知条件出发，一路推导到问题的解。</p>
<p>波利亚在他的三卷本中把这种做法叫做Working Backwards（倒过来解）。</p>
<p><a href="http://en.wikipedia.org/wiki/Ren%C3%A9_Descartes">笛卡尔</a>也曾经试图将人类思维的规则总结为36条（最终完成了<a href="http://en.wikisource.org/wiki/Rules_for_the_Direction_of_the_Mind">21条</a>）。<a href="http://en.wikipedia.org/wiki/Gottfried_Leibniz">莱布尼兹</a>，现代计算机实质上的发明者，也说到：</p>
<p>在我看来，没有什么能比探索发明的源头还要重要，它远比发明本身更重要。</p>
<p>再后来，捷克数学家<a href="http://en.wikipedia.org/wiki/Bernard_Bolzano">波尔查诺</a>也试图总结人类思维的本质规律，他在他的著作《科学的理论》中写道：</p>
<p>我根本不奢望自己能够提供任何超于其他天才所使用过的科学探索方法之外的新方法，从这个意义上，你别指望能在书中看到什么新的东西。但是，我会尽我的全力去总结所有伟大的思想者们共有的、思维的原则和方法，我认为即便是他们自己在思考的时候也未必全都意识到自己在使用什么方法。</p>
<p>再后来，就到了近代，随着科学技术的进步，心理学最活跃的子学科——<a href="http://en.wikipedia.org/wiki/Cognitive_science">认知科学</a>——开始辉煌起来，人类开始向思维乃至自我意识的物质基础发起进攻。两位多才多艺的计算机科学家兼认知科学家，<a href="http://en.wikipedia.org/wiki/Herbert_Simon">Herbert Simon</a>（另外还是经济学家）和<a href="http://en.wikipedia.org/wiki/Allen_Newell">Allen Newell</a>写出了世界上第一个<a href="http://en.wikipedia.org/wiki/General_Problem_Solver">一般性解题机</a>的程序（GPS），虽然GPS只能解决很狭窄的一类问题，但这是第一个将“问题解决策略”和“知识”分离开来的程序。显然，在知识之外，人类的思维是有着一些一般性的指导规则的。事实上，波利亚在《数学与猜想》中写道，欧拉是最重数学思维的教学的，欧拉认为如果不能把解决数学问题背后的思维过程教给学生的话，数学教学就是没有意义的。</p>
<p><b>一些方法</b><a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image0041.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image004" border="0" alt="clip_image004" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image004-thumb1.jpg" width="104" height="152" /></a></p>
<p>这些一般性的思维方法，就是波利亚用了整整三本书，五卷本（<a href="http://www.douban.com/subject/1456890/">《How To Solve It》</a>、<a href="http://www.douban.com/subject/1850407/">《数学的发现》</a>、<a href="http://www.douban.com/subject/1134230/">《数学与猜想》</a>）来试图阐明的。波利亚的书是独特的，从小到大，我们看过的数学书几乎无一不是欧几里德式的：从定义到定理，再到推论。是属于“顺流而下”式的。这样的书完全而彻底的扭曲了数学发现的真实过程。举个例子，<a href="http://www.douban.com/subject/2124368/">《证明与反驳：数学发现的逻辑》</a>在附录一中讲了一个非常有趣的例子：<a href="http://en.wikipedia.org/wiki/Cauchy">柯西</a>当年试图将函数的连续性从单个函数推广到无穷级数上面去，即证明由无穷多个连续函数构成的收敛级数本身也是一个连续的函数，柯西给出了一个巧妙的证明，似乎漂亮地解决了这个问题。然而傅立叶却给出了一个噩梦般的三角函数的收敛级数，它的和却并不是连续的。这令柯西大为头疼，以至于延迟了他的数学分析教程的出版好些年。后来，赛德尔解决了这个问题：原来柯西在他看似无懈可击的证明中非常隐蔽（他自己也不知觉的情况下）引入了一个潜在的假设，这个假设就是后来被称为的“一致收敛”条件。当时我看到这里就去翻我们的数学分析书，发现“一致收敛”这个概念第一次出现的时候是这样写的：定义：一致收敛&#8230;</p>
<p>所以说，从这个意义上，<a href="http://www.douban.com/subject/1049136/">《数学，确定性的丧失》</a>从历史的角度再现了真实的数学发展过程，是一本极其难得的好书。而事实上，<a href="http://www.math.nmsu.edu/%7Ehistory/">从真实的数学 历史发展的角度去讲授数学</a>，也是<a href="http://en.wikipedia.org/wiki/Mathematics_education#Methods">数学教学法</a>的最佳方法。不过，《数学，确定性的丧失》的弱点是并没有从思维的角度去再现数学发现的思维过程，而这正是波利亚所做的。</p>
<p>总结波利亚在书中提到的思维方法，尤其是《How To Solve It》中的启发式思考方法，有这样一些：<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image005.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image005" border="0" alt="clip_image005" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image005-thumb.jpg" width="103" height="152" /></a></p>
<ul>
<li><b>时刻不忘未知量</b>（即时刻别忘记你到底想要求什么，<a href="http://www.douban.com/subject/1135754/">问题是什么</a>。）莱布尼兹曾经将人的解题思考过程比喻成晃筛子，把脑袋里面的东西都给抖落出来，然后正在搜索的注意力会抓住一切细微的、与问题有关的东西。事实上，要做到能够令注意力抓住这些有关的东西，就必须时刻将问题放在注意力层面，否则即使关键的东西抖落出来了也可能没注意到。</li>
<li><b>用特例启发思考</b>。一个泛化的问题往往给人一种无法把握、无从下手、或无法抓住里面任何东西的感觉，因为条件太泛，所以看起来哪个条件都没法入手。一个泛化的问题往往有一种 “不确定性”（譬如元素的个数不确定，某个变量不确定等等），这种不确定性会成为思维的障碍，通过考虑一个合适的特例，我们不仅使得问题的条件确定下来从而便于通过试错这样的手法去助探问题的内部结构，同时很有可能我们的特例中实质上隐藏了一般性问题的本质结构，于是我们便能够通过对特例的考察寻找一般问题的解。</li>
<li><b>反过来推导</b>。反过来推导是一种极其重要的启发法，正如前面提到的，Pappus在他的宏篇巨著中将这种手法总结为解题的最重要手法。实际上，反向解题隐含了解题中至为深刻的思想：归约。归约是一种极为重要的手法，一个著名的关于归约的笑话这样说：有一位数学家失业了，去当消防员。经过了一些培训之后，正式上任之前，训练的人考他：如果房子失火了怎么办？数学家答出了所有的正确步骤。训练人又问他：如果房子没失火呢？数学家答：那我就把房子点燃，这样我就把它归约为了一个已知问题。人类思维本质上善于“顺着”推导，从一组条件出发，运用必然的逻辑关系，得出推论。然而，如果要求的未知量与已知量看上去相隔甚远，这个时候顺着推实际上就是运用另一个启发式方法——试错——了。虽然试错是最常用，又是也是最有效的启发法，然而试错却并不是最高效的。对于许多题目而言，其要求的结论本身就隐藏了推论，不管这个推论是充分的还是必要的，都很可能对解题有帮助。如果从结论能够推导出一个充要推论，那么实际上我们就将问题进行了一次“双向”归约，如果原问题不容易解决，那么归约后的问题也许就容易解决了，通过一层层的归约，让逻辑的枝蔓从结论上一节节的生长，我们往往会发现，离已知量越来越近。此外，即便是从结论推导出的必要非充分推论（“单向”归约），对问题也是有帮助的——任何不满足这个推论的方案都不是问题的解：譬如通过驻点来求函数的最值，我们通过考察函数的最值（除了函数边界点外），发现它必然有一个性质，即在这个点上函数的一阶导数为0，虽然一阶导数为0的点未必是最值点，但我们可以肯定的是，任何一阶导数不为0的点都可以排除，这就将解空间缩小到了有穷多个点，剩下的只要做做简单的排除法，答案就出现了。再譬如线性规划中经典的<a href="http://en.wikipedia.org/wiki/Simplex_algorithm">单纯形算法</a>（又见<a href="http://www.douban.com/subject/1996256/">《Algorithms》</a>），也是通过对结论的考<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image006.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image006" border="0" alt="clip_image006" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image006-thumb.jpg" width="112" height="140" /></a> 察揭示出只需遍历有限个顶点便必然可以到达最值的。此外很多我们熟知的经典题目也都是这种思路的典范，譬如《How To Solve It》上面举的例子：通过一个9升水的桶和一个4升水的桶在河里取6升水。这个题目通过正向试错，很快也能发现答案，然而通过反向归约，则能够不偏不倚的命中答案。另一些我们耳熟能详的题目也是如此，譬如：100根火柴，两个人轮流取，每个人每次只能取1~7根，谁拿到最后一根火柴谁赢；问有必胜策略吗，有的话是先手还是后手必胜？这个问题通过试错就不是那么容易发现答案了。同样，这个问题的推广被收录在<a href="http://www.douban.com/subject/3004255/">《编程之美》</a>里面：两堆橘子，各为m和n个，两人轮流拿，拿的时候你只能选择某一堆在里面拿（即不能跨堆拿），你可以拿1~这堆里面所有剩下的个橘子，谁拿到最后一个橘子谁赢；问题同上。算法上面很多聪明的算法也都是通过考察所求结论隐藏的性质来减小复杂度的，譬如刚才提到的单纯形问题，譬如经典面试题“名人问题”、“和最小（大）的连续子序列”等等。倒推法之所以是一种极为深刻的思维方法，本质上是因为它充分利用了题目中一个最不易被觉察到的信息——结论。<strong>结论往往蕴含着丰富的条件，譬如对什么样的解才是满足题意的解的约束。一般来说，借助结论中蕴含的知识，我们便可以更为“</strong><strong>智能地”</strong><strong>搜索解空间</strong>。举一个直白的例子，有人要你在地球上寻找一栋满足如下条件的建筑：__层高（填空自己填），__风格，__年代始建，&#8230; （省略若干约束条件）。对于这样一个问题，最平凡的解法是穷举地球上每一栋建筑，直到遇到一个满足条件的为止。而更“智能”的（或者说更“启发”的）方法则是充分利用题目里面的约束信息，譬如假若条件里面说要60层楼房，你就不会去非洲找，如果要拜占庭风格的，你估计也不会到中国来找，如果要始建于很早的年代的，你也不会去非常新建的城市里面去找，等等。倒推法是如此的重要，以至于笛卡尔当时认为可以把一切问题归结为求解代数方程组，笛卡尔的万能解题法就是首先将问题转化为代数问题，然后设出未知数，列出方程，最后解这组（个）方程。其中设未知数本质上就是一种倒推：通过设出一个假想的结论x，来将题目对x的需求表达出来，然后顺势而下推导出x。仔细想想设未知数这种手法所蕴含的深刻思想，也就难怪笛卡尔会认为它是那个解决所有问题的一般性钥匙了。</li>
<li><b>试错</b>。试错估计是世界上被运用最广泛的启发法，你拿到一个题目，里面有一些条件，你需要求解一个未知量。于是你对题目这里捅捅那里捣捣，你用上所有的已知量，或使用所有你想到的操作手法，尝试着看看能不能得到有用的结论，能不能离答案近一步。事实上，如果一个问题的状态空间是有限的话，往往可以通过穷举所有可能性来找到那个关键的性质。譬如这样一个问题：有一个囚犯，国王打算处决他，但仁慈的国王给了他一个生还的机会。现在摆在他面前有两个瓶子，一个里面装了50个白球，一个装了50个黑球，这个囚犯有一个机会可以随便怎样重新分配这些球到两个瓶子中（当然，要保证不空），分配完了之后囚犯被蒙上眼睛，国王随机取一个瓶子给他，他在里面摸出一个球（因为蒙着眼睛，所以也是随机抽取），如果白球，则活，否则挂掉。问，这个囚犯如何分配，才能最大化生还几率。结合特例和试错法，这个题目的答案是很容易发现的。这样的题目还有很多。实际上，历史上很多有名的发现也都是<a href="http://www.xiaolai.net/?p=904">无意间发现的</a>（可以看作是试错的一种）。</li>
<li><b>调整题目的条件</b>（如，删除、增加、改变条件）。有时候，通过调整题目的条件，我们往往迅速能够发现条件和结论之间是如何联系的。通过扭曲问题的内部结构，我们能发现原本结构里面重要的东西。譬如这样一个题目（感谢alai同学提供）：A国由1000000个岛组成，岛与岛之间只能用船作为交通工具，有些岛之间有船来往，从任意一个岛都可以去到另外任一个岛，当然其中可能要换船。现在有一个警察要追捕一个逃犯，开始时他们在不同的岛上，警察和逃犯都是每天最多乘一次船，但这个逃犯还有点迷信，每个月的13日不乘船，警察则不迷信。警察每天乘船前都知道逃犯昨天在哪个岛上，但不知道他今天会去哪个岛。请证明，警察一定可以抓到逃犯（即到达同一个岛）。通过拿掉题目中一个关键的条件，观察区别，然后再放上那个条件，我们就能“感觉”到题目的内在结构上的某种约束，进而得到答案。</li>
<li><b>求解一个类似的题目</b>。类似的题目也许有类似的结构，类似的性质，类似的解方案。通过考察或回忆一个类似的题目是如何解决的，也许就能够借用一些重要的点子。然而如何在大脑中提取出真正类似的题目是一个问题。所谓真正类似的题目，是指那些抽象结构一样的题<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image007.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image007" border="0" alt="clip_image007" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image007-thumb.jpg" width="103" height="152" /></a>目。很多问题表面看是类似的，然而抽象结构却不是类似的；另一些题目表面看根本不像，然而抽象层面却是一致的。表面一致抽象不一致会导致错误的、无效的类比；而表面不一致（抽象一致）则会阻碍真正有用的类比。<a href="http://www.douban.com/subject/2845839/">《Psychology of Problem Solving》</a>里面对此有详细 的介绍。后面也会提到，为了便于脑中的知识结构真正能够“迁移”，在记忆掌握和分析问题的时候都应该尽量抽象的去看待，这样才能够建立知识的本质联系，才能够最大化联想空间。</li>
<li><b>列出所有可能跟问题有关的定理或性质</b>。这个不用说，我们在最初学习解题的时候就是这么做的了。</li>
<li><b>考察反面，考察其他所有情况</b>。很多时候，我们在解题时容易陷入一种特定的手法，比如为什么一定要是构造式的来解这个题目呢？为什么不能是逼近式的？为什么一定要一步到位算出答案？为什么不能从一个错误的答案调整到正确答案？为什么这个东西一定成立？不成立又如何？等等。经典例子：100个人比赛，要决出冠军至少需要赛多少场。</li>
<li><b>将问题泛化，并求解这个泛化后的问题</b>。刚才不是说过，应该通过特例启发思考吗？为什么现在又反倒要泛化呢？实际上，有少数题目，泛化之后更容易解决。即，解决一类问题，比解决这类问题里面某个特定的问题还要容易。波利亚称之为“发明者悖论”，关于“发明者悖论”，《数学与猜想》第一卷的开头有一个绝妙的例子，可惜这里空间太小，我就不摘抄了- _-||| <a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image008.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image008" border="0" alt="clip_image008" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image008-thumb.jpg" width="104" height="151" /></a></li>
</ul>
<p>以上是我认为最重要的，也是最具一般性的、放之四海都可用的思维法则。一些更为“问题特定”的，或更为现代的启发法，可以参见<a href="http://www.douban.com/subject/1232071/">《如何解题：现代启发式方法》</a>以及所有的<a href="http://www.douban.com/people/pongba/booktags/%E7%AE%97%E6%B3%95">算法书</a>。不过，在结束这一节之前，还有两个有趣的启发法值得一提： </p>
<ul>
<li><b>下意识孵化法</b>。这个方法有点像老母鸡孵小鸡的过程：我们先把问题的吃透，放在脑子里，然后等着我们的下意识把它解出来。不过，不宜将这个方法的条件拉伸过远，实际上，除非能够一直保持一种<a href="http://blog.csdn.net/pongba/archive/2007/05/24/1624382.aspx">思索的状态</a>（金出武雄所谓“思维体力”），或者问题很简单，否则一转头去做别的事情之后，你的下意识很容易就把问题丢开了。据说庞加莱有一次在街上，踏上一辆马车的那一瞬间，想出了一个重要问题的解。其他人也像仿效，结果没一个人成功。实际上，非但马车与问题无关，更重要的是，庞加莱实际上在做任何事的时候除了投入有限的注意力之外，其他思维空间都让给了那个问题了。同样，阿基米德从浴缸里面跳出来也是如此；如若不是经过了极其痛苦和长时间的思索，也不会如此兴奋。如果你也曾经花过几天的时间思考一个问题，肯定也是会有类似的经历的。</li>
<li><b>烫手山芋法</b>。说白了，就是把问题扔给别人解决。事实上，在这个网络时代，这个方法有着无可比拟的优越性。几乎任何知识性的问题，都可以迅速搜索或请教到答案。不过，如何在已知知识之外发掘出未知知识，如何解决未知问题，那就还是要看个人的能力了。数学界流传一个与此有关的笑话：如果你有一个未解决问题，你有两个办法，一，自己解决它。二，让<a href="http://en.wikipedia.org/wiki/Terence_Tao">陶哲轩</a>对它感兴趣。<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image009.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image009" border="0" alt="clip_image009" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image009-thumb.jpg" width="103" height="153" /></a></li>
</ul>
<p>除了波利亚的书之外，陶哲轩的<a href="http://www.douban.com/subject/1859573/">《Solving Mathematical Problems》</a>也对解题的启发式思路作了极有意义的介绍，他在书的第一章遵循波利亚的思路从一个具体的题目出发，介绍了如何运用波利亚在书中提到的各种启发式方法来对解题进行尝试。</p>
<p>总而言之，充分挖掘题目中蕴含的知识，是解题的最关键步骤。本质上，所有启发式方法某种意义上都是为此服务的。这些知识，有些时候以联想的方式被挖掘出来，此时启发式方法充当的便是辅助联想的手段。有时候则以演绎和归纳的手法被挖掘出来，此时启发式方法则充当助探（辅助探索）工具。 </p>
<p><b>一点思考</b></p>
<p><b>1. 联想的法则</b></p>
<p>人类的大脑是一个复杂而精妙的器官，然而某种程度上，人类的大脑也是一个愚蠢的器官。如果你总结过你解过的一些有意义的好题目，你会发现它们有一个共同点：没有用到你不知道的知识，然而那个最关键的、攸关成败的知识点你就是想不到。所以你不禁要问，为什么明明这个知识在我脑子里（也就是说，明明我是“能够” 解决这个问题的），但我就是没法想到它呢？“你是怎么想到的？”这是问题解决者最常问的一个问题。甚至对于熟练的解题者来说，这个问题的答案也并不总是很明确的，很可能他们自己也不清楚那个关键的想法是怎么“蹦”出来的。我们在思考一个问题的时候，自己能意识到的思维部分似乎是很少的，绝大多数时候我们能感知到的就是一个一个的转折点在意识层面显现，我们的意识就像一条不连续的线，在其上的每一段之间那个空档内发生了什么我们一无所知，往往我们发现被卡在一个地方，我们苦思冥想，然后一个知识（也许是一个性质，也许是一个定理）从脑子里冒了出来，或者说，被我们意识到，然后我们沿着这条路走一段，然后又卡住，然后又等待一个新的关键知识的出现。而至于这些知识是怎么冒出来的？我们可以对它们的“冒出来”提供怎样的帮助？我们可以在意识层面做一些工作，帮助我们的下意识联想到更多重要的知识吗？那些灵光一现的瞬间，难道只能等待它们的出现？难道我们不能通过一些系统化的步骤去“捕获”或“生成”它们？又或者我们能不能至少做些什么工作以使得它们更容易发生呢？</p>
<p>正如金出武雄在《像外行一样思考，像专家一样实践》中所说的，人类的灵感一定是有规律的，认知科学目前至少已经确认了人类思维的整个物质基础——神经元。而既然它们是物质，自然要遵循物质的运行规律。只不过我们目前还没有窥破它们，但至少我们可以确信的是，它们在那里。事实上，不需要借助于认知科学，单单是通过对我们自己思维过程的自我观察，也许就已经能够总结出一些重要的规律了，也许，对自身思维过程的反观真的是人有别于其它动物的本质区别。</p>
<p><a href="http://www.douban.com/subject/2296845/">《专注力》</a>当中有这样一个例子：一天夜里，你被外面的吵闹声叫醒了，你出去一看，发现有一群人，其中有一个人开着很名贵的轿车，他跟你说他们正在玩一个叫“拾荒者”的游戏，由于一些原因，他必须要赢这个游戏，现在他需要一块1.5m*1m的木板，如果你能帮忙的话，愿以一万美元酬报。你怎么办？被测试的大多数人都没有想到，只要把门拆给他就可以了（如果你想到了，祝贺你:-)），也许你会说现在的门都是钢的，没关系，那你有没有想到床板、立柜的门、大桌子的桌面之类的？这个问题测试的就是心理学上所谓的“范畴陷阱”，“木板”这个名词在你脑子里的概念中如果是指“那些没有加工的，也许放在木材厂门口的，作为原材料的木板”的话，那么“木板”就会迅速在你的下意识里面建立起一个搜索范畴，你也会迅速的反应到“这深更半夜叫我上哪去找木板呢？”如果你一下就想到了，那么很大的可能性是“木板”这个概念在你脑子里的范畴更大，更抽象，也许包含了所有“木质的、板状的东西”。</p>
<p>这就是联想的法则。</p>
<p>我们的大脑无时无刻不在对事物进行归类，实际上，不仅是事物，一切知识，都在被自动的归类。在有关对世界的认知方面，被称为<a href="http://en.wikipedia.org/wiki/Schema_%28psychology%29">认知图式</a>，我们根据既有的知识结构来理解这个世界，会带来很大的优势。实际上，<a href="http://www.sciam.cn/article.php?articleid=334">模块化</a>是一个重要的降低复杂性的手段。然而，<b>知识是一把双刃剑，一方面，它们提供给了我们解决问题的无以伦比的捷径优势</b>，“砖头是砌墙的”，于是我们遇到砌墙这个问题的时候就可以迅速利用砖头。然而<b>另一方面，知识却也是思维的桎梏</b>。思维定势就是指下意识遵循既有知识框架思考的过程。上面的那个木板的例子也是思维定势的例子。每一个知识都是一个优势，同时又是一个束缚。著名的科幻作家<a href="http://en.wikipedia.org/wiki/Arthur_C._Clarke">阿瑟·克拉克</a>有一句名言：如果一位德高望重的老科学家说某个事情是不可能的，那么他很可能是错的。所以，如何在获取知识优势的同时，防止被知识束缚住，是一门技术。</p>
<p>掌握这门技术的钥匙，就是抽象。在吸收知识的时候进行抽象，同时在面对需要用到知识的新问题时也要对问题进行抽象。就以大家都知道的“砖头”有多少种用途为例，据说这道题目是用于测试人的发散思维的，能联想到的用途越多，思维定势就越小。实际上，借助于抽象这个利器，这类题目（乃至更广的一类问题）是可以系统性的进行求解的，我们只需对砖头从各个属性维度进行抽象。譬如，砖头是——长方形的（长方形的东西有什么用途？还有哪些东西也是长方形的，它们都有什么用途？）、有棱角的（问题同上）、坚硬的、固体、有一定大小的体积的、红色的、边界线条平直的、有一定重量的&#8230; 对于每一个抽象，我们不妨联想还有其他什么物体也是具有同样抽象性质的，它们具有同样的用途吗？当然，除了抽象之外，还有“修改”，我们可以在各个维度上对砖头的属性进行调整，以期得到新的属性：譬如大小可以调整、固体可以调整为碎末、棱角可以打磨、重量也可以调整、形状也可以调整&#8230; 然后看看新的属性可以如何联想开去。</p>
<p>除了这个简单的例子之外，我们也不妨看一看一些算法上的例子，同样一个算法，不同的人来理解，也许你脑子里记得的是某个特定的巧妙技巧（也许这个技巧在题目的某步关键的地方出现，从而带来了最令人意外的转折点），然而另一人个记得得也许是“递归”这种手法，还有另外一个人记得的也许是“分治”这种更一般化的解题思路。从不同的抽象层面去掌握这道题目的知识信息，以后遇到类似的问题，你能够想起这道题所提供的知识的可能性是有极大的差异的。《Psychology of Problem Solving》的第11章举了这样一个例子：先让被试（皆为大学生）阅读一段军事材料，这个材料是说一小撮军队如何通过同时从几个不同方向小规模攻击来击溃一个防守严实的军事堡垒的。事实上这个例子的本质是对一个点的同时的弱攻击能够集聚成强大的力量。然后被试被要求解决一个问题：一个医生想要用X射线杀死一个恶性肿瘤，这个肿瘤只可以通过高强度的X射线杀死，然而那样的话就会伤及周围的良好组织。医生应该怎么办呢？在没有给出先前的军队的例子的被试中只有10%想到答案，这是控制基线。然后，在先前学习了军队例子的被试中，这个比例也仅仅只增加到30%，也就是说只有额外20%的人“自动”地将知识进行了转移。最后一组是在提醒之下做的，达到了75%，即比“自动”转移组增加了45%之多。这个例子说明，知识的表象细节会迷惑我们的眼睛，阻碍我们对知识的运用，在这个例子中是阻碍问题之间的类比。</p>
<p>而抽象，则正是对非本质细节去枝减叶的过程，抽象是我们在掌握知识和解决问题时候的一把有力的<a href="http://en.wikipedia.org/wiki/Occam%27s_Razor">奥卡姆剃刀</a>。所以，无论是在解题还是在学习的过程中，问自己一个问题“<b>我是不是已经掌握了这个知识最深刻最本质的东西</b>”是非常有益的。</p>
<p><b>2. 知识，知识</b></p>
<p>如果你是一个熟练的解题者，你也许会发现，除了一些非常一般性的、本质的思维法则之外，将不同“能力”的解题者区分开来的，实际上还是知识。知识是解<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image010.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image010" border="0" alt="clip_image010" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image010-thumb.jpg" width="105" height="150" /></a>题过程中的<a href="http://en.wikipedia.org/wiki/Rosetta_Stone">罗塞塔碑石</a>。一道几何题为什么欧几里德能够做出来我们不能，是因为欧几里德比我们所有人都更了解几何图形有哪些性质，借助于一个性质，他很容易就能抵达问题的彼岸；反之，对于不知道某个性质的我们，倒过来试图“发现”需要这样的性质有时几乎是不可能的。有人说数学是在黑暗中摸索的学科，是有道理的。并不是所有的问题都能够通过演绎、归纳、类比等手法解出来的。这方面，费马大定理就是一个绝好的例子，<a href="http://www.douban.com/subject/1322358/">《费马大定理：一个困惑了世间智者358年的谜》</a>一书描述了费马大定理从诞生到被解决的整个过程，事实上，通过对费马大定理本身的考察，几乎是毫无希望解决这个问题的，我们根本不能推导出“好，这里我只需要这样一个性质，就可以解决它了”，也许大多数时候我们可以，但那或者是因为我们有已知的知识，或者这样的归约很显然。而对于一些致命的问题，譬如费马大定理，最重要的归约却是由别人在根本不是为了解决费马大定理的过程中得出来的。运气好的话，我们在既有的知识系统中会有这样的定理可以用于归约，运气不好的话，就得去摸索了。</p>
<p>所幸的是，绝大多数问题并不像费马大定理这样难以解决。而且绝大多数问题需要用到的知识，在现有的知识系统里面都是存在的。我们只要掌握得足够好，就有希望联想起来，并用于解题。</p>
<p>当然，也有许多题目，求解它们的那个关键的知识可以通过考察题目本身蕴涵的条件来获得，这类题目就是测试思维本身的能力的好题目了。而如果这个性质根本无法通过对题目本身的考察得出来，那么这个题目测试的就是知识储备以及联想能力。</p>
<p><b>3. 好题目、坏题目</b></p>
<p>在我看来，好题目即测试一个人思维的习惯的题目（因为知识性的东西是更容易弥补的，尤其是在这样一个年代；而好习惯不是一朝一夕养成的），它应有这样一些性质：</p>
<ul>
<li>不需要用到未知的知识，或者</li>
<li>需要用到未知的知识，但一个敏锐的解题者可以通过对题目的分析自行发现这些所需的知识。</li>
<li>考察解题的一般性思路，而不是特定（<a href="http://en.wikipedia.org/wiki/Ad_hoc">ad hoc</a>）的解题技巧，尤其是当这个技巧几乎不可能在短时间内通过演绎和试错发现的时候。譬如题目需要用到某种性质，而这个性质对于不知道它的人来说几乎是无法从对题目的考察中得出来的。</li>
<li>考察思维能力：联想能力、类比能力、抽象能力、演绎能力、归纳能力、观察能力、发散能力（思维不落巢臼的能力）。</li>
<li>考察一般性的思维方法：通过特例启发思考、通过试错寻找规律、通过泛化试探更一般性命题、通过倒过来推导将问题进行归约、通过调整（分解、删除、增加等等）题目的条件来感知它们之间的联系以及和结论的联系、通过系统化的分类讨论来覆盖每种可能性。</li>
<li>好题目举例：烙饼排序问题（考察特例启发法以及观察能力）、Nim问题（还有简单版本的取火柴问题）（烙饼排序问题和Nim问题可参见<a href="http://www.douban.com/subject/3004255/">《编程之美》</a>）、9公升4公升水桶倒6公升水的问题（考察倒过来思考问题的能力）、<a href="http://en.wikipedia.org/wiki/Thinking_outside_the_box">9点连线问题</a>、6根火柴搭出4个面的问题、“木板”问题（考察思维定势，此外《心理学与生活》的第九章也有好几个经典的问题）、许多数论问题（观察能力、演绎能力、归纳能力）。此外，<a href="http://groups.google.com/group/pongba">我们</a>最近也在<a href="http://blog.csdn.net/pongba/archive/2008/04/09/2270171.aspx">讨论好题目</a>。</li>
</ul>
<p>而坏题目呢：</p>
<ul>
<li>好题目各有各的好，坏题目都是相似的。</li>
<li>坏题目基本上就是指那些所谓的 unfair questions，什么是unfair，举个例子：一个人住在一栋非常高的楼上，每天早晨他乘电梯下到一楼，出门上班。但晚上回来之后却最多只能坐到一半高度的楼层，剩下一半只能走楼梯上去，除非是下雨天。问为什么。这个例子据说不少人小时候在脑筋急转弯里面做过，但我很怀疑基本上任何正常人是不是可能想出来。这个问题的问题在于他需要用到千百个有可能与问题有关的性质中的一个，而且这个性质还根本无法通过对题目本身的考察得出来，只可能某天我们碰巧遇到类似的场景也许才能想到。知道答案的人也许会说答案很显然，但别忘了心理学上的<b>事后偏见</b><b>——</b><b>一旦知道结果之后，所有指向结果的证据看上去都那么显然和充分，而同时所有反结果的证据看起来都那么不显然和不充分</b>。譬如这题关键是要想到这人是矮子和雨天要带伞，也许你会说“只要考虑一下电梯的按钮面板就会发现了”，或者“看到下雨，那还不想到带伞么？”，然而这只是事后的合情推断。在不知道答案的情况下，这个故事中有数不清的因素可能会成为问题的解释，除非某天我们碰到类似的问题，否则大致也只能一个个穷举了去使劲往上凑，譬如除了身高之外还有：是不是瞎子、是不是聋子、是不是哑子、男人女人、什么牌子的电梯、大厦是哪种大厦？这些因素重要吗？不重要吗？最令人头疼的是，在不知道答案的时候，我们也根本不知道他们重不重要，一个出谜语的人可能从任何一个微小的地方引申出某个谜语来；更头疼的是，我们不知道我们不知道的那些因素是不是也可能与题目的解有关，譬如这样一个问题：一个人走进酒吧，问酒保要一杯水，酒保掏出一只枪，拉上扳机；这人说声“谢谢”，走了出去。这些题目固然有趣，但几乎没有价值。</li>
<li>值得注意的是，这样的问题跟著名的<a href="http://en.wikipedia.org/wiki/Thinking_outside_the_box">9点连线问题</a>和6根火柴搭出4个面的问题（还有《如何解题：现代启发式方法》里面那个经典的“小球在盒内碰撞何时回到原轨迹”的问题）不同，后者的条件都在眼前，并且解的搜索空间无论如何很小，就看思维能不能突破某一个框框。而上面这些问题则是要人进行根本不可能的联想。9点问题实际上是可以系统化思考解决的，但unfair question则像许多谜语一样，随便哪个人都可以出一个另一个人根本无法想出来的谜语，因为从谜语隐含的信息加上人可能从谜语中联想出来的信息，加起来也不足以构成解题的充分条件；这种情况下除非你遇到出题人在出题时的心理或所处情况，否则是无法解的。</li>
<li>最后，发散性思维其实是可以系统化的，参见前文“联想的规则”。</li>
</ul>
<p>出题的误区：</p>
<ul>
<li>最大的误区就是把知识性的题目误当成能力型的题目。如果题目中需要用到某个重要的定理或性质，而对于一个原本不知道这个定理或性质的人来说是无法通过题目本身到达这个性质的，那这就属于知识性的题目。</li>
<li>虽然几乎所有题目归根到底都是知识性的，但有些题目更为知识性，尤其是当解题中需要用到的定理或性质并不那么trivial的时候。</li>
<li>一个最好的题目就是问题明明白白，而且最终的解也没有用到什么神秘的定理，但要想获知到解，取决于你会不会思考一个问题（参见“好问题”）。譬如烙饼问题和Nim问题，还有许许多多问题简洁明确但很锻炼思考的算法问题。</li>
</ul>
<p><b>4. 一个好习惯</b></p>
<p>在解题的过程中，除了必要条件——知识储备——之外，对于一些并不涉及什么你不知道的定理的题，很大程度上就要看思维能力或者习惯了。而在思考一个问题的时候，最容易犯的一类错误就是忘了考虑某种可能性，不管这种可能性是另一种做法（譬如只顾着构造一个能一步得出结果的算法，没记得还可以从错误情况逼近。譬如只顾着正着推导，却忘了可以反过来推。只顾着反过来推，居然忘了可以考察简单特例。试了各种手法，却发现忘了考虑题目的某个条件。觉得试遍了所有可能性，已经走不下去了，然后其实在思维的早些时候就已经落入了思维陷阱。等等）事实上，即便是一个熟练的解题者也容易犯顾此失彼的问题，因为我们一旦意识到一个看似能够得到结论的解法，整个注意力就容易被吸引过去，而由于推导的路径是很长的，所以很容易在一条路上走到黑，试图再往下走一步就得出解。却忘了回过头来看看再更高的层面上还有没有其它手法，思路上有没有其他可能性。</p>
<p>而对于像我这样目前尚不谙熟所有思维方法的人来说，则更容易犯这样的错误。为了避免这样的错误，一个有效的办法就是将自己的思考过程（中的重要环节）清晰的写在纸上（称为“看得见的思考”），这有如下几个好处：</p>
<ul>
<li>人在思考一个问题的时候，就像是在黑暗中打着电筒往前走（事实上，我们的<a href="http://en.wikipedia.org/wiki/Working_memory">工作记忆</a>资源是有限的，有研究证明我们只能在工作记忆里面持有<a href="http://en.wikipedia.org/wiki/The_Magical_Number_Seven%2C_Plus_or_Minus_Two">7加减2个项目</a>；此外<a href="http://en.wikipedia.org/wiki/Cognitive_load">认知负荷</a>也是有极限的），每一步推导，每一步逻辑或猜测都将我们往前挪一步，然而电筒的光亮能找到的范围是有限的，我们走了几步发现后面又黑了。有时候，我们是如此努力地试图一下就走出很远，同时又老是怕忘记目前已经取得的进展和重要结论，结果意识的微光就在一个很小的范围内打转，始终无法往前走出很远。而将思维过程记录下来，则给了我们完全的回顾机会。如果你是经常做笔记的人，你肯定会发现，有时候一个在脑子里觉得两句话就能说完不需要记下来的东西，一旦开始往纸上写下来，你就自然而然能得出更多的结论和东西，越写越多，最终关于你的问题的所有方面都被推导出来展现在你面前。</li>
<li>思考问题时的注意力是<a href="http://en.wikipedia.org/wiki/Attention#Neural_correlates_of_attention">自上而下控制大脑的神经处理过程</a>的，当我们集中注意力在某一个过程上时，其它的过程就会受到抑制。我们平常都遇到过一些时候，由于集中注意力从而忽略了周围发生的事情的时候（处理环境输入的神经回路受到抑制）。所以，当我们竭尽全力将一些非常重要的因素控制在工作记忆里面的时候，实际上很大程度上抑制了其余的思考——可以想见如果科比在跳投的瞬间集中注意力思考跳投的各种技术要素的话会发生怎样的灾难。此外，这么做还占用了宝贵的工作记忆空间，从这个意义上，借助于纸笔，将思考的东西写下来实际上就是扩充了我们的工作记忆，增大了思维的缓存。注意，这倒不是说思考问题不需要集中注意力，而是说由于将项目维持在工作记忆中需要很大的认知精力，使得我们的注意力无法暂时移开去思考其它相关的子问题，而写到纸上的话我们就减轻了工作记忆的负担，可以转移注意力去集中思考某个子问题；同时我们又可以随时回过头来，重新将以前想过的结论装入记忆（内存），完全不用费心去阻止它们被我们的工作记忆遗忘。</li>
<li>一句话从嘴里说出来，或者写到纸上，被视觉或听觉模块接收，再认知；跟在心里默念所产生的神经兴奋程度是不一样的。我们都有过这样的经历：一句令人不愉快的话，我们心里清楚，但就是不愿意自己也不愿意别人从嘴里说出来。同样，将思考的过程写到纸上，能够激起潜在的更多的联想。为什么会这样的另一个可能的原因是我们大脑中思维过程的呈现形式和纸上的表现形式是不一样的，既没有那么严格、详细也没有那么多的符号（如数学符号）——再一次，工作记忆资源是很有限的——而后者，作为视觉线索，可能激起更多对既有知识的回忆。</li>
<li>我们在思考问题的过程中容易落入思维定势，不知不觉就走上来某条“绝大部分时候是如此”的思维捷径，对于一些问题而言这固然能够让我们快速得到解，但对于另一些问题而言却是致命的。我们容易在逻辑的路径上引入想当然的假设，从而排除某种不该排除的可能性或做法。通过将思路过程写到纸上，我们便能够回头细细考察自己的思考过程，觉察到什么地方犯了想当然的毛病。</li>
<li>我们在思维过程中的每一个关键的一步也许都有另一种可能性，一个问题越复杂，需要推导的步骤就越多，我们就越容易忽视过程中的其它可能性，容易一条路走到黑。而将思维过程写下来，在走不下去的时候可以回过头看看，也许会发现另一种可能性，另一条“少有人走的路”。</li>
<li>最后，通过将思维过程写下来，我们就能够在解题完毕之后完整的回顾自己的整个思维过程，并从中再次体悟那些关键的想法背后所发生的心理活动过程，总结思考中的重要的一般原则，分析思维薄弱的环节，等等。就算是最终发现并没有到达结果的无效思路，也未必就没有意义，因为不是因为错误的思路，也不会知道正确的思路，况且对一道题目用不上的思路，对其它题目未必用不上。通过对自己思维过程的彻底反思，就能从每次解题中获得最多的收获。</li>
</ul>
<p><strong>5. </strong><strong>练习，练习</strong></p>
<p>本质上，练习并不产生新能力。然而练习最重要的一个作用就是将<a href="http://en.wikipedia.org/wiki/Explicit_memory">外显记忆</a>转化为<a href="http://en.wikipedia.org/wiki/Implicit_memory">内隐记忆</a>。用大白话来说就是将平时需要用脑子去想（参与）的东西转化为内在的习惯。譬如我们一开始学骑自行车的时候需要不断提醒自己注意平衡，但随着不断的联系，这种技能就内化成了所谓的<a href="http://en.wikipedia.org/wiki/Procedural_memory">程序式记忆</a>（内隐记忆的一种），从而就算你一边骑车一边进行解题这样需要消耗大量脑力的活动，也无需担心失去平衡（不过撞树是完全可能的，但那是另一回事）。</p>
<p>同样，对于解题中的思维方法来说，不断练习这些思维方法就能做到无意识间就能运用自如，大大降低了意识的负担和加快了解题速度。</p>
<p>不过，并非所有的练习方法都是等效的，有些练习方法肯定要比另一些更有效率。譬如就解题来说，解题是一项涉及到人类最高级思维机制的活动，其中尤其是推理（归纳和演绎）和联想。而后者中又尤数联想是最麻烦的，前面提到，绝大多数时候启发式方法实质上都是在为联想服务——为了能像晃筛子那样把你脑袋里那个关键的相关知识抖落出来。并且，为了方便以后能够联想，在当初吸收知识的时候就需要做最恰当的加工才行，譬如前面提到的“抽象”加工，除此之外还有将知识与既有的知识框架整合，建立最多的思维连接点（或者说“钩子”）。对于知识的深浅加工所带来的影响，<a href="http://www.douban.com/subject/1315575/">《找寻逝去的自我》</a>里面有精彩的介绍（里面也提到了提取线索对回忆的影响——从该意义上来说运用启发式思维方法来辅助联想，其实就是进行策略性记忆提取的过程）。最后，人类的无意识思维天生有着各种各样的坏习惯，譬如前面提到的范畴陷阱就是创新思维的杀手，譬如根据表面相似性进行类比也是知识转移的一大障碍。更遑论各种各样的<a href="http://www.douban.com/doulist/127649/">思维捷径</a>了（我们平常进行的绝大多数思考和决策，都是通过认知捷径来进行的）。所以说，如果任由我们天生的思维方式发展，也许永远都避不开无意识中的那些陷阱，好在我们除了无意识之外还多出了一层监督机制——意识。通过不断反省思维本身，时时纠正不正确的思考方式，我们就能够对其进行淬炼，最终养成良好的思维习惯。反之被动的练习虽然也能熟能生巧，但势必花的时间更多，而且对于涉及复杂的思维机制的解题活动来说，远远不是通过钱眼往油壶里面倒油这样简单的活动所能类比的，倒油不像思维活动那样有形形色色的陷阱，倒油不需要联想和推理，倒油甚至几乎完全不需要意识的辅助性参与，除了集中注意力（而解题活动就算对于极其熟练的人来说也不断需要大量的意识参与）。所以对于前者，良好的思维习惯至关重要，而反省加上运用正确的思维方法则是最终养成良好思维习惯的途径。</p>
<p>练习还有另外一个很重要的作用，就是增加领域知识（关于知识在问题解决中的作用，前面已经提到过）。我们看到很多人，拿到一道题目立即脑子里就反应出解法，这个反应快到他自己都不能意识到背后有什么逻辑。这是因为既有的知识（我们常说的“无他，实在是题做得太多了”）起到了极大的作用，通过对题目中几个关键元素或结构的感知，大脑中的相关知识迅速被自动提取出来。而对于知道但不熟悉相应知识（譬如很早我们就知道归纳法，但是很久以后我们才真正能够做到面对任何一道可能用归纳法的题目就立即能够想到运用归纳法），或者干脆就不知道该知识的人来说，就需要通过启发法来辅助联想或探索了。后者可以一定程度上代偿对知识的不够熟悉，但在一些时候知识的缺失则是致命的（参见上面第2点）。不过要注意的是，那种看到题目直接反应出答案的或许也不是纯粹的好事，因为这样的解题过程严重依赖于既有知识，尤其是做过的类似的题目，其思维过程绝大部分运用的是联想或类比，而非演绎或归纳。更重要的是，联想也分两种，被动联想和策略性联想（参考《找寻逝去的自我》），这里用的却是被动联想。所以，能直接反应出答案并不代表遇到真正新颖的题目的时候的解决能力，后者由于不依赖于既有领域知识，就真正需要看一个人的思维能力和习惯究竟如何了。</p>
<p><strong>6. </strong><strong>启发法的局限性</strong></p>
<p>首先肯定的是，启发法一定（也许很大）程度上是可以代偿知识的不足的（这里的知识主要是指大脑中的“联系”，下面还会提到另一种知识，即hard knowledge）。譬如，一道题目，别人直接就能通过类比联想到某道解过的题目，并直接使用了其中的一个关键的性质把题目给解出来了。你并没有做过那道题目，这导致两种可能的结果：一，你就是不知道那个性质。二，你虽然“知道”那个性质，但并没有在以前的解题经历中将那个性质跟你手头的这个问题中的“线索”联系起来，所以你还是“想不到”。后一种可以称为soft knowledge，即你“知道”，但就是联想（联系）不起来。所谓不能活学活用，某些时候就是这种情况，即书本上提供什么样的知识联系，脑子里也记住什么，而没有事后更广泛地去探索知识之间的本质联系（总结的作用）。前一种则可以称为hard knowledge，即你就是不知道，它不在你的脑子里。</p>
<p>而启发式方法在两个层面上起作用：</p>
<ul>
<li><strong>辅助联想起soft knowledge</strong>：譬如，特例法是一种启发式思考方法，它通过引入一个简单的特例，特例中往往蕴含有更多的“线索”，通过这些线索，有可能就会激发起对既有的知识的联想。另外一种强大的辅助联想办法就是对题目进行变形，变形之后就产生了新的视觉和语意线索，比如式子的对称性、从直角坐标到极坐标从而引发对后者的知识的联想等等。大量的启发式方法实际上的作用就是辅助联想，通过对题目中的线索的发掘，激起大脑中已知相关知识的浮现。在这个意义上，相对于那些能够直接联想到某个性质的人，那些不知道但可以通过启发式思维联想到的，启发式思维就提供了一种“曲径通幽”的策略性联想。还是以经典的例子来说：砖头的用途。有人立即能够直接联想到“敲人”。有人也许不能。然而启发式联想策略“抽象”就能够帮助后者也能够联想到“敲人”，因为“抽象”策略启发人去考虑砖头的各个性质维度，如“质地”，“形状”，当你考察到“质地坚硬”，“棱角”，离“敲人”的功能还会远么？本质上，能够直接联想到“敲人”功能的人是因为大脑中从砖头到敲人这两个概念之间的神经通路被走过了很多遍（譬如由于经常拿砖头敲人），神经元之间的联系相当“粗”（形象的说法，严格的事实请参考《追寻记忆的痕迹》），而不经常拿砖头敲人的人呢，这个联系就非常的弱，乃至于根本激不起一次神经冲动。那么为什么通过启发式方法又能联想到呢？因为启发式方法相当于带入了一种新的神经调控回路，首先它增加你联系到砖头的属性维度上的可能性，使得“质地坚硬”、“棱角”这两个语意概念被激活起来（注意，如果没有启发式方法的参与，这是不会发生的），一旦后者被激活起来，从后者到“敲人”的联系就被激活起来了。从本质上，解题中的启发联想方法做的也就是这个工作。而越是一般性的启发式方法就越是能对广泛的问题有帮助（譬如《How to Solve It》中介绍的那些，譬如分类讨论、分治、乃至我认为很重要的一个——写下自己的思维过程，详细分解各个环节，考察思维路径中有无其它可能性（我们很容易拿到一道题目便被一种冲动带入到某一条特定的思路当中，并且遵循着“最可能的”推导路径往下走，往往不自觉的忽略其它可能性，于是那些可能性上的联想就被我们的注意力“抑制”了。））。</li>
<li><strong>辅助探索出hard knowledge</strong>：倒推法是一种启发式思考方法，它将你的注意力集中到问题的结论中蕴含的知识上，一旦你开始关注可能从结论中演绎出来的知识，你就可能得到hard knowledge，即并不是早先就存在你脑子里，但是可以通过演绎获得的。上文中的最小和子序列中的倒推方法就是一个例子。</li>
</ul>
<p>而启发式方法的局限性也存在于这两个方面：</p>
<ul>
<li><strong>有些联系是不管怎样“</strong><strong>启发”</strong><strong>也想不起来的。</strong>譬如“当布被刺破了，干草堆就重要了”，你怎么解释这句话？如果有人提示一下“降落伞”，每个人都会恍然大悟。这是因为从“布”到“降落伞”之间的单向联系是近乎不存在的。而且就算运用启发法，譬如，考虑所有布做的东西，也基本绝无可能想到降落伞，因为同样，从“布做的东西”到“降落伞”之间的关联也是极其微弱的。我们脑子里只能保留那些最最重要的联系。（如果一提到布，“降落伞”和“衣服”、“被单”、“窗帘”等日常物品以同等重要级别闪现，就乱套了。）那为什么从降落伞我们能想到布呢？我们实际上不能，我们为什么有些时候能，是因为譬如有人叫你“考虑降落伞的材料”，后者就激发了“降落伞之材料”这个语意，后者又指导了我们去考察降落伞的材料构成，于是我们想到是布。否则“布”是不会直接被激发起来的。那为什么在我们的这个问题中，一旦有人提到降落伞，我们就能建立从布到降落伞的关联呢？这是因为“降落伞”和“布”这两个语意单元的同时兴奋增大了它们之间关联的可能性，就好比是加大另一端的电压从而发生了“击穿”一样。从本质上，解数学题也是如此，费马大定理的求解过程是一个很好的例子，谷山志村猜想，就相当于那个“降落伞”的提示。我们还听到很多这样的故事（或者自己经历）：苦思冥想一个问题不得要领，某一天在路上走，看到某个东西或听到某句话，然后忽然，一道闪电划破长空，那个问题解开了（阿基米德是因为躺在浴缸里从而想到浮力原理的吗？）。我敢保证，如果一个人早就把那个问题从脑海里扔到九霄云外去了（不再处于兴奋状态了），那么就算线索出现，也是不可能发生顿悟的。我们都知道，带着一个问题（使其在大脑中处于兴奋状态）去寻找答案更可能找到，即便不是有意去寻找，只要问题还在脑子里，任何周围的有可能与它相关的线索都不会被大脑漏掉，因为“问题”和“周围的其他线索”同时的兴奋增大了关联的可能性。如果问题早就被从大脑（意识或者潜意识）中撤下了，即便周围出现提示也不会被捕捉到。</li>
<li><strong>许多hard knowledge</strong><strong>是不能被启发探索出来的。</strong>至少是不能被“直接命中目标”地探索出来的。一个问题有可能跟三角函数有关，也许你只能带着问题去探索三角函数的所有性质，从而最终发现那个关键的性质。费马大定理与椭圆方程有关，也许只能去探索椭圆方程的所有性质，这个过程一定程度上是盲目的，试错的，遍历的。而不是直接面向目标的。再聪明的人也无法从费马大定理直接反推到谷山志村猜想。在这些时候，启发式方法最多只能提供一个探索的大致方向：譬如，探索三角函数的性质，并随时注意其中哪个可能对我这个问题有帮助。譬如，探索模运算的性质，看看哪些性质可能会有用。譬如，探索椭圆曲线的性质&#8230;等等。启发式方法并不能使我们的探索精准地命中目标。而只能划定一个大致的范围。也难怪有人说数学是盲目的。</li>
</ul>
<p>但话说回来，启发式方法的局限性并不能否认在大量场合启发式方法的巨大帮助，许多时候，单靠启发式方法就能带来突破。而且，一旦知识性的东西掌握的是<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image011.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image011" border="0" alt="clip_image011" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image011-thumb.jpg" width="105" height="150" /></a>一样多的，能否运用更优秀的思维方法就决定了能力的高下。有很多<a href="http://www.douban.com/people/pongba/booktags/%E6%80%9D%E7%BB%B4">介绍思维方法的书</a>。</p>
<p><strong>7. </strong><strong>总结的意义</strong></p>
<p>解题练习的最重要目的不是将特定的题目解出来，而是在于反思解题过程中的一般性的，跨问题的思维法则。简单的将题目解出来（或者解不出来看答案，然后 “恍然大悟”），只能得到最少的东西，解出来固然能够强化导致解出来的那个思维过程和方法，但缺少反思的话便不能抽取出一般性的东西供更多的题目所用。而解不出来，看答案然后“哦”的一声更是等同于没有收获，因为“理解”和“运用”相差何止十万八千里。每个人都有过这样的经历：一道题目苦思冥想不得要领，经某个人一指点其中的关键一步，顿时恍然大悟——这是理解。但这个理解是因为别人已经将新的知识（那个关键的一步）放到你脑子里了，故而你才能理解。而要运用的话，则需要自己去想出那关键的一步。因此，去揣测和总结别人的思维是如何触及那关键的一步，而你自己的思维又为什么触及不到它，有一些一般性的原则可以指导你下次也能想到那个“关键的一步”吗，是很有意义的。我们很多时候会发现，一道题目，解不出来，最终在提示下面解出来之后，发现其中并没有用到任何自己不知道的知识，那么不仅就要问，既然那个知识是在脑子里的，为什么我们当时愣是提取不出来呢？而为什么别人又能够提取出来呢？我怎么才能像别人那样也提取出相应的知识呢？实际上这涉及到关于记忆的最深刻的<a href="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image012.jpg"><img style="border-bottom: 0px; border-left: 0px; display: inline; margin-left: 0px; border-top: 0px; margin-right: 0px; border-right: 0px" title="clip_image012" border="0" alt="clip_image012" align="right" src="http://mindhacks.cn/wp-content/uploads/2009/02/clip-image012-thumb.jpg" width="103" height="153" /></a>原理，实际上文中已经提到了一些。（有兴趣的建议参考以下几本书：<a href="http://www.douban.com/subject/1944205/">《追寻记忆的痕迹》</a>，<a href="http://www.douban.com/subject/1315575/">《找寻逝去的自我》</a>，<a href="http://www.douban.com/subject/2345245/">《Synaptic Self》</a>，<a href="http://www.douban.com/subject/2845839/">《Psychology of Problem Solving》</a>）一般性的思维法则除了对于辅助联想（起关键的知识）之外，另一个作用就是辅助演绎/归纳（助探），一开始学解题的时候，我们基本上是先读懂题目条件，做可能的一些显然的演绎。如果还没推到答案的话，基本就只能愣在那里等着那个关键的步骤从脑子里冒出来了。而所谓的启发式思维方法，就是在这个时候可以运用一些一般性的，所有题目都适用的探索手法，进一步去探索问题中蕴含的知识，从而增大成功解题的可能性。启发式的思维方法有很多，从一般到特殊，最具一般性的，在波利亚的《How to Solve It》中已经基本全部都介绍了。一些更为特殊性的（譬如“如果全局搜索空间没有递归结构，那么考虑分割搜索空间”，譬如那些“看到XX，要想到YY”的联系），则需要自己在练习中不断抽象总结。</p>
<p><b>一句结尾</b></p>
<p>“我想我就在这里结束”——如果你知道我在说什么的话:-)</p>
<h3  class="related_post_title">你可能也会喜欢以下文章</h3><ul class="related_post"><li><a href="http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/" title="知其所以然（三）：为什么算法这么难？">知其所以然（三）：为什么算法这么难？</a> (57)</li><li><a href="http://mindhacks.cn/2011/01/23/escape-from-your-shawshank-4/" title="逃出你的肖申克（四）：理智与情感">逃出你的肖申克（四）：理智与情感</a> (127)</li><li><a href="http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/" title="知其所以然（续）">知其所以然（续）</a> (42)</li><li><a href="http://mindhacks.cn/2010/03/18/escape-from-your-shawshank-part3/" title="逃出你的肖申克（三）：遇见20万年前的自己">逃出你的肖申克（三）：遇见20万年前的自己</a> (113)</li><li><a href="http://mindhacks.cn/2009/03/28/effective-learning-and-memorization/" title="[BetterExplained]如何有效地记忆与学习">[BetterExplained]如何有效地记忆与学习</a> (104)</li><li><a href="http://mindhacks.cn/2009/03/15/preconception-explained/" title="逃出你的肖申克（二）：仁者见仁智者见智？从视觉错觉到偏见">逃出你的肖申克（二）：仁者见仁智者见智？从视觉错觉到偏见</a> (148)</li><li><a href="http://mindhacks.cn/2009/02/15/why-you-should-start-blogging-now/" title="[BetterExplained]为什么你应该（从现在开始就）写博客">[BetterExplained]为什么你应该（从现在开始就）写博客</a> (206)</li><li><a href="http://mindhacks.cn/2009/02/09/writing-is-better-thinking/" title="[BetterExplained]书写是为了更好的思考">[BetterExplained]书写是为了更好的思考</a> (96)</li><li><a href="http://mindhacks.cn/2009/02/07/better-explained-conflicts-in-intimate-relationship/" title="[BetterExplained]亲密关系中的冲突解决">[BetterExplained]亲密关系中的冲突解决</a> (50)</li><li><a href="http://mindhacks.cn/2009/01/18/escape-from-your-shawshank-part1/" title="逃出你的肖申克（一）：为什么一定要亲身经历了之后才能明白？">逃出你的肖申克（一）：为什么一定要亲身经历了之后才能明白？</a> (64)</li></ul><hr />
<h3>订阅 Mind Hacks</h3>
<a title="用Google Reader订阅" href="http://fusion.google.com/add?feedurl=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_google.gif" style="border:0" alt="订阅到 | Google" /></a> 
<a title="用鲜果订阅" href="http://www.xianguo.com/subscribe.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_xianguo.gif" style="border:0" alt="订阅到 | 鲜果" /></a>
<br/> 
<a title="用抓虾订阅" href="http://www.zhuaxia.com/add_channel.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_zhuaxia.gif" style="border:0" alt="订阅到 | 抓虾" /></a> 
<a title="用有道订阅" href="http://reader.youdao.com/b.do?keyfrom=mindhacks&url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_yodao1.gif" style="border:0" alt="订阅到 | 有道" /></a> 
<hr />
<h3>我是你的信息过滤器</h3>
想了解作者最近在关注什么，欢迎 Follow <a href="http://weibo.com/pongba">刘未鹏pongba@微博</a>
<br/>
程序员朋友请到作者发起的 <a href="https://groups.google.com/group/pongba">TopLanguage</a> (<a href="http://mindhacks.cn/about-toplanguage/">about</a>) 社群逛逛，定有收获 :)
<br/>
想了解作者在阅读哪些书，请到 <a href="http://www.douban.com/people/pongba/">pongba@豆瓣</a>，或者直接访问以下四个豆列：<a href="http://www.douban.com/doulist/46003/">[只读经典]思维改变生活</a> | <a href="http://www.douban.com/doulist/127649/">[只读经典]思考的技术与艺术</a> | <a href="http://www.douban.com/doulist/197706/">决策与判断</a> | <a href="http://www.douban.com/doulist/176513/">机器学习与人工智能书籍资源导引</a> 。
<hr />
<p><small>
本文由 刘未鹏 发布在 <a href="http://mindhacks.cn">刘未鹏 | Mind Hacks</a>, 2008. | <a href="http://mindhacks.cn/2008/04/18/learning-from-polya/#commenting">23 条评论</a> | 标签: <a href="http://mindhacks.cn/tags/%e6%80%9d%e7%bb%b4%e6%94%b9%e5%8f%98%e7%94%9f%e6%b4%bb/" rel="tag">思维改变生活</a>, <a href="http://mindhacks.cn/tags/%e7%ae%97%e6%b3%95/" rel="tag">算法</a>
<br/>
转载请注明作者，出处，以及<a href="http://mindhacks.cn/2008/04/18/learning-from-polya/">原始超链接</a>: http://mindhacks.cn/2008/04/18/learning-from-polya/
</small></p>]]></content:encoded>
			<wfw:commentRss>http://mindhacks.cn/2008/04/18/learning-from-polya/feed/</wfw:commentRss>
		<slash:comments>23</slash:comments>
		</item>
		<item>
		<title>数学之美番外篇：进化论中的概率论</title>
		<link>http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/</link>
		<comments>http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/#comments</comments>
		<pubDate>Sun, 02 Dec 2007 10:55:00 +0000</pubDate>
		<dc:creator>刘未鹏</dc:creator>
				<category><![CDATA[数学]]></category>
		<category><![CDATA[算法]]></category>
		<category><![CDATA[概率论]]></category>

		<guid isPermaLink="false">http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/</guid>
		<description><![CDATA[偶然性在进化中确实存在（例如，偶然性的突变可以产生新的特征），但是进化并不依赖偶然性来产生新的器官、蛋白质或其他实体。截然相反的是，自然选择，作为进化中已知的最主要机制，却会明确保留“需要的”（能适应的）特性，消除“不需要的”（无法适应的）特性。只要选择的影响力存在，自然选择就能把进化向一个方向推进，在出乎意料的短时间内产生复杂的结构。举个例子，现有由13个字母构成的序列“TOBEORNOTTOBE”，假设有几百万只猴子，每只猴子每秒钟挑一条短语，需要78,800年才能从26^13种可能中选出这样的排列。不过，Glendale College的Richard Hardison在20世纪80年代写过一个程序，它能够在随机产生序列的同时，保证那些已经出现在正确位置上的字母不会变化（这样做倒有点《汉姆雷特》 的味道。译注：这个句子看了大半天才明白，嘿嘿）。这个程序平均只需要336次迭代就能生成上文提到的短语，时间少于90秒。更神奇的是，把莎士比亚的整个剧本重新生成一遍也只需要四天半时间。]]></description>
			<content:encoded><![CDATA[<p><a href="http://www.xiaolai.net/">李笑来</a>老师<a href="http://www.xiaolai.net/?p=565">在blog上转了</a>一篇宏文，“<a href="http://www.sciam.com/article.cfm?articleID=000D4FEC-7D5B-1D07-8E49809EC588EEDF">15 Answers to Creationist Nonsense</a>”；然后<a href="http://www.luanxiang.org/">余晟</a>同学（顺便推荐<a href="http://www.luanxiang.org/">余晟</a>同学译的<a href="http://www.douban.com/subject/2154713/">《精通正则表达式》（第3版）</a>）把它给<a href="http://www.luanxiang.org/blog/?p=348">译了出来</a>。漂亮的文章加上漂亮的翻译，当然是要拜读的:-)</p>
<p>进化论从其诞生以来受到的非难不计其数。这里提到的这篇便收集了广为神创论者提出以及广为大众误解的一些观点。其中有一点尤其引起了我的兴趣，如下：</p>
<blockquote><p><b>8. </b><b>严格说起来，我们很难相信复杂如蛋白质的物质能偶然出现，更不用说人或是活细胞了。</b><b></b></p>
<p>偶然性在进化中确实存在（例如，偶然性的突变可以产生新的特征），但是进化并不依赖偶然性来产生新的器官、蛋白质或其他实体。截然相反的是，自然选择，作为进化中已知的最主要机制，却会明确保留“需要的”（能适应的）特性，消除“不需要的”（无法适应的）特性。只要选择的影响力存在，自然选择就能把进化向一个方向推进，在出乎意料的短时间内产生复杂的结构。举个例子，现有由13个字母构成的序列“TOBEORNOTTOBE”，假设有几百万只猴子，每只猴子每秒钟挑一条短语，需要78,800年才能从26^13种可能中选出这样的排列。不过，Glendale College的Richard Hardison在20世纪80年代写过一个程序，它能够在随机产生序列的同时，保证那些已经出现在正确位置上的字母不会变化（这样做倒有点《汉姆雷特》 的味道。译注：这个句子看了大半天才明白，嘿嘿）。这个程序平均只需要336次迭代就能生成上文提到的短语，时间少于90秒。更神奇的是，把莎士比亚的整个剧本重新生成一遍也只需要四天半时间。</p>
</blockquote>
<p>关于这个随机枚举特定的13字母的单词的问题有点意思。如果是纯粹随机枚举的话，由于长度为13的单词一共有26^13个不同的（假设所有组合都是有效单词的话），其中只有一个跟目标单词一样，也就是说平均（数学期望）枚举26^13次才能枚举出目标串来。</p>
<p>我们不妨把人类的DNA链当成一个长长的单词。突变是产生随机枚举的动力。那么根据上面的分析，要枚举出我们现在用的DNA，需要的迭代次数将是跟DNA链上的“字母数”（碱基对）成指数关系的。枚举一个13个字母的单词就需要26^13次方了，上亿碱基对，需要多少次迭代？更不要说人类的一代更迭就平均要耗上十来二十年了。</p>
<p>从这个角度来看，作为生命只有短短几十年的我们，似乎的确很难理解像眼睛这么复杂精妙的结构是如何从随机的突变中产生出来的。而这也恰恰是神创论者最经常用来非难进化论的论点之一。那么，这个貌似有力的论点到底正确与否呢？其实，在达尔文同学的<a href="http://www.douban.com/subject/1969983/">《物种起源》</a>中就已经进行了一定程度的驳斥。达尔文同学列举了一系列的比我们人类眼睛简单的眼睛结构，从复杂逐渐到简单，其中最简单的“眼睛”只由一些聚集在一起的感光细胞构成；并且，达尔文同学还雄辩了为什么眼睛的复杂性并非是不可归约（irreducible）的。</p>
<p>然而，达尔文同学毕竟不是专业的理工科出生（在大学里面是学神学的），虽然其实践精神是每个科学家的楷模，虽然在《物种起源》中他运用了一系列的证据和推理，某种程度上论证了随机突变加上自然的选择之手，的确能够进化出如眼睛这么复杂的结构。然而他并没有从数学上加以证明，为什么定向选择能够导致在短得多的时间内产生复杂结构，以及这个时间与纯随机枚举相比到底短到什么程度？</p>
<p>我们仍以那个13个字母的单词为例TOBEORNOTTOBE。如果是纯随机枚举的话，平均需要26^13次方才能枚举出一个来。那么，自然中的进化过程也是这样的吗？并非如此。虽然每一个基因位都可能发生变异，然而自然选择之手会将那些“好”的部分留下来，差的部分剔掉（因为突变出来的好基因比差基因更有生存繁殖优势，于是渐渐就会在种群中通过遗传扩散开来）。反映在我们枚举单词的过程中就是，一旦我们枚举出了某个或某些特定位上的字母，那么这个字母就确定下来，不再变动，只需继续枚举剩下来的字母。这样，直觉上需要枚举的次数就会大大减少。而实际上也正是如此，引文中提到的Richard同学写的一个程序便说明了这一点：本来需要78,800年，现在只需90秒。差异何等巨大！</p>
<p>现在，我们关心的问题是，按照后一种枚举方法，能从数学上精确计算出来，要枚举出这个目标单词需要的迭代次数的数学期望吗？（即，平均枚举多少次，才能枚举出它呢？）</p>
<p>Richard同学写的那个程序显示需要平均336次；然而，问题是他写的是一个程序，而不是用数学来计算到底需要多少次。336次是数学期望吗？不是。它是一个实践值。</p>
<p>实际上，我也写了一个这样的程序，然而结果显示却是大约82次左右。那么到底谁的正确呢？</p>
<p>要检验这个结论，更重要的问题是，应该有一个数学方法能够计算出按照这种方法，可（数学）期望通过迭代多少次就迭代出目标串。</p>
<p>为了从数学上解决这个问题。我们需要用到一点基本的概率论知识：</p>
<p>如果一个随机变量X的值为x<sub>i</sub>的几率是P<sub>i</sub>，那么X的数学期望Ex就等于∑x<sub>i</sub>P<sub>i</sub>。举个例子，假设小明的考试成绩为90分的概率为30%，为80分的概率为70%，那么小明的成绩的数学期望便是90×30%+80×70%=83（虽然83其实是个不可能的成绩）。</p>
<p>回到我们要求解的问题：我们想知道需要枚举多少次才能枚举出目标单词。要求解这个问题，我们可以反过来思考：<b>平均每枚举一次能正确枚举出目标单词上的几个字母</b>（这里“正确”的意思当然是要满足“在相同位置上的字母也相同”，比如目标单词是TUBE的话，一旦枚举出POTE，我们就说正确枚举出了最后一个字母E，而T由于位置不对应，因此就不能算是正确的了）。</p>
<p>很显然，平均一次能正确枚举出的字母数目是一个随机变量，不妨令为X。该随机变量依赖于在这次枚举中，目标单词上的每一个位置上的字母是否被正确枚举出来了，于是我们设目标单词第i位上的字母被枚举的结果（即是否枚举中——只有“中”或“不中”两种结果，因而是一个二元随机变量）为随机变量X<sub>i</sub>；X<sub>i</sub>只有“中”或“不中”两种可能，我们将“中”的值量化为1，“不中”为0。由于每一位上枚举中的概率都是独立同分布的，因此对于任意一个X<sub>i</sub>来说，为0（“不中”）的概率皆为25/26；为1（“中”）的概率皆为1/26。这很容易理解，因为字母表中一共有26个字母，随机选择一个，跟目标字母相同的概率自然是1/26，不同的概率则是25/26。</p>
<p>有了X<sub>i</sub>，我们进一步发现，X其实是X<sub>i</sub>的函数：X = ∑X<sub>i</sub>。这个式子这样理解：如果在位置i处枚举中了，那么X<sub>i</sub>便是1，这样就给总共枚举中的位数X贡献了1；否则X<sub>i</sub>则为0，即没有贡献。</p>
<p>现在，我们回过头审视我们想要求得的东西：我们想求得是<b>枚举一次能正确枚举出目标单词上的字母数目的数学期望</b>。也就是X的数学期望EX。由于X = ∑X<sub>i</sub>。于是EX = E(∑X<sub>i</sub>) = ∑EX<sub>i</sub>。而EX<sub>i­</sub>对每个X<sub>i</sub>是相同的（独立同分布嘛），都是0×25/26 + 1×1/26 = 1/26。因此EX = n×EX<sub>i</sub>（其中n是目标单词的长度——本例中是13）= 13×1/26 = 1/2。</p>
<p>综上，我们得出结论：随机枚举一次可（数学）期望枚举中目标单词上的1/2个字母。</p>
<p>1/2个字母？是不是开玩笑？哪有“半个字母”的说法？实际上，因为是数学期望，而数学期望的值很可能并非所有可能值中的任一个，而是它们的概率加权平均，所以半个字母的说法在数学期望上是说得通的；更关键的是，这个期望值给我们提供了一个极其重要的信息，那就是<b>要想枚举中其中的一位，我们（数学期望上）需要枚举</b><b>2</b><b>次才行（因为每次枚举中</b><b>1/2</b><b>位）</b>。</p>
<p>一旦枚举中了其中的一位，那么后面的随机枚举过程便不需要考虑这一位，只需要考虑剩下的了。换句话说，目标单词中的字母便被剔掉了一个，只剩12个字母。而在12个字母的单词中，要想再枚举中一位，需要多少次迭代呢？重用上面的推导过程，EX仍然还是等于n×EX<sub>i</sub>。EX<sub>i</sub>没变，而n变成了12。即迭代一次平均命中12/26个字母，那么要完全命中其中一位字母，便需要26/12（即2.17次）。</p>
<p>如此类推，每次减掉其中的一个字母需要特定次数的枚举，一直到减至只剩最后一个字母，需要26次。把所有这些枚举次数的期望值加起来，便是总共需要枚举的次数了。即26/13 + 26/12 + 26/11 + … + 26/1 ~= 82.68次。</p>
<p><b>另一种思路</b><b></b></p>
<p>我们回顾一下上一个解法过程中的核心问题：<b>要命中其中的一个字母，（数学期望上）需要枚举多少次</b>，我们令这个次数为随机变量X。我们回顾一下数学期望的本质定义：每个可能的值的概率加权平均。于是，要求得X的数学期望EX，我们只需知道X所有可能的取值以及对应于各个取值的概率。</p>
<p>那么，要命中其中一个字母，究竟需要枚举多少次呢？可能是一次就中了，也可能需要两次，也可能需要三次…你会发现，有可能需要任意次。只不过随着所需次数的增加，概率也越来越小。实际上，这是一个无穷级数求和问题；所幸，你马上就会看到，这个求和其实很简单。</p>
<p>所谓一次就命中的意思是，只随机枚举一次，就会命中目标单词中的一个且仅一个字母。这个随机事件由三个部分组成，首先是其余n-1个字母不中，然后是剩下的1个字母中了，再然后是那个命中的字母有n种可能的位置。因此，其概率是[(25/26)^(n-1)]×1/26×n。同理，需要两次才能命中其中一个字母的概率是(25/26)^n×1/26×n×[(25/26)^(n-1)]…以此类推。</p>
<p>而命中其中一个字母所需枚举次数X的数学期望是：</p>
<p>1×一次就中的概率 + 2×二次才中的概率 + 3×三次才中的概率 + …</p>
<p>也就是：</p>
<p>1/26×n×(25/26)^(n-1) [1 + 2×(25/26)^n + 3×(25/26)^2n + 4×(25/26)^3n … ]</p>
<p>左边方括号内的无穷级数求和的形式为1+2q+3q^2+4q^3+…，结果为1/(1-q)^2（利用类似等比级数求和的技巧——错位相减），所以上式求和简化后的结果为：</p>
<p>[n×25^(n-1)×26^n]/[(26^n-25^n)^2]</p>
<p>即，当目标单词长为n时，平均需要[n×25^(n-1)×26^n]/[(26^n-25^n)^2]次枚举才能命中其中一个字母；而一旦命中一个字母之后，该字母就会被从单词当中剔掉，并继续枚举第二个字母，此时n减少了1，因而上式的值也发生了变化。</p>
<p>简言之，长度为N的单词，需要∑[n×25^(n-1)×26^n]/[(26^n-25^n)^2]次（其中n从1变化到N）迭代便能够完全枚举出来。</p>
<p>现在我们回到原来的问题：一个长为13的单词，TOBEORNOTTOBE，究竟需要枚举多少次才能够完全枚举出来呢？按照以上的式子，得出的结果是82.39。而采用前面的不精确近似，计算的结果82.68。跟我自己写的一个枚举程序运行一万次平均之后的结果刚好相符，后者也是在82左右徘徊。</p>
<p>不过，以上两种做法其实都建立在一个假设之上，即我们是一个一个地枚举出目标单词中的字母的。不是两个，也不是三个。然而实际当中可能一次就枚举出多个乃至全部的字母。因此，其实以上两种做法计算出的都是一个不精确的值，这也是为什么它们的结果相近但不一样的原因（真正的结果只有一个）。然而，如果想给出精确表达式或计算方法就非常复杂了，或者说至少我没有想到更简单的表达方法，如果你有不妨告诉我:-)</p>
<p><b>小结</b><b></b></p>
<p>本文介绍了隐藏在自然选择中的概率论，并说明了为什么自然选择能够在相对（与纯随机枚举相比）极短的时间内塑造出复杂的有机体；简而言之，选择之手总是不断地将生物的基因向某个方向推进，一旦基因中变异出有益的片段，该片段就会被选择保留下来并逐渐在种群中蔓延开来，反映到文中讨论的枚举单词的例子中就是，一旦某个位上的字母被枚举出来，便会被保留住，不再受到后续变异的影响（除非是更好的变异）（通过一个数学示例，我们看到，原本需要26^13次迭代才能产生的目标序列，只需82次居然就进化出来了，其间的差距是无法估计的；事实上，计算机算法上就有使用进化思想来实现算法的，也就是所谓的<a href="http://www.douban.com/subject/1232071/">进化算法</a>）；再则，加以种群中数量巨大的个体（每个个体都是一个单独的枚举器），我们就不难理解为什么自然选择能够进化出复杂如眼睛的结构了。</p>
<p><b><i>Update 2007-12-3</i></b>：<a href="http://blog.youxu.info/">徐宥</a>同学<a href="http://blog.csdn.net/pongba/archive/2007/12/02/1912466.aspx#747121">指出</a>，<a href="http://surge.ods.org/listarc/20020621.HTM">在Richard原来写的那个程序中</a>，是采用的“挨个枚举”办法。即先枚举出第一个字母（期望需要26次），然后枚举第二个，如此直到把所有字母枚举完。如此需要的时间为13×26=338；符合文中给出的值。如此说来，之所以我得出的结果跟Richard的不一样，是因为<b>采用的枚举策略不一样</b>，我将“一次枚举”定义为“枚举整个单词”，而非“枚举其中某个字母”。不过我感觉Richard的那个枚举策略显然不符合自然选择的工作方式，自然状况下，每一个基因位（“字母”）都可能发生变异（独立分布，不过变异概率未必一样），而对变异基因的择优筛选则发生在“遗传”这一个环节（严格来说是发生在遗传过程中的“差异繁殖率”上）。</p>
<p>不过，总而言之，尽管计算机模拟的选择算法不同，总的思想是一样的，即一旦加入了“选择之手”，就能够极大地加快进化的速度。</p>
<p><b>下期预告？</b><b></b></p>
<p>本来打算写写Google Pagerank算法的，但那玩意往细了写太麻烦，总找不出动力，所以索性先写篇短的:-) Google Pagerank算法是数学与工程的完美结合，其中蕴含了数学的纯粹和工程的务实，实在值得欣赏，所以，强烈推荐下面这篇：</p>
<p><a href="http://www.ams.org/featurecolumn/archive/pagerank.html">How Google Finds Your Needle in the Web&#8217;s Haystack</a>，那么这篇有多火呢？del.icio.us上save的人竟有1,774人！可见一斑。</p>
<h3  class="related_post_title">你可能也会喜欢以下文章</h3><ul class="related_post"><li><a href="http://mindhacks.cn/2008/06/13/why-is-quicksort-so-quick/" title="数学之美番外篇：快排为什么那样快">数学之美番外篇：快排为什么那样快</a> (25)</li><li><a href="http://mindhacks.cn/2011/07/10/the-importance-of-knowing-why-part3/" title="知其所以然（三）：为什么算法这么难？">知其所以然（三）：为什么算法这么难？</a> (57)</li><li><a href="http://mindhacks.cn/2010/11/14/the-importance-of-knowing-why-part2/" title="知其所以然（续）">知其所以然（续）</a> (42)</li><li><a href="http://mindhacks.cn/2008/09/21/the-magical-bayesian-method/" title="数学之美番外篇：平凡而又神奇的贝叶斯方法">数学之美番外篇：平凡而又神奇的贝叶斯方法</a> (51)</li><li><a href="http://mindhacks.cn/2008/07/07/the-importance-of-knowing-why/" title="知其所以然（以算法学习为例）">知其所以然（以算法学习为例）</a> (36)</li><li><a href="http://mindhacks.cn/2008/04/18/learning-from-polya/" title="跟波利亚学解题(rev#3)">跟波利亚学解题(rev#3)</a> (23)</li><li><a href="http://mindhacks.cn/2006/10/15/cantor-godel-turing-an-eternal-golden-diagonal/" title="康托尔、哥德尔、图灵&mdash;&mdash;永恒的金色对角线(rev#2)">康托尔、哥德尔、图灵&mdash;&mdash;永恒的金色对角线(rev#2)</a> (34)</li></ul><hr />
<h3>订阅 Mind Hacks</h3>
<a title="用Google Reader订阅" href="http://fusion.google.com/add?feedurl=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_google.gif" style="border:0" alt="订阅到 | Google" /></a> 
<a title="用鲜果订阅" href="http://www.xianguo.com/subscribe.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_xianguo.gif" style="border:0" alt="订阅到 | 鲜果" /></a>
<br/> 
<a title="用抓虾订阅" href="http://www.zhuaxia.com/add_channel.php?url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_zhuaxia.gif" style="border:0" alt="订阅到 | 抓虾" /></a> 
<a title="用有道订阅" href="http://reader.youdao.com/b.do?keyfrom=mindhacks&url=http://mindhacks.cn/feed/"><img src="http://mindhacks.cn/wp-content/uploads/2009/02/feed_yodao1.gif" style="border:0" alt="订阅到 | 有道" /></a> 
<hr />
<h3>我是你的信息过滤器</h3>
想了解作者最近在关注什么，欢迎 Follow <a href="http://weibo.com/pongba">刘未鹏pongba@微博</a>
<br/>
程序员朋友请到作者发起的 <a href="https://groups.google.com/group/pongba">TopLanguage</a> (<a href="http://mindhacks.cn/about-toplanguage/">about</a>) 社群逛逛，定有收获 :)
<br/>
想了解作者在阅读哪些书，请到 <a href="http://www.douban.com/people/pongba/">pongba@豆瓣</a>，或者直接访问以下四个豆列：<a href="http://www.douban.com/doulist/46003/">[只读经典]思维改变生活</a> | <a href="http://www.douban.com/doulist/127649/">[只读经典]思考的技术与艺术</a> | <a href="http://www.douban.com/doulist/197706/">决策与判断</a> | <a href="http://www.douban.com/doulist/176513/">机器学习与人工智能书籍资源导引</a> 。
<hr />
<p><small>
本文由 刘未鹏 发布在 <a href="http://mindhacks.cn">刘未鹏 | Mind Hacks</a>, 2007. | <a href="http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/#commenting">13 条评论</a> | 标签: <a href="http://mindhacks.cn/tags/%e6%95%b0%e5%ad%a6/" rel="tag">数学</a>, <a href="http://mindhacks.cn/tags/%e6%a6%82%e7%8e%87%e8%ae%ba/" rel="tag">概率论</a>, <a href="http://mindhacks.cn/tags/%e7%ae%97%e6%b3%95/" rel="tag">算法</a>
<br/>
转载请注明作者，出处，以及<a href="http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/">原始超链接</a>: http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/
</small></p>]]></content:encoded>
			<wfw:commentRss>http://mindhacks.cn/2007/12/02/probability-theory-in-evolution/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
	</channel>
</rss>

