<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>GoCalf Blog - 算法</title><link href="https://blog.gocalf.com/" rel="alternate"></link><link href="https://blog.gocalf.com/feeds/algorithm.atom.xml" rel="self"></link><id>https://blog.gocalf.com/</id><updated>2014-08-28T23:25:00+08:00</updated><subtitle>1/100 ALGO&amp;amp;MATH; 1/100 IT&amp;amp;GAME; 1/100 INFO&amp;amp;SHARING; 1/100 WHO KNOWS</subtitle><entry><title>自描述语句</title><link href="https://blog.gocalf.com/self-descriptive-sentence" rel="alternate"></link><published>2014-08-28T17:15:00+08:00</published><updated>2014-08-28T23:25:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2014-08-28:/self-descriptive-sentence</id><summary type="html">&lt;p class="first last"&gt;自描述语句（Self-Descriptive Sentence），也叫做 autogram，是一种自己描述自己的语句，今天就介绍一种简单的生成这种句子的方法。&lt;/p&gt;
</summary><content type="html">
&lt;p&gt;自描述语句（Self-Descriptive Sentence），也叫做 &lt;a class="reference external" href="http://en.wikipedia.org/wiki/Autogram"&gt;autogram&lt;/a&gt;，是一种自己描述自己的语句。&lt;/p&gt;
&lt;p&gt;我在 2004 年的时候（时光飞逝啊）看到这种句子，想了一个简单的方法来用程序生成，效果还可以，但还有很大的不足。后来也没在做什么改进，今天就把这陈年老事拿出来晒晒太阳。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id20"&gt;介绍&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;先举个简单的自描述语句的例子。十年前好友 &lt;a class="reference external" href="http://www.truevue.org/"&gt;@gdh&lt;/a&gt; 发邮件来说在网上看到有人发了这样一个句子，号称是用来折腾 Visual Studio 2003 的，但是没有更具体的信息，就想问问我有没有什么想法。句子是这样的：&lt;/p&gt;
&lt;blockquote&gt;
聪明的张先生生成的这个句子一共有一百一十三个字，其中有二个 “百”，三个 “有”，二个 “子”，七个 “三”，三个 “生”，十五个 “二”，二个 “先”，二个 “字”，一个 “八”，二个 “七”，一个 “四”，二个 “明”，一个 “六”，二个 “成”，一个 “零”，二个 “张”，二个 “中”，二个 “其”，二个 “共”，二个 “这”，五个 “十”，二个 “句”，十个 “一”，三十一个 “个”，三个 “的”，二个 “聪”，一个 “九”，三个 “五”。&lt;/blockquote&gt;
&lt;p&gt;特点很明显，不考虑标点符号的话，一数便知，这句话一共有 113 个汉字，跟句子中声明的一致。另外，句子中出现过的每一个汉字，句子自身都给出了这个汉字的总出现次数。比如“聪”字一共出现两次，第一次是句子开头“聪明的”那里，第二次是声明“聪”字个数的时候说的“二个‘聪’”。再比如“个”字，数一数，包括“三十一个‘个’”中出现的两次，确确实实总共出现了 31 次。&lt;/p&gt;
&lt;p&gt;这样的句子就是自描述语句的一种，它自己对自己做了一番统计，表明了自身的总字数，以及所包含的每一个汉字的次数。当然如果愿意的话，还可以让它把包含的每一个标点符号也都统计出来。&lt;/p&gt;
&lt;p&gt;再来一个例子：&lt;/p&gt;
&lt;blockquote&gt;
你看到的这个句子一共有九十个字，其中有二个“你”，二个“看”，二个“到”，二个“的”，二个“这”，二十六个“个”，二个“句”，二个“子”，二个“共”，三个“有”，二个“字”，二个“其”，二个“中”，四个“一”，十七个“二”，二个“三”，四个“四”，一个“五”，二个“六”，二个“七”，一个“八”，二个“九”，四个“十”。&lt;/blockquote&gt;
&lt;p&gt;这么有意思的东西，如果你也是刚刚见到，不妨停下来自己想一想，看看有什么好的办法来构造出这样的句子。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id21"&gt;直接的思路&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当时看到这样的句子，其实我也没什么思路，开始也没想要写程序来生成，只是顺着一点儿模糊的想法在纸上打打草稿。结果因为自己的小学数学基本功太差，加加减减的时候总是会出错，越算越头疼，只好写个程序来帮我算，没想到程序就直接把结果算出来了。&lt;/p&gt;
&lt;p&gt;这中句子的结构就是一个前缀（如“你看到的这个句子”），跟着对总字数的声明（“一共有 XX 个字”），然后是对句子中包含的每一个汉字的次数的声明。那首先就得知道可能会包含哪些汉字啊。显然前缀子句和其他句子结构中出现的汉字都在其中，另外所有的数词（零一二三等）都有可能会出现。&lt;/p&gt;
&lt;p&gt;再想想每个字可能的次数。既不是数词也不在词频统计部分重复出现的字（如上面示例中的“个”字），它们的出现的次数应该是固定的，即除了在前缀等地方出现已知的次数外，还会在声明它的词频时再出现一次。比如上面第二个例子中的“你”字，它就应该是出现两次，一次是作为句子的成分（前缀部分）出现一次，另一次就是声明它的词频的时候。而数词的次数就没法直接确定了，明显能感觉到它们是牵一发而动其全身的。&lt;/p&gt;
&lt;div class="section" id="id4"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id22"&gt;初始化&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;那么先把句子模板确定下来，然后往里面套总字数和完整的词频分布。这里我对句子结构做了些简化，假设我想要生成的句子是这样的：&lt;/p&gt;
&lt;blockquote&gt;
这一句话一共有__个字，__个“__”，__个“__”，……。&lt;/blockquote&gt;
&lt;p&gt;先确定可能会出现的汉字集合，显然目前能看到的有“这”、“一”、“句”、“话”、“共”、“有”、“个”、“字”。另外预计会出现的就是所有的数词了。因为字数比较少，所以暂时就不用考虑“零”、“百”、“千”等字，只要考虑“一”到“十”即可。&lt;/p&gt;
&lt;p&gt;关于把数字用中文表示出来的方法，参见之前的博文 &lt;a class="reference external" href="https://blog.gocalf.com/number-to-chinese"&gt;将整数数字转换成中文&lt;/a&gt; ，非常简单的程序，这里不再重复。&lt;/p&gt;
&lt;p&gt;如果你的心算笔算能力还不错，下面的过程用一张纸和一支笔就能搞定了。首先列出所有可能出现的汉字，并且所有字的初始计数均为 0。总字数也要记录在案，初始值也是 0。列出来就是这样的表格，其中最后一列“TC”表示总字数。&lt;/p&gt;
&lt;style&gt;
    table.docutils thead {text-align: center;}
    table.docutils tbody {text-align: center;}
&lt;/style&gt;&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;一&lt;/th&gt;
&lt;th class="head"&gt;二&lt;/th&gt;
&lt;th class="head"&gt;三&lt;/th&gt;
&lt;th class="head"&gt;四&lt;/th&gt;
&lt;th class="head"&gt;五&lt;/th&gt;
&lt;th class="head"&gt;六&lt;/th&gt;
&lt;th class="head"&gt;七&lt;/th&gt;
&lt;th class="head"&gt;八&lt;/th&gt;
&lt;th class="head"&gt;九&lt;/th&gt;
&lt;th class="head"&gt;十&lt;/th&gt;
&lt;th class="head"&gt;个&lt;/th&gt;
&lt;th class="head"&gt;这&lt;/th&gt;
&lt;th class="head"&gt;句&lt;/th&gt;
&lt;th class="head"&gt;话&lt;/th&gt;
&lt;th class="head"&gt;共&lt;/th&gt;
&lt;th class="head"&gt;有&lt;/th&gt;
&lt;th class="head"&gt;字&lt;/th&gt;
&lt;th class="head"&gt;TC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="id5"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id23"&gt;第一次迭代&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;接下来对句子模板做第一次扫描，并根据情况更新相应汉字的字数和总字数。处理的方法很简单，每看到一个汉字，就要给它的计数加 1，总字数加 1。任何一个汉字，如果计数从 0 变成 1，即第一次遇到它，就要再给它的计数和总字数加 1，同时对“个”字执行同样的操作。把这个过程叫做“increase”操作，即：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7
8&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;def increase(character c):
    if c.count == 0:
        c.count += 2
        total.count += 2
        increase('个')
    else:
        c.count += 1
        total.count += 1
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;比如首先看到“这”字，当前计数为 0，执行 increase 操作给它的个数加 2，总字数加 2，再对“个”字执行 increase 操作。由于“个”字此时的计数也是 0，因此它的计数直接加到 2，总字数加 2，这时注意还要再对“个”字执行一次 increase 操作。但第二次对“个”字执行 increase 操作时，由于其当前计数是 2 不是 0，所以直接给计数和总字数分别加 1 就行了。处理完第一个字“这”之后，计数情况为：2 个“这”，3 个“个”，总字数 5。&lt;/p&gt;
&lt;p&gt;然后是“一”字，同样执行 increase 操作，计数增加 2，总字数增加 2，再对“个”字执行 increase，效果是其字数和总字数又分别加 1。完成后的计数情况为：2 个“这”，4 个“个”，2 个“一”，总字数 7。&lt;/p&gt;
&lt;p&gt;用类似的办法把后面的“句”、“话”、“一”、“共”、“有”、“个”、和“字”都处理完，最后得到如下的计数情况：&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;一&lt;/th&gt;
&lt;th class="head"&gt;二&lt;/th&gt;
&lt;th class="head"&gt;三&lt;/th&gt;
&lt;th class="head"&gt;四&lt;/th&gt;
&lt;th class="head"&gt;五&lt;/th&gt;
&lt;th class="head"&gt;六&lt;/th&gt;
&lt;th class="head"&gt;七&lt;/th&gt;
&lt;th class="head"&gt;八&lt;/th&gt;
&lt;th class="head"&gt;九&lt;/th&gt;
&lt;th class="head"&gt;十&lt;/th&gt;
&lt;th class="head"&gt;个&lt;/th&gt;
&lt;th class="head"&gt;这&lt;/th&gt;
&lt;th class="head"&gt;句&lt;/th&gt;
&lt;th class="head"&gt;话&lt;/th&gt;
&lt;th class="head"&gt;共&lt;/th&gt;
&lt;th class="head"&gt;有&lt;/th&gt;
&lt;th class="head"&gt;字&lt;/th&gt;
&lt;th class="head"&gt;TC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="id6"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id24"&gt;第二次迭代&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;下一轮就把所有的计数都翻译成中文，并统计它们所带来的计数改变。比如第一个计数是 3，就要对“三”字执行 increase 操作。然后是 10，对“十”字做 increase 操作。……。最后总字数 25，分别对“二”、“十”和“五”操作即可。处理完后得到新的一轮计数：&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;一&lt;/th&gt;
&lt;th class="head"&gt;二&lt;/th&gt;
&lt;th class="head"&gt;三&lt;/th&gt;
&lt;th class="head"&gt;四&lt;/th&gt;
&lt;th class="head"&gt;五&lt;/th&gt;
&lt;th class="head"&gt;六&lt;/th&gt;
&lt;th class="head"&gt;七&lt;/th&gt;
&lt;th class="head"&gt;八&lt;/th&gt;
&lt;th class="head"&gt;九&lt;/th&gt;
&lt;th class="head"&gt;十&lt;/th&gt;
&lt;th class="head"&gt;个&lt;/th&gt;
&lt;th class="head"&gt;这&lt;/th&gt;
&lt;th class="head"&gt;句&lt;/th&gt;
&lt;th class="head"&gt;话&lt;/th&gt;
&lt;th class="head"&gt;共&lt;/th&gt;
&lt;th class="head"&gt;有&lt;/th&gt;
&lt;th class="head"&gt;字&lt;/th&gt;
&lt;th class="head"&gt;TC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="id7"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id25"&gt;第三次迭代&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;对比这两组计数，发现：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;“一”、“这”、“句”等字的计数没有发生变化，并且这些数字对应的汉字已经全部统计过了，所以不必对它们做别的处理。&lt;/li&gt;
&lt;li&gt;“二”、“三”、“十”等字，计数从没有变成了若干个，就要用跟刚才一样的方法，把这些数字翻译成中文并增加相应汉字的计数。&lt;/li&gt;
&lt;li&gt;“个”字和总字数，我本来是想加入 10 和 25，但现在分别是 14 和 44，所以要把刚才加入的 10 和 25 都去掉，换成 14 和 25。比如把 10 换成 14，先对 “十”字做 decrease 操作，然后分别对“十”和“四”字执行 increase 操作。decrease 的过程如下所示，注意，由于每个字的计数都是直接从 0 涨到 2 的，所以也会直接从 2 降到 0。&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7
8&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;def decrease(character c):
    if c.count == 2:
        c.count -= 2
        total.count -= 2
        decrease('个')
    else:
        c.count -= 1
        total.count -= 1
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;这样处理完后得到的新一轮计数为：&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;col width="6%"/&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;一&lt;/th&gt;
&lt;th class="head"&gt;二&lt;/th&gt;
&lt;th class="head"&gt;三&lt;/th&gt;
&lt;th class="head"&gt;四&lt;/th&gt;
&lt;th class="head"&gt;五&lt;/th&gt;
&lt;th class="head"&gt;六&lt;/th&gt;
&lt;th class="head"&gt;七&lt;/th&gt;
&lt;th class="head"&gt;八&lt;/th&gt;
&lt;th class="head"&gt;九&lt;/th&gt;
&lt;th class="head"&gt;十&lt;/th&gt;
&lt;th class="head"&gt;个&lt;/th&gt;
&lt;th class="head"&gt;这&lt;/th&gt;
&lt;th class="head"&gt;句&lt;/th&gt;
&lt;th class="head"&gt;话&lt;/th&gt;
&lt;th class="head"&gt;共&lt;/th&gt;
&lt;th class="head"&gt;有&lt;/th&gt;
&lt;th class="head"&gt;字&lt;/th&gt;
&lt;th class="head"&gt;TC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="id8"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id26"&gt;周而复始&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;之后用同样的方法一轮一轮地迭代，结果如下（包含从初始化开始的每次迭代，第一列 ID 表示迭代次数）：&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;ID&lt;/th&gt;
&lt;th class="head"&gt;一&lt;/th&gt;
&lt;th class="head"&gt;二&lt;/th&gt;
&lt;th class="head"&gt;三&lt;/th&gt;
&lt;th class="head"&gt;四&lt;/th&gt;
&lt;th class="head"&gt;五&lt;/th&gt;
&lt;th class="head"&gt;六&lt;/th&gt;
&lt;th class="head"&gt;七&lt;/th&gt;
&lt;th class="head"&gt;八&lt;/th&gt;
&lt;th class="head"&gt;九&lt;/th&gt;
&lt;th class="head"&gt;十&lt;/th&gt;
&lt;th class="head"&gt;个&lt;/th&gt;
&lt;th class="head"&gt;这&lt;/th&gt;
&lt;th class="head"&gt;句&lt;/th&gt;
&lt;th class="head"&gt;话&lt;/th&gt;
&lt;th class="head"&gt;共&lt;/th&gt;
&lt;th class="head"&gt;有&lt;/th&gt;
&lt;th class="head"&gt;字&lt;/th&gt;
&lt;th class="head"&gt;TC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;很高兴地发现，第十次的结果和第十一次的结果是完全一致的，这时候就没有任何操作可做了，实际上算法也就终止了。显然，通过这组数据生成的句子就是符合要求的自我统计的句子：&lt;/p&gt;
&lt;blockquote&gt;
这一句话一共有五十九个字，四个“一”，十一个“二”，二个“三”，三个“四”，二个“五”，二个“七”，二个“九”，四个“十”，十七个“个”，二个“这”，二个“句”，二个“话”，二个“共”，二个“有”，二个“字”。&lt;/blockquote&gt;
&lt;/div&gt;
&lt;div class="section" id="id9"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id27"&gt;算法&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;总的看来，一轮一轮迭代的处理方法是这样的：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;所有汉字的计数和总字数均初始化为 0；&lt;/li&gt;
&lt;li&gt;遍历句子模板中的每一个汉字，对其计数做“increase”操作，得到一组新的计数；&lt;/li&gt;
&lt;li&gt;比较当前计数数组与前一轮计数数组中的每一项：&lt;ol class="arabic"&gt;
&lt;li&gt;如果二者一致，无操作；&lt;/li&gt;
&lt;li&gt;否则，对前一轮的数值对应的每个汉字执行 decrease 操作（0 除外），对当前数值对应的每个汉字执行 increase 操作（0 除外）；&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;重复步骤 3，直到相邻两轮计数数组完全一致。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;其中 increase 和 decrease 操作均如前所述，不再重复了。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id10"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id28"&gt;初遇困境&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="id11"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id29"&gt;问题&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;换一个例子看看，把上面的句子模板开头的“这一句话”换成“新的一句话”，即：&lt;/p&gt;
&lt;blockquote&gt;
新的一句话一共有__个字，__个“__”，__个“__”，……。&lt;/blockquote&gt;
&lt;p&gt;按照同样的算法一轮一轮迭代处理，却永远都无法终止。仔细看了看，发现计数数组会在几组值之间不断地反复，却怎么都无法收敛。具体的数据就不列出来了。&lt;/p&gt;
&lt;p&gt;想想也是，上面的算法只是保证了，如果收敛，得到的一定是满足条件的解，却完全无法保证收敛。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id12"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id30"&gt;解决&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;想到一个比较简单的变通方法就是修改之前的 decrease 操作。本来一个汉字如果是第一次出现，计数就直接从 0 涨到 2，如果要去掉，也直接从 2 回到 0。这样避免了出现“一个 XX”的情况。这其实不是必须的，如果把条件放宽，允许出现“一个 XX”（仍然符合自我统计的要求，只是显得有点儿多余），可以让 decrease 操作把计数从 2 降到 1。写出来大概是这样：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;def decrease(character c):
    c.count -= 1
    total.count -= 1
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;根据这个规则重新迭代计算，结果如下表示，发现到第 9 次迭代后就收敛完毕。&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;ID&lt;/th&gt;
&lt;th class="head"&gt;一&lt;/th&gt;
&lt;th class="head"&gt;二&lt;/th&gt;
&lt;th class="head"&gt;三&lt;/th&gt;
&lt;th class="head"&gt;四&lt;/th&gt;
&lt;th class="head"&gt;五&lt;/th&gt;
&lt;th class="head"&gt;六&lt;/th&gt;
&lt;th class="head"&gt;七&lt;/th&gt;
&lt;th class="head"&gt;八&lt;/th&gt;
&lt;th class="head"&gt;九&lt;/th&gt;
&lt;th class="head"&gt;十&lt;/th&gt;
&lt;th class="head"&gt;个&lt;/th&gt;
&lt;th class="head"&gt;新&lt;/th&gt;
&lt;th class="head"&gt;的&lt;/th&gt;
&lt;th class="head"&gt;句&lt;/th&gt;
&lt;th class="head"&gt;话&lt;/th&gt;
&lt;th class="head"&gt;共&lt;/th&gt;
&lt;th class="head"&gt;有&lt;/th&gt;
&lt;th class="head"&gt;字&lt;/th&gt;
&lt;th class="head"&gt;TC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;这样得到的自统计句子是：&lt;/p&gt;
&lt;blockquote&gt;
新的一句话一共有六十六个字，五个“一”，十一个“二”，三个“三”，二个“四”，二个“五”，三个“六”，一个“八”，二个“九”，四个“十”，十九个“个”，二个“新”，二个“的”，二个“句”，二个“话”，二个“共”，二个“有”，二个“字”。&lt;/blockquote&gt;
&lt;p&gt;其中比较特殊的就是“一个‘八’”，句子中确实只有这个地方出现了 1 个“八”字，其他字的个数和总字数也都没错，但这个“一个‘八’”没有什么实际的意义。不引入这种“一个 XX”能否找到符合这个模板的解呢？目前我还没有明确的答案。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id13"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id31"&gt;再遇困境&lt;/a&gt;&lt;/h2&gt;
&lt;div class="section" id="id14"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id32"&gt;问题&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;很快就又遇到了新的麻烦，比如把句子模板开头换成“这句话”，即：&lt;/p&gt;
&lt;blockquote&gt;
这句话一共有__个字，__个“__”，__个“__”，……。&lt;/blockquote&gt;
&lt;p&gt;算一下就会发现，不论是像开始那样直接从 2 减到 0，还是像刚才那样从 2 减到 1，全都会陷入无法终止的迭代。&lt;/p&gt;
&lt;p&gt;实际上随便想一个句子模板出来，十有八九会是这样的，能像前两个例子那样收敛出结果的非常少。这可怎么办呢？&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id15"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="#id33"&gt;解决&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;想了一个简单粗暴的办法，效果还不错。前面提到 decrease 操作有两个方案，区别在于对于计数 2 做 decrease 的时候，要么直接减到 0，要么减到 1。那么一个直接的想法就是不要那么死板，让这个抉择可以随机的使用，即有一半的概率会直接减到 0，另一半的概率是减到 1。这个不确定性因素实际上是给迭代过程带来了一点儿干扰。如果按照某个确定的方案，迭代过程很容易陷入无穷尽的震荡，这时候如果引入一下随机的干扰，就有可能打破稳定的震荡，使得迭代过程偏离当前的动态平衡点，或许就刚好落在一个收敛的位置上了。&lt;/p&gt;
&lt;p&gt;改造后的 decrease 操作大致是这样的：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7
8&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;def decrease(character c):
    if c.count == 2 and random.choice(0, 1) == 0:
        c.count -= 2
        total.count -= 2
        decrease('个')
    else:
        c.count -= 1
        total.count -= 1
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;用上面遇到问题的模板来说，以 3 作为随机数种子时可以得到这样的迭代过程，其中第 9 次和第 10 次的结果一致，是一个可行解。&lt;/p&gt;
&lt;table border="1" class="docutils"&gt;
&lt;colgroup&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;col width="5%"/&gt;
&lt;/colgroup&gt;
&lt;thead valign="bottom"&gt;
&lt;tr&gt;&lt;th class="head"&gt;ID&lt;/th&gt;
&lt;th class="head"&gt;一&lt;/th&gt;
&lt;th class="head"&gt;二&lt;/th&gt;
&lt;th class="head"&gt;三&lt;/th&gt;
&lt;th class="head"&gt;四&lt;/th&gt;
&lt;th class="head"&gt;五&lt;/th&gt;
&lt;th class="head"&gt;六&lt;/th&gt;
&lt;th class="head"&gt;七&lt;/th&gt;
&lt;th class="head"&gt;八&lt;/th&gt;
&lt;th class="head"&gt;九&lt;/th&gt;
&lt;th class="head"&gt;十&lt;/th&gt;
&lt;th class="head"&gt;个&lt;/th&gt;
&lt;th class="head"&gt;这&lt;/th&gt;
&lt;th class="head"&gt;句&lt;/th&gt;
&lt;th class="head"&gt;话&lt;/th&gt;
&lt;th class="head"&gt;共&lt;/th&gt;
&lt;th class="head"&gt;有&lt;/th&gt;
&lt;th class="head"&gt;字&lt;/th&gt;
&lt;th class="head"&gt;TC&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody valign="top"&gt;
&lt;tr&gt;&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;56&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;把计数结果带入到模板中，得到一个自统计句子：&lt;/p&gt;
&lt;blockquote&gt;
这句话一共有五十八个字，三个“一”，十一个“二”，三个“三”，二个“四”，二个“五”，二个“七”，二个“八”，四个“十”，十七个“个”，二个“这”，二个“句”，二个“话”，二个“共”，二个“有”，二个“字”。&lt;/blockquote&gt;
&lt;p&gt;思考一下随机干扰对迭代收敛的作用，可以想见，这个随机选择进行的越频繁，迭代过程就越不稳定，遇到收敛点的概率相对也就越大。但如果迭代已经陷入到一个稳定震荡的状态，在整个振荡周期内却始终没有用到这个随机干扰，那还是没有办法跳出死循环。所以实际的程序会通过某种方式（最简单的就是设置最大迭代次数）判断是否陷入死循环并强制终止当前的迭代过程，从头开始重新走一遍。由于早期计数比较小，很容易遇到需要从 2 减到 0 或 1 的情况，大量的随机干扰可能会使得整个迭代过程完全变样，有可能会得到收敛的解。&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id16"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id34"&gt;三碗不过岗&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;引入随机扰动后，大部分问题都能搞定了。但对于过于复杂的模板依旧无能为力，模板复杂之后，能够遇到从 2 减到 0 或 1 的次数很少，很难跳出死循环。&lt;/p&gt;
&lt;p&gt;有时候很简单的模板也无法得到结果，比如&lt;/p&gt;
&lt;blockquote&gt;
一三五七一共有__个字，__个 “__”，__个 “__”，……。&lt;/blockquote&gt;
&lt;p&gt;这里可能有两个问题，一个是我的算法只能保证收敛得到的结果是正确的，无法保证一定收敛。加入随机干扰可能在一定程度上加大收敛的概率，却也没有本质的提升。另一个是任意给定一个模板，是否一定有解？这方面也还没有太多的思路。&lt;/p&gt;
&lt;p&gt;相关的程序 &lt;a class="reference external" href="https://github.com/calfzhou/self-descriptive-sentence"&gt;在 github 上&lt;/a&gt;，目前除了可以生成中文的句子，还可以生成基于数字的句子。比如：&lt;/p&gt;
&lt;blockquote&gt;
1 employs 11 digits, 4 1's, 3 2's, 2 3's, 2 4's.&lt;/blockquote&gt;
&lt;p&gt;以后可能会在两个方面做改进，一是模板化，一是迭代算法。现在是按照语言分的，其实语言只是模板的一个因素。模板化之后可以用同样的方法构造出更多更有趣的句子来。迭代算法方面，目前想到的是利用遗传算法，不过具体怎么操作还没有太多的想法。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id17"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id35"&gt;后记&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;说来也巧，刚才无意间又找到了十年前我所看到的文章，原来是 &lt;a class="reference external" href="http://blog.joycode.com/zee/"&gt;@zee&lt;/a&gt; 在 2004 年 8 月的一篇博文 &lt;a class="reference external" href="http://blog.joycode.com/zee/archives/2004/08/04/29469.joy"&gt;《以前玩出来的几个句子，怀旧一下》&lt;/a&gt;。摘录原文如下。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;一年多前写程序生成的，当时好像 VS2K3 刚出来，写这个程序的主要目的是为了玩弄 2K3&lt;/p&gt;
&lt;p&gt;聪明的张先生生成的这个句子一共有一百一十三个字其中有二个百三个有二个子七个三三个生十五个二二个先二个字一个八二个七一个四二个明一个六二个成一个零二个张二个中二个其二个共二个这五个十二个句十个一三十一个个三个的二个聪一个九三个五&lt;/p&gt;
&lt;p&gt;你看到的这个句子一共有九十个字。其中有：二个“你”，二个“看”，二个“到”，二个“的”，二个“这”，二十六个“个”，二个“句”，二个“子”，二个“共”，三个“有”，二个“字”，二个“其”，二个“中”，四个“一”，十七个“二”，二个“三”，四个“四”，一个“五”，二个“六”，二个“七”，一个“八”，二个“九”，四个“十”。&lt;/p&gt;
&lt;p&gt;这个句子一共有七十五个字。其中有四个“十”；二个“子”；四个“三”；十二个“二”；二个“字”；一个“八”；三个“四”；一个“六”；二个“七”；二个“中”；二个“其”；二个“共”；二个“这”；二个“句”；五个“一”；二十二个“个”；三个“有”；一个“九”；三个“五”。&lt;/p&gt;
&lt;p&gt;现在是既没有心情也没有时间玩这些东东了............&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;谨以此纪念逝去的岁月。&lt;/p&gt;
&lt;/div&gt;
</content><category term="算法"></category><category term="Puzzle"></category><category term="Natural Language"></category></entry><entry><title>从大量整数中选取最小 / 大的若干个</title><link href="https://blog.gocalf.com/topn-of-massive-data" rel="alternate"></link><published>2012-04-17T15:09:00+08:00</published><updated>2012-04-17T15:09:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2012-04-17:/topn-of-massive-data</id><summary type="html">&lt;p class="first last"&gt;问题描述：现在有非常大量的一堆对象，比如有几十亿甚至上百亿个。对象本身是什么可以忽略，每个对象都有唯一标识符和一个正整数属性值，属性值范围有限（不大于一亿）。在单核机器上，内存和磁盘空间充足，用什么方法可以最快地输出属性值最小的若干（如一万）个对象，要求输出结果按照属性值排序。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;问题描述：现在有非常大量的一堆对象，比如有几十亿甚至上百亿个。对象本身是什么可以忽略，每个对象都有唯一标识符和一个正整数属性值，属性值范围有限（不大于一亿）。在单核机器上，内存和磁盘空间充足，用什么方法可以最快地输出属性值最小的若干（如一万）个对象，要求输出结果按照属性值排序。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;先说个题外话。前几天面试的时候，问了一个用栈模拟队列的题目，被 candidate 反问：这种问题在工作中会遇到么？有用么？&lt;/p&gt;
&lt;p&gt;这个问题其实很好，值得我仔细思考。大体上来讲，我在面试的时候可能会这么几类问题（聊项目经验之类的除外）：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;某个编程语言的基础知识，如果 candidate 自称熟悉某语言；&lt;/li&gt;
&lt;li&gt;常见的数据结构，用来大概了解 candidate 的基本功；&lt;/li&gt;
&lt;li&gt;基本的编程题目，考察他的写代码能力；&lt;/li&gt;
&lt;li&gt;数学或算法问题，一方面了解 candidate 的基本功，另一方面看看他的思维能力和解决问题的能力；&lt;/li&gt;
&lt;li&gt;一些从实际工作中抽象出来的，相对而言比较开放的题目，主要是看他分析问题和解决问题的能力。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;今天这个题目是我最近比较喜欢的一个问题，是曾经在工作中遇到过的。&lt;/p&gt;
&lt;p&gt;先来看看题目中出现的数字带来什么信息。&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;对象的个数（设为 n），十亿甚至百亿：也就是 10^9 到 10^10 这样的量级，已经接近甚至超过 32 位整数的范围。即使每个对象只占用 1 字节，总共也需要 1G 到 10G 的空间。&lt;/li&gt;
&lt;li&gt;对象属性值的范围，正整数，一亿：相对于个数，属性值的范围还是相当有限的，最多有 100M 个各不相同的值，可以用 32 位整数表示。&lt;/li&gt;
&lt;li&gt;要求选取的对象个数（设为 m），一万左右：相对于个数来说是非常非常小的。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可见，单单保存所有的属性值也需要 4G 到 40G 的空间。标识符最少也得用 64 位整数表示，又需要 8G 到 80G 的空间。&lt;/p&gt;
&lt;p&gt;在这种特殊的要求下，怎么处理是最高效的呢？来看看以下的几种方法。&lt;/p&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;方法一：快速选择 / 线性选择算法&lt;/h2&gt;
&lt;p&gt;快速选择和线性选择算法都是平均时间复杂度 O(n) 的选择算法，当然线性选择算法在最坏情况下也能保证 O(n) 时间，缺点是实现起来比较复杂。简单起见，我就用快速选择算法。&lt;/p&gt;
&lt;p&gt;快速选择算法类似于快速排序，用一个轴值将数组分成两部分，一部分全都比轴值小，而另一部分全都比轴值大。然后看看两部分分别包含多少个元素，从而确定第 m 大的元素应该再哪一半，然后对那一半递归处理，直到找到第 m 大的元素。&lt;/p&gt;
&lt;p&gt;但是将快速选择算法应用到本题时，遇到主要问题是内存恐怕不够。&lt;/p&gt;
&lt;p&gt;如果在内存中放入所有的对象，我们需要 12G 到 120G 内存，取决于对象的个数是十亿还是一百亿。进入内存后，还需要至少两次遍历才可以找到第 m 大的元素。另外加载数据时需要有一次完整的文件遍历。&lt;/p&gt;
&lt;p&gt;如果内存中无法放入所有的对象，那就比较麻烦了，在头几次二分递归的时候可能需要动用硬盘来缓存数据，磁盘 IO 将成为可怕的瓶颈。累加起来相当于至少两次全文件的读遍历和写遍历。实际上，在确定了第 m 大的对象后，可能还需要遍历一次整个文件，找出比它小的对象。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;方法二：堆排序算法&lt;/h2&gt;
&lt;p&gt;堆排序也是一个很好的可以用于部分排序的算法，C++
STL 就用堆排序来实现 partial_sort。&lt;/p&gt;
&lt;p&gt;在内存中维护一个大小为 m 的最大值堆（没错，是最大值堆），遍历整个文件，每拿到一个对象，拿它与堆顶的属性值比较一下，如果新对象的属性值大就直接丢弃，否则用它取代堆顶元素。平均来讲，这样处理的时间复杂度是 n
* log m。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h2&gt;方法三：哈希算法&lt;/h2&gt;
&lt;p&gt;我们注意到，虽然对象的个数非常大，但属性值的范围非常小（相对来讲）。如果在值域范围上建立一个哈希表，只需要 100M 个格子，如果一个格子存储一个 32 位整数，只需要 400M 内存。&lt;/p&gt;
&lt;p&gt;哈希表总是会有冲突的，在这个问题中，冲突是必然的，平均每个属性值上会有 10 到 100 个不同的对象。但处理冲突的办法非常简单，因为我们不需要在哈希表中记录每个对象，只需要记录这个属性值对应的对象的个数。&lt;/p&gt;
&lt;p&gt;开辟一个能存放 100M 个 32 位整数的数组（为保险起见，可以用 64 位整数，但总共也只需要 800M 内存），数组的下标对应于属性值（实际操作中可能要减一）。然后遍历整个文件，每拿到一个对象，将对应的数组元素值加一。&lt;/p&gt;
&lt;p&gt;文件遍历完后，过一下这个数组，可以找出第 m 大的对象的属性值，这个值就是一个边界。然后再遍历一次原始文件，把属性值小于等于边界的对象都放到内存中（注意在相等时，会有个数的限制）。最后把内存中的 m 个对象按照属性值排一下序再输出即可。&lt;/p&gt;
&lt;p&gt;这样最多只需要遍历两次文件，使用 O(n) 时间就可以完成题目的要求。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id5"&gt;
&lt;h2&gt;到底哪个方法快？&lt;/h2&gt;
&lt;p&gt;上面提到了三种算法，到底哪一个最快呢？说说你的看法吧？&lt;/p&gt;
&lt;p&gt;我以前一直觉得哈希法是最快的，它对内存的需求量适中，算法是线性时间。但后来又仔细想了想，觉得不太对。这里实际上不完全是内存中的运算了，瓶颈主要是在磁盘 IO 上。&lt;/p&gt;
&lt;p&gt;让我们来比较一下三种算法：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;快速选择算法（所有对象可以全进内存）：只需要一次文件读遍历，内存操作是 O(n) 时间（系数至少为 2，可能会很大）。&lt;/li&gt;
&lt;li&gt;快速选择算法（只有部分对象可进内存）：平均需要三次文件读遍历，两次写遍历，内存操作是 O(n) 时间（系数至少为 2，可能会很大）。&lt;/li&gt;
&lt;li&gt;堆排序算法：需要一次文件读遍历，内存操作是 O(n * log
m) 时间，这里 m 取 10000 的话，大概是 13。当然实际数值会少于 13，因为并不是每个对象都需要进入堆中。&lt;/li&gt;
&lt;li&gt;哈希算法（所有对象可以全进内存）：需要一次文件读遍历，在内存中要开辟大小为 O(n) 和 O(m) 的两块缓冲区，内存操作时间为 O(n)（系数大约为 2）。&lt;/li&gt;
&lt;li&gt;哈希算法（只有部分对象可进内存）：需要两次文件读遍历，内存操作是 O(n) 时间（系数小于 2）。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;题目的本意就是几乎不可能让所有对象都进入内存。虽然堆排序的内存操作时间复杂度偏高，却只需要一次磁盘遍历操作，其消耗的时间应该要小于哈希算法的。你是否同意呢？&lt;/p&gt;
&lt;p&gt;最近做了个实验，生成了十亿个对象，每个对象有一个 64 位整数作为标识符，还有一个不超过一亿的随机整数作为属性值。生成的文件用文本格式存储，占用 18G 磁盘空间。我没有实验快速选择算法，只是比较了堆排序和哈希。实验结果是：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;单纯遍历一次文件，包括逐行读取，把属性值解析成整数：耗时 34 分钟；&lt;/li&gt;
&lt;li&gt;堆排序算法：耗时 40 分钟；&lt;/li&gt;
&lt;li&gt;哈希算法：耗时 73 分钟。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这三个时间的相对值是否在你的预料之中呢？&lt;/p&gt;
&lt;p&gt;当然，如果把硬盘换成 SSD 恐怕结果又完全不同了，有兴趣的童鞋可以试一试。&lt;/p&gt;
&lt;/div&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Selection Algorithm"></category></entry><entry><title>程序基本功之遍历二叉树</title><link href="https://blog.gocalf.com/traversing-binary-tree" rel="alternate"></link><published>2012-04-04T16:51:00+08:00</published><updated>2012-04-04T16:51:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2012-04-04:/traversing-binary-tree</id><summary type="html">&lt;p class="first last"&gt;最近工作忙，没时间思考复杂的问题了。正好要招人就得有面试的嘛，自己也温习一下，要不然怎么去问别人。今天复习一下二叉树的遍历，前序（pre-order，NLR）、中序（in-order，LNR）、后序（post-order，LRN）、层序（level-order），用和不用递归。&lt;/p&gt;
</summary><content type="html">
&lt;p&gt;最近工作忙，没时间思考复杂的问题了。正好要招人就得有面试的嘛，自己也温习一下，要不然怎么去问别人。&lt;/p&gt;
&lt;p&gt;今天复习一下二叉树的遍历，前序（pre-order，NLR）、中序（in-order，LNR）、后序（post-order，LRN）、层序（level-order），用和不用递归。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;概念就不用多解释了，前、中、后是指根结点的访问时机，在左、右子树之前、中间、或之后。层序就是从根结点开始从上至下、从左到右地依次访问。&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="bin-tree" src="https://blog.gocalf.com/images/2012/04/bin-tree.png"/&gt;
&lt;p class="caption"&gt;一棵二叉树&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;如上图所示的一棵二叉树，对应的遍历结果分别是：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;前序（NLR）：&lt;tt class="docutils literal"&gt;A B D C E G H F I&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;中序（LNR）：&lt;tt class="docutils literal"&gt;D B A G E H C F I&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;后序（LRN）：&lt;tt class="docutils literal"&gt;D B G H E I F C A&lt;/tt&gt;&lt;/li&gt;
&lt;li&gt;层序：&lt;tt class="docutils literal"&gt;A B C D E F G H I&lt;/tt&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id7"&gt;一、用递归处理二叉树的前序、中序和后序遍历&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;递归真是一个迷人东西，它可以把复杂的逻辑变得异常简洁，这也是自然界的表现形式之一。基于递归的前、中、后序遍历二叉树的程序几乎完全相同，用两个递归调用分别处理左、右子树，剩下的事情就是打印根结点。为节省篇幅，直接把三个程序写在一起，用一个参数来控制是哪种遍历方式，也可以更方便地看出三者之间的区别。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;VisitTree_Recursive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'NLR'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;VisitTree_Recursive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'LNR'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;VisitTree_Recursive&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'LRN'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id8"&gt;二、非递归的前序、中序遍历&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;如果不用递归呢？实际上我们要做的就是自己维护一个栈（数据结构）来保存需要但尚未来得及处理的数据。&lt;/p&gt;
&lt;p&gt;前序和中序都是非常简单的，当遇到一个非空的根结点时，打印其数据（如果是前序遍历），并将其压栈，然后递归地（这里用循环来模拟递归）处理其左子结点；当没有左子结点时，从栈中弹出之前遇到的某个根结点（它没有左子结点，或者左子结点已经处理完毕，需要再处理右子结点），打印数据（如果是中序遍历），然后继续处理右子结点。同样地，把两种遍历方式写在一起以便比较。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;VisitTree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'NLR'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;order&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'LNR'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id9"&gt;三、非递归的后序遍历&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;后序遍历要稍微复杂一点点，在前序和中序遍历的程序中，当我们准备进入根结点的右子树时，根结点就被扔出栈外了。但在后序遍历时，我们仍需保留它，直到右子树处理完毕。&lt;/p&gt;
&lt;p&gt;首先想到的改动就是在上面的程序的第 9 行到 11 行，不要从栈 s 中将根结点弹出，而是直接开始处理右子结点。但这就会带来一个问题：什么时候弹出根结点？实际上当左子树遍历完成、或者右子树遍历完成时，我们都会在栈里看到根结点，为了区分这两种状态，添加一个临时变量记录前一次访问的结点，如果前一个结点是根结点的右子树，就说明左右子树全都遍历完成了。非常简单。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;VisitTreeLRN&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
      &lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;pre&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;div class="section" id="id5"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id10"&gt;四、非递归的层序遍历&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;层序遍历可以写成递归吗？还真没研究过。非递归的时候，层序遍历使用的是队列，而非栈。&lt;/p&gt;
&lt;p&gt;处理过程非常简明，遇到一个结点，打印信息，然后依次将左、右子结点加入队列等待后续处理。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;collections&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;VisitTree_LevelOrder&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;
  &lt;span class="n"&gt;q&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;deque&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;popleft&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
&lt;div class="section" id="id6"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id11"&gt;附录&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上面的 python 代码基于 v2.7。另外可以用下面这段代码来定义最简单的二叉树结点类，生成最上面图示的二叉树：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;

&lt;span class="n"&gt;g&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'G'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'H'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'E'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'I'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'F'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'C'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'D'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'B'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'A'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;/div&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Data Structure"></category><category term="Binary Tree"></category></entry><entry><title>计算斐波纳契数，分析算法复杂度</title><link href="https://blog.gocalf.com/calc-fibonacci" rel="alternate"></link><published>2011-11-22T20:34:00+08:00</published><updated>2012-12-04T17:00:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-11-22:/calc-fibonacci</id><summary type="html">&lt;p class="first last"&gt;问题描述：Fibonacci 数（Fibonacci Number）的定义是：F(n) = F(n - 1) + F(n - 2)，并且 F(0) = 0，F(1) = 1。对于任意指定的整数 n（n &amp;gt;= 0），计算 F(n)，并分析算法的时间、空间复杂度。&lt;/p&gt;
</summary><content type="html">
&lt;p&gt;问题描述：Fibonacci 数（&lt;a class="reference external" href="http://en.wikipedia.org/wiki/Fibonacci_number"&gt;Fibonacci Number&lt;/a&gt;）的定义是：F(n) = F(n -
1) + F(n - 2)，并且 F(0) = 0，F(1) = 1。对于任意指定的整数 n（n ≥
0），计算 F(n) 的精确值，并分析算法的时间、空间复杂度。&lt;/p&gt;
&lt;p&gt;假设系统中已经提供任意精度长整数的运算，可以直接使用。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;这其实是个老生常谈的问题了，不过可能在复杂度分析的时候，很多人忽略了一些事情。另外这个问题恰好有几种复杂度迥异的算法，在刚刚介绍完 &lt;a class="reference external" href="https://blog.gocalf.com/algorithm-complexity-and-master-theorem"&gt;算法复杂度&lt;/a&gt; 之后，正好来直观地理解一下。&lt;/p&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id9"&gt;一、递归法&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;一个看起来很直观、用起来很恐怖的算法就是递归法。根据 Fibonacci 的递推公式，对于输入的 n，直接递归地调用相同的函数分别求出 F(n
- 1) 和 F(n -
2)，二者相加就是结果。递归的终止点就是递推方程的初值，即 n 取 0 或 1 的时候。&lt;/p&gt;
&lt;p&gt;程序（in
Python）写出来那也是相当的简洁直观（为了跟后面的程序区分开来，这里取名 &lt;tt class="docutils literal"&gt;SlowFibonacci&lt;/tt&gt;）。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;SlowFibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'invalid n'&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;  &lt;span class="c1"&gt;# F(0) = 0, F(1) = 1&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;SlowFibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;SlowFibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;这个算法的时间复杂度有着跟 Fibonacci 类似的递推方程：T(n) = T(n - 1) + T(n
- 2) + O(1)，很容易得到 &lt;strong&gt;T(n) = O(1.618 ^
n)&lt;/strong&gt;（1.618 就是黄金分割，&lt;span class="math"&gt;\((1+\sqrt5)/2\)&lt;/span&gt;）。空间复杂度取决于递归的深度，显然是 &lt;strong&gt;O(n)&lt;/strong&gt;。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id10"&gt;二、递推法&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;虽然只是一字之差，但递推法的复杂度要小的多。这个方法就是按照递推方程，从 n
= 0 和 n =
1 开始，逐个求出所有小于 n 的 Fibonacci 数，最后就可以算出 F(n)。由于每次计算值需要用到前两个 Fibonacci 数，更小的数就可以丢弃了，可以将空间复杂度降到最低。算法如下：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;NormFibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'invalid n'&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# F(0), F(1)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prev&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prev&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;显然时间复杂度是 &lt;strong&gt;O(n)&lt;/strong&gt;，空间复杂度是 &lt;strong&gt;O(1)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;比较一下递归法和递推法，二者都用了分治的思想——把目标问题拆为若干个小问题，利用小问题的解得到目标问题的解。二者的区别实际上就是普通分治算法和动态规划的区别。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id11"&gt;三、矩阵法&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;算 Fibonacci 数精确值的最快的方法应该就是矩阵法，看过的人都觉得这个方法很好。如果你跟我一样，曾经为记住这个方法中的矩阵而烦恼，那今天就来看看怎么进行推导。其实方法非常简单，想清楚了也就自然而然地记住了。&lt;/p&gt;
&lt;p&gt;我们把 Fibonacci 数列中相邻的两项：F(n) 和 F(n -
1) 写成一个 2x1 的矩阵，然后对其进行变形，看能得到什么：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{bmatrix}F_n\\F_{n-1}\end{bmatrix}
=\begin{bmatrix}F_{n-1}+F_{n-2}\\F_{n-1}\end{bmatrix}
=\begin{bmatrix}1\times F_{n-1}+1\times F_{n-2}\\1\times F_{n-1}+0\times F_{n-2}\end{bmatrix}
=\begin{bmatrix}1&amp;amp;1\\1&amp;amp;0\end{bmatrix}\times\begin{bmatrix}F_{n-1}\\F_{n-2}\end{bmatrix}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;是不是非常自然呢？把等式最右边继续算下去，最后得到：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{bmatrix}F_n\\F_{n-1}\end{bmatrix}
=\begin{bmatrix}1&amp;amp;1\\1&amp;amp;0\end{bmatrix}^{n-1}\times\begin{bmatrix}F_{1}\\F_{0}\end{bmatrix}
=\begin{bmatrix}1&amp;amp;1\\1&amp;amp;0\end{bmatrix}^{n-1}\times\begin{bmatrix}1\\0\end{bmatrix}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;因此要求 F(n)，只要对这个二阶方阵求 n -
1 次方，最后取结果方阵第一行第一列的数字就可以了。&lt;/p&gt;
&lt;p&gt;看起来有点儿化简为繁的感觉，但关键点在于，幂运算是可以二分加速的。设有一个方阵 a，利用分治法求 a 的 n 次方，有：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
a^n=\begin{cases}
a^{n/2}\times a^{n/2}&amp;amp;,\text{ if }x\text{ is even}\\
a^{(n-1)/2}\times a^{(n-1)/2}\times a&amp;amp;,\text{ if }x\text{ is odd}
\end{cases}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;可见复杂度满足 T(n) = T(n / 2) + O(1)，根据 &lt;a class="reference external" href="https://blog.gocalf.com/algorithm-complexity-and-master-theorem"&gt;Master 定理&lt;/a&gt; 可得：T(n) =
O(log n)。&lt;/p&gt;
&lt;p&gt;在实现的时候，可以用循环代替递归实现这里的二分分治，好处是降低了空间复杂度（用递归的话，空间复杂度为 O(log
n)）。下面的 Python 程序直接利用的 numpy 库中的矩阵乘法（当然这个库也实现了矩阵的幂运算，我把它单独写出来是为了强调这里的分治算法）。另外如果不用第三方库，我也给出了矩阵乘法的简单实现。&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;Using numpy Library&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;numpy&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;MatrixPower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'invalid n'&lt;/span&gt;
  &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mat&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;FastFibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'invalid n'&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;  &lt;span class="c1"&gt;# F(0) = 0, F(1) = 1&lt;/span&gt;
  &lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matrix&lt;/span&gt;&lt;span class="p"&gt;([[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt; &lt;span class="n"&gt;dtype&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MatrixPower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;ul class="simple"&gt;
&lt;li&gt;Without numpy Library&lt;/li&gt;
&lt;/ul&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;DotProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'x and y must have the same length'&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;MatrixMultiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# x is a m*a matrix, y is a a*n matrix.&lt;/span&gt;
  &lt;span class="c1"&gt;# x * y is a m*n matrix.&lt;/span&gt;
  &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;

  &lt;span class="c1"&gt;# transpose y&lt;/span&gt;
  &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

  &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="n"&gt;DotProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;MatrixPower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'invalid n'&lt;/span&gt;
  &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mat&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MatrixMultiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;temp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MatrixMultiply&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;res&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;FastFibonacci&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'invalid n'&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;  &lt;span class="c1"&gt;# F(0) = 0, F(1) = 1&lt;/span&gt;
  &lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="n"&gt;mat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;MatrixPower&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mat&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;二阶方阵相乘一次可以看成是常数时间（虽然这个常数会比较大），因此整个算法的时间复杂度是 &lt;strong&gt;O(log
n)&lt;/strong&gt;，空间复杂度是 &lt;strong&gt;O(1)&lt;/strong&gt;。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id5"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id12"&gt;四、运行时间大比拼&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;至此，我们得到的时间复杂度分别是 O(1.618 ^ n)、O(n) 和 O(log
n) 的算法，让我们来直观地比较比较它们。&lt;/p&gt;
&lt;p&gt;用 Python 的 timeit 模块对以上三个算法的运行时间进行了测量，记录了每个算法对于不同的 n 的每千次运算所消耗的时间（单位是秒），部分数据记录在 &lt;a class="reference external" href="https://blog.gocalf.com/assets/2011/11/fibonacci_data.zip"&gt;fibonacci_data&lt;/a&gt;。利用 Mathematica 可以很方便地对这些数据进行拟合，对于较小的 n，用三个复杂度表达式分别去拟合，得到的效果都非常好。尤其值得注意的是，对于第一个算法，我用 a
* b ^ n 去拟合，结果得到 b 等于 1.61816，这与黄金分割数的正确值相差无几。&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;递归法拟合结果：0.000501741 * 1.61816 ^ n，RSquare = 0.999993。&lt;/li&gt;
&lt;li&gt;递推法拟合结果：0.000788421 + 0.000115831 * n，RSquare = 0.999464。&lt;/li&gt;
&lt;li&gt;矩阵法拟合结果：-0.0114923 + 0.0253609 log(n)，RSquare = 0.986576。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;下图是 n &amp;lt;= 35 时，三种算法的千次运行耗时比较。其中红色为 O(1.618 ^
n) 的递归法；蓝色为 O(n) 的递推法；绿色为 O(log
n) 的矩阵法。散点为实际测量到的运行时间，实线为拟合方程的曲线。&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="compare_a" src="https://blog.gocalf.com/images/2011/11/compare_a.png"/&gt;
&lt;p class="caption"&gt;三种算法的运行时间比较&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;当 n &amp;gt;
10 的时候，指数时间就已经超出画面范围了。另外在这张图里，身为对数时间复杂度的矩阵法似乎没有任何优势，其耗时远远高于线性时间复杂度的递推法。这是因为 n 还不够大，体现不出 log(n) 的优势。在考虑更大的 n 之前，先来看看指数时间复杂度会增大到什么程度。&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="compare_b" src="https://blog.gocalf.com/images/2011/11/compare_b.png"/&gt;
&lt;p class="caption"&gt;三种算法的运行时间比较（对数坐标轴）&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id6"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id13"&gt;五、大整数情况下的复杂度&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Python 内置了大整数支持，因此上面的程序都可以直接接受任意大的 n。当整数在 32 位或 64 位以内时，加法和乘法都是常数时间，但大整数情况下，这个时间就不能忽略了。&lt;/p&gt;
&lt;p&gt;先来看一下 Fibonacci 数的二进制位数。我们知道 Fibonacci 数的通项公式是：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
F_n=\frac{1}{\sqrt5}\left(\frac{1+\sqrt5}{2}\right)^n-\frac{1}{\sqrt5}\left(\frac{1-\sqrt5}{2}\right)^n
\end{equation*}
&lt;/div&gt;
&lt;p&gt;当 n 充分大（其实都不需要很大）的时候，第二项就可以忽略不计了。把第一项对 2 取对数，就可以得到 Fibonacci 数的二进制位数的近似表达式，大概是 &lt;span class="math"&gt;\(\log_2{1.618}\times n-0.5\log_2{5}=\log_2{1.618}\times n-1.161=O(n)\)&lt;/span&gt;。由此可以算出，F(47) 是 32 位无符号整数可以表达的最大的 Fibonacci 数，F(93) 是 64 位无符号整数可以表达的最大的 Fibonacci 数。上面图中的 n 在 36 以内，不需要动用大整数运算，复杂度也比较符合之前的结论。但对于更大的 n，之前的复杂度就不再适用了。&lt;/p&gt;
&lt;p&gt;指数复杂度的算法就不管了，还不等用到大整数，它就已经慢到不行了。&lt;/p&gt;
&lt;p&gt;来看看 O(n) 时间复杂度的递推法。每次递推的时候都要计算两个 Fibonacci 数之和，第 i 次运算时，这两个 Fibonacci 数分别有 O(i) 个二进制位，完成加法需要 O(i) 的时间。因此总的时间大约是：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\sum_{i=1}^n{O(i)}=O(n^2)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;可见对于很大的 n，递推法的时间复杂度实际上是 &lt;strong&gt;O(n ^
2)&lt;/strong&gt; 的，空间复杂度是 &lt;strong&gt;O(n)&lt;/strong&gt; 用来存储 Fibonacci 数的各个二进制位。&lt;/p&gt;
&lt;p&gt;再看矩阵法，注意到矩阵运算中有乘法，两个长度为 n 的大整数相乘，传统算法是 O(n
^ 2) 时间复杂度，较好的 Karatsuba 算法是 O(n ^ (log 3 / log
2)) 时间，更快的快速傅立叶变换法是 O(n log n) 时间。Python
2.5 中使用的是 Karatsuba 算法（Python
3 里面似乎是快速傅立叶变换法）（参见 &lt;a class="reference external" href="http://www.endless-loops.com/2011/01/python%E6%BA%90%E7%A0%81%E4%B8%AD%E7%9A%84%E7%AE%97%E6%B3%95%E5%88%86%E6%9E%90-%E4%B9%8B-%E5%A4%A7%E6%95%B4%E6%95%B0%E4%B9%98%E6%B3%95-378.html"&gt;Python 源码中的算法分析 之 大整数乘法&lt;/a&gt;）。以 Karatsuba 算法为例，矩阵法的时间复杂度递推方程为：&lt;span class="math"&gt;\(T(n)=T(n/2)+O(n^{\log_2{3}})\)&lt;/span&gt;，应用 &lt;a class="reference external" href="https://blog.gocalf.com/algorithm-complexity-and-master-theorem"&gt;Master 定理&lt;/a&gt; 求得 &lt;span class="math"&gt;\(T(n)=O(n^{\log_2{3}})\)&lt;/span&gt;。因此对于很大的 n，矩阵法的时间复杂度为 &lt;strong&gt;O(n
^ 1.585)&lt;/strong&gt;，空间复杂度 &lt;strong&gt;O(n)&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;利用 Mathematica 对大 n 情况下这两种算法每千次运行时间进行拟合，分别得到：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;递推法大整数拟合结果：0.0131216 + 0.000102101 * n + 2.44765 * 10 ^
-7 * n ^ 2，RSquare = 0.999482。&lt;/li&gt;
&lt;li&gt;矩阵法大整数拟合结果：0.171487 + 9.74496 * 10 ^ -7 * n ^
1.51827，RSquare = 0.998395。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;看一下 n 在 4000 以内时，两种复杂度的对比情况：&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="compare_c" src="https://blog.gocalf.com/images/2011/11/compare_c.png"/&gt;
&lt;p class="caption"&gt;递推法（蓝色）与矩阵法（绿色）运行时间比较（大整数）&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;从图中可以看出，递推法的增长速度也是很快的，当 n 增大到 60 多的时候，它的运行时间就超过矩阵法了。矩阵法的增长速度非常慢，看起来像是线性的，让我们把 n 调的更大来看一下。&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="compare_d" src="https://blog.gocalf.com/images/2011/11/compare_d.png"/&gt;
&lt;p class="caption"&gt;矩阵法的运行时间（更大的 n）&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="id7"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id14"&gt;六、更快的算法？&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;试了试 Mathematica 中的 &lt;tt class="docutils literal"&gt;Fibonacci&lt;/tt&gt; 函数，发现其运算速度相当惊人，估计时间复杂度在 O(n
log
n) 上下，而且对于相同的 n，运算速度远远高于我的矩阵法。可惜我还不了解它的算法，只是在帮助文档里看到：&lt;/p&gt;
&lt;blockquote&gt;
Fibonacci[n] uses an iterative method based on the binary digit
sequence of n.&lt;/blockquote&gt;
&lt;p&gt;来看看它到底有多快：&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="compare_e" src="https://blog.gocalf.com/images/2011/11/compare_e.png"/&gt;
&lt;p class="caption"&gt;矩阵法（绿色）与 Mathematica Fibonacci 函数（橙色）运行时间比较&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;好吧，这个问题留待以后慢慢研究。&lt;/p&gt;
&lt;p&gt;最后相关的 Mathematica 命令文件放在这里：&lt;a class="reference external" href="https://blog.gocalf.com/assets/2011/11/fibonacci_timecost.zip"&gt;fibonacci_timecost&lt;/a&gt;。&lt;/p&gt;
&lt;/div&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Algorithm Complexity"></category><category term="Fibonacci"></category></entry><entry><title>算法的复杂度与 Master 定理</title><link href="https://blog.gocalf.com/algorithm-complexity-and-master-theorem" rel="alternate"></link><published>2011-11-16T14:16:00+08:00</published><updated>2011-11-16T14:16:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-11-16:/algorithm-complexity-and-master-theorem</id><summary type="html">&lt;p class="first last"&gt;平时设计或者阅读一个算法的时候，必然会提到算法的复杂度（包括时间复杂度和空间复杂度）。比如我们说一个二分查找算法的平均时间复杂度为 O(log n)，快速排序可能是 O(n log n)。那这里的 O 是什么意思？这样的表达是否准确呢？今天来复习一下与算法复杂度相关的知识：函数渐进阶；记号 O、Ω、θ和 o；Master 定理。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;平时设计或者阅读一个算法的时候，必然会提到算法的复杂度（包括时间复杂度和空间复杂度）。比如我们说一个二分查找算法的平均时间复杂度为 O(log
n)，快速排序可能是 O(n log
n)。那这里的 O 是什么意思？这样的表达是否准确呢？&lt;/p&gt;
&lt;p&gt;今天来复习一下与算法复杂度相关的知识：函数渐进阶，记号 O、Ω、θ和 o；Master 定理。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;先插一句，在算法复杂度分析中，log 通常表示以 2 为底的对数。&lt;/p&gt;
&lt;p&gt;算法复杂度（算法复杂性）是用来衡量算法运行所需要的计算机资源（时间、空间）的量。通常我们利用渐进性态来描述算法的复杂度。&lt;/p&gt;
&lt;p&gt;用 n 表示问题的规模，T(n) 表示某个给定算法的复杂度。所谓渐进性态就是令 n→∞ 时，T(n) 中增长最快的那部分。严格的定义是：如果存在 &lt;span class="math"&gt;\(\widetilde{T}(n)\)&lt;/span&gt;，当 n→∞ 时，有&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\frac{T(n)-\widetilde{T}(n)}{T(n)} \to 0
\end{equation*}
&lt;/div&gt;
&lt;p&gt;就说 &lt;span class="math"&gt;\(\widetilde{T}(n)\)&lt;/span&gt; 是 T(n) 当 n→∞ 时的渐进性态。&lt;/p&gt;
&lt;p&gt;比如 T(n) = 2 * n ^ 2 + n log n + 3，那么显然它的渐进性态是 2 * n ^
2，因为当 n→∞ 时，后两项的增长速度要慢的多，可以忽略掉。引入渐进性态是为了简化算法复杂度的表达式，只考虑其中的主要因素。当比较两个算法复杂度的时候，如果他们的渐进复杂度的阶不相同，那只需要比较彼此的阶（忽略常数系数）就可以了。&lt;/p&gt;
&lt;p&gt;总之，分析算法复杂度的时候，并不用严格演算出一个具体的公式，而是只需要分析当问题规模充分大的时候，复杂度在渐进意义下的阶。记号 O、Ω、θ和 o 可以帮助我们了解函数渐进阶的大小。&lt;/p&gt;
&lt;p&gt;假设有两个函数 f(n) 和 g(n)，都是定义在正整数集上的正函数。上述四个记号的含义分别是：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;f(n) = O(g(n))：&lt;span class="math"&gt;\(\exists c&amp;gt;0,n_0\in\mathbb{N},\forall n\geq n_0,f(n)\leq c g(n)\)&lt;/span&gt;；f 的阶&lt;strong&gt;不高于&lt;/strong&gt; g 的阶。&lt;/li&gt;
&lt;li&gt;f(n) =&amp;nbsp;Ω(g(n))：&lt;span class="math"&gt;\(\exists c&amp;gt;0,n_0\in\mathbb{N},\forall n\geq n_0,f(n)\geq c g(n)\)&lt;/span&gt;；f 的阶&lt;strong&gt;不低于&lt;/strong&gt; g 的阶。&lt;/li&gt;
&lt;li&gt;f(n) =&amp;nbsp;θ(g(n))：&lt;span class="math"&gt;\(\iff f(n)=O(g(n))\&amp;amp;\&amp;amp;f(n)=\Omega(g(n))\)&lt;/span&gt;；f 的阶&lt;strong&gt;等于&lt;/strong&gt; g 的阶。&lt;/li&gt;
&lt;li&gt;f(n) =&amp;nbsp;o(g(n))：&lt;span class="math"&gt;\(\forall\varepsilon &amp;gt; 0,\exists n_0\in \mathbb{N},\forall n\geq n_0,f(n)/g(n) &amp;lt; \varepsilon\)&lt;/span&gt;；f 的阶&lt;strong&gt;低于&lt;/strong&gt; g 的阶。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;可见，记号 O 给出了函数 f(n) 在渐进意义下的上界（但不一定是最小的），相反，记号Ω给出的是下界（不一定是最大的）。如果上界与下界相同，表示 f(n) 和 g(n) 在渐进意义下是同阶的（θ），亦即复杂度一样。&lt;/p&gt;
&lt;p&gt;列举一些常见的函数之间的渐进阶的关系：&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\log n!=\Theta(n\log n)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\log n^2=\Theta(\log n)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\log n^2=O(\sqrt n)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(n=\Omega(\log^2n)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(\log^2n=\Omega(\log n)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(2^n=\Omega(n^2)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(2^n=O(3^n)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(n!=o(n^n)\)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span class="math"&gt;\(2^n=o(n!)\)&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;有些人可能会把这几个记号跟算法的最坏、最好、平均情况复杂度混淆，它们有区别，也有一定的联系。&lt;/p&gt;
&lt;p&gt;即使问题的规模相同，随着输入数据本身属性的不同，算法的处理时间也可能会不同。于是就有了最坏情况、最好情况和平均情况下算法复杂度的区别。它们从不同的角度反映了算法的效率，各有用处，也各有局限。&lt;/p&gt;
&lt;p&gt;有时候也可以利用最坏情况、最好情况下算法复杂度来粗略地估计算法的性能。比如某个算法在最坏情况下时间复杂度为θ(n
^ 2)，最好情况下为θ(n)，那这个算法的复杂度一定是 O(n&amp;nbsp;^
2)、Ω(n) 的。也就是说 n ^ 2 是该算法复杂度的上界，n 是其下界。&lt;/p&gt;
&lt;p&gt;接下来看看 Master 定理。&lt;/p&gt;
&lt;p&gt;有些算法在处理一个较大规模的问题时，往往会把问题拆分成几个子问题，对其中的一个或多个问题递归地处理，并在分治之前或之后进行一些预处理、汇总处理。这时候我们可以得到关于这个算法复杂度的一个递推方程，求解此方程便能得到算法的复杂度。其中很常见的一种递推方程就是这样的：&lt;/p&gt;
&lt;p&gt;设常数 a &amp;gt;= 1，b &amp;gt; 1，f(n) 为函数，T(n) 为非负整数，T(n) = a T(n / b) +
f(n)，则有：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;若 &lt;span class="math"&gt;\(f(n)=O(n^{\log_b a-\varepsilon}),\varepsilon &amp;gt; 0\)&lt;/span&gt;，那么 &lt;span class="math"&gt;\(T(n)=\Theta(n^{\log_b a})\)&lt;/span&gt;。&lt;/li&gt;
&lt;li&gt;若 &lt;span class="math"&gt;\(f(n)=\Theta(n^{\log_b a})\)&lt;/span&gt;，那么 &lt;span class="math"&gt;\(T(n)=\Theta(n^{\log_b a}\log n)\)&lt;/span&gt;。&lt;/li&gt;
&lt;li&gt;若 &lt;span class="math"&gt;\(f(n)=\Omega(n^{\log_b a+\varepsilon}),\varepsilon &amp;gt; 0\)&lt;/span&gt;，并且对于某个常数 c &amp;lt; 1 和充分大的 n 有 &lt;span class="math"&gt;\(a f(n/b)\leq c f(n)\)&lt;/span&gt;，那么 &lt;span class="math"&gt;\(T(n)=\Theta(f(n))\)&lt;/span&gt;。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;比如常见的二分查找算法，时间复杂度的递推方程为 T(n) = T(n / 2) +
θ(1)，显然有 &lt;span class="math"&gt;\(n^{\log_b a}=n^0=\Theta(1)\)&lt;/span&gt;，满足 Master 定理第二条，可以得到其时间复杂度为 T(n)
= θ(log n)。&lt;/p&gt;
&lt;p&gt;再看一个例子，T(n) = 9 T(n / 3) + n，可知 &lt;span class="math"&gt;\(n^{\log_b a}=n^2\)&lt;/span&gt;，令ε取 1，显然满足 Master 定理第一条，可以得到 T(n) =&amp;nbsp;θ(n ^
2)。&lt;/p&gt;
&lt;p&gt;来一个稍微复杂一点儿例子，T(n) = 3 T(n / 4) + n log
n。&lt;span class="math"&gt;\(n^{\log_b a}=O(n^{0.793})\)&lt;/span&gt;，取ε = 0.2，显然当 c = 3 /
4 时，对于充分大的 n 可以满足 a * f(n / b) = 3 * (n / 4) * log(n / 4) &amp;lt;=
(3 / 4) * n * log n = c * f(n)，符合 Master 定理第三条，因此求得 T(n)
=&amp;nbsp;θ(n log n)。&lt;/p&gt;
&lt;p&gt;运用 Master 定理的时候，有一点一定要&lt;strong&gt;特别注意&lt;/strong&gt;，就是第一条和第三条中的ε必须&lt;strong&gt;大于零&lt;/strong&gt;。如果无法找到大于零的ε，就不能使用这两条规则。&lt;/p&gt;
&lt;p&gt;举个例子，T(n) = 2 T(n / 2) + n log n。可知 &lt;span class="math"&gt;\(n^{\log_b a}=n^1\)&lt;/span&gt;，而 f(n) = n log
n，显然不满足 Master 定理第二条。但对于第一条和第三条，也无法找到大于零的ε使得 &lt;span class="math"&gt;\(n \log n=O(n^{1-\varepsilon})\)&lt;/span&gt; 或者 &lt;span class="math"&gt;\(n \log n=\Omega(n^{1+\varepsilon})\)&lt;/span&gt;，因此不能用 Master 定理求解，只能寻求别的方式求解。比如可以利用递归树求出该算法的复杂度为 &lt;span class="math"&gt;\(T(n)=O(n \log^2{n})\)&lt;/span&gt;。简单的说一下计算过程：&lt;/p&gt;
&lt;p&gt;递归树的建立过程，就像是模拟算法的递推过程。树根对应的是输入的规模为 n 的问题，在递归处理子问题之外，还需要 n
log
n 的处理时间。然后根据递推公式给根节点添加子节点，每个子节点对应一个子问题。这里需要两个子节点，每个节点处理规模为 n
/ 2 的问题，分别需要 (n / 2) * log(n / 2) 的时间。因此在第二层一共需要 n *
(log n -
1) 的时间。第三层节点就是将第二层的两个节点继续分裂开，得到四个各需要 (n /
4) * log(n / 4) 时间的节点，总的时间消耗为 n * (log n -
2)。依此类推，第 k（设树根为 k = 0）层有 2 ^ k 的节点，总的时间为 n * (log n
- k)。而且可以知道，这棵树总共有 log
n 层（最后一层每个节点只处理规模为 1 的子问题，无须再分治）。最后将每一层消耗的时间累加起来，得到：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\sum_{k=0}^{\log n}{n(\log n-k)}=\frac{1}{2}n\log n(\log n + 1)=O(n\log^2{n})
\end{equation*}
&lt;/div&gt;
</content><category term="算法"></category><category term="Algorithm Complexity"></category><category term="Master Theorem"></category></entry><entry><title>求内积最大的子数组</title><link href="https://blog.gocalf.com/max-inner-product" rel="alternate"></link><published>2011-10-29T23:09:00+08:00</published><updated>2011-10-30T15:05:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-10-29:/max-inner-product</id><summary type="html">&lt;p class="first last"&gt;问题描述：有两个长度均为 n 的整数数组 A 和 B，现在要从这两个数组中各抽出 s 个数字，分别构成两个新的数组 C 和 D，要求数组 C 和 D 的内积最大。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;之前在网上看到有好多人在讨论这道题，据说是一道 Google 的面试题。&lt;/p&gt;
&lt;p&gt;问题描述：有两个长度均为 n 的整数数组 A 和 B，现在要从这两个数组中各抽出 s 个数字，分别构成两个新的数组 C 和 D，要求数组 C 和 D 的内积最大。&lt;/p&gt;
&lt;p&gt;用数学语言描述一下题目，就是已知：&lt;span class="math"&gt;\(A=\left[a_1,a_2,\cdots,a_n\right]\)&lt;/span&gt;，&lt;span class="math"&gt;\(B=\left[b_1,b_2,\cdots,b_n\right]\)&lt;/span&gt;；&lt;/p&gt;
&lt;p&gt;求：&lt;span class="math"&gt;\(C=\left[c_1,c_2,\cdots,c_s\right]\)&lt;/span&gt;，&lt;span class="math"&gt;\(D=\left[d_1,d_2,\cdots,d_s\right]\)&lt;/span&gt;，满足：&lt;span class="math"&gt;\(\forall c_i\in C,c_i\in A\)&lt;/span&gt;，&lt;span class="math"&gt;\(\forall d_i\in D,d_i\in B\)&lt;/span&gt;；&lt;/p&gt;
&lt;p&gt;要使得 C、D 的内积（&lt;span class="math"&gt;\(C\cdot D=c_1d_1+c_2d_2+\dots+c_s d_s\)&lt;/span&gt;）最大。&lt;/p&gt;
&lt;p&gt;一、先考虑只有正数的情况：&lt;/p&gt;
&lt;p&gt;当 s = 1 时，题目就退化成，从 n 个正整数中选取一个，从另外 n 个正整数中选取一个，使得乘积最大。显然，两次选取的都应该是那些数中最大的。&lt;/p&gt;
&lt;p&gt;当 s &amp;gt; 1 时，我们分两步考虑，先考虑选取哪些数，再考虑这些数怎么配对。&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;相信很多人都可以轻松地得出这样的结论：从 A 中选取最大的 s 个数构成 C，从 B 中选取最大的 s 个数构成 D，才有可能使得 C、D 内积最大。因为如果用 A 中的某个较小的数替换 C 中的任何一个数字，都会导致对应的乘积变小，从而整个内积变小。对于 D 也是类似的。&lt;/li&gt;
&lt;li&gt;对于选定的 C 和 D，如何配对呢？显然，应该让 C 中最大的数与 D 中最大的数相乘，C 中第二大的数与 D 中第二大的数相乘，以此类推。这个命题的证明也是很简单的，考虑任意两对数字：&lt;span class="math"&gt;\(c_i\leq c_j\)&lt;/span&gt;，&lt;span class="math"&gt;\(d_k\leq d_l\)&lt;/span&gt;，显然有&lt;/li&gt;
&lt;/ol&gt;
&lt;div class="math"&gt;
\begin{equation*}
\label{eq}\tag{1}\begin{array}{cl}
&amp;amp; (c_i d_k+c_j d_l)-(c_i d_i+c_j d_k) \\
= &amp;amp; c_i(d_k-d_l)-c_j(d_k-d_l) \\
= &amp;amp; (c_i-c_j)(d_k-d_l) \\
\geq &amp;amp; 0
\end{array}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;因此，如果 A、B 全部都是正整数，那只需要分别排序后，从大到小选取 s 个数即可。&lt;/p&gt;
&lt;p&gt;二、接下来考虑只有负数的情况：&lt;/p&gt;
&lt;p&gt;只有负数跟只有正数是类似的，因为两个负数相乘的结果与这两个负数的&lt;strong&gt;绝对值&lt;/strong&gt;相乘是一样的。根据上面的分析，我们只要对 A、B 分别排序后，从小到大（即&lt;strong&gt;绝对值&lt;/strong&gt;从大到小）选取 s 个数即可。&lt;/p&gt;
&lt;p&gt;三、再考虑两个数组一个全是正数，另一个全是负数的情况：&lt;/p&gt;
&lt;p&gt;不妨设 A 中全是正数，B 中全是负数。&lt;/p&gt;
&lt;p&gt;当 s = 1 时，题目就退化成，从 n 个正整数中选取一个，从另外 n 个负整数中选取一个，使得乘积最大。显然，两次选取的都应该是那些数中&lt;strong&gt;绝对值&lt;/strong&gt;最小的（即最小的正数和最大的负数）。&lt;/p&gt;
&lt;p&gt;当 s &amp;gt; 1 时，还是分两步考虑。&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;很容易证明，应该从两个数组中分别选取&lt;strong&gt;绝对值&lt;/strong&gt;最小的 s 个数（即正数数组中最小的 s 个数，负数数组中最大的 s 个数）。因为如果剩余的任何数字替换进来，都会导致对应的乘积的绝对值变大，乘积本身变小，从而整个内积变小。值得注意的是，很多人在这里容易出错，他们没有考虑到乘积为负数时，绝对值越大，乘积本身越小。&lt;/li&gt;
&lt;li&gt;对于选定的 C 和 D，如何配对呢？根据上面的式子 &lt;span class="math"&gt;\(\ref{eq}\)&lt;/span&gt; 可以知道，我们还是要让最大的那对数相乘，第二大的那对数相乘，……。这里需要注意，也是很多人容易出错的地方，最大的那对数是正数中的最大值（绝对值也最大）和负数中的最大值（绝对值最小）。与全是正数时不同的一点是，两个数组都是正数时，最大的那对数的乘积恰好也是最大的；但一正一负的时候，最大的那对数的乘积并不一定是最大，最小的一对数的乘积也不一定是最小，但他们累加起来一定是最大的。比如 [1, 2] 和[-1, -2]，正确的配对应该是 2 * -1 + 1 * -2 = -4，而不是 1 * -1 + 2 * -2 = -5。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;四、几种特殊情况都考虑完了，最后就是正负数任意混合的一般情况。根据上面的分析，我们终归是要对 A 和 B 分别排序的，排序之后将两个数组的下标对齐，可以将两个数组分成三个部分，第一个部分中两个的数组元素都是负数（负数部分），第二个部分中一个数组元素都是负数而另一个都是正数（异号部分），第三个部分中两个数组的元素都是正数（正数部分），如下所示：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{matrix}
A:&amp;amp;[&amp;amp;-&amp;amp;|&amp;amp;+&amp;amp;|&amp;amp;+&amp;amp;]\\
B:&amp;amp;[&amp;amp;-&amp;amp;|&amp;amp;-&amp;amp;|&amp;amp;+&amp;amp;]
\end{matrix}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;由于负数部分和正数部分都产生正的乘积，我们需要同时考虑这两个部分。每次从这两个部分各选出&lt;strong&gt;绝对值&lt;/strong&gt;最大的一对数，将乘积更大的那对从 A、B 中转移到 C、D 中，然后继续比较。&lt;/p&gt;
&lt;p&gt;如果负数部分和正数部分都取完了，还缺 m 对数，那就从异号部分选取最小的 m 个正数，和最大的 m 个负数，对应配对即可。&lt;/p&gt;
&lt;p&gt;算法示意：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;MinInnerProduct&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="ne"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;Invalid arguments.&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;([],&lt;/span&gt; &lt;span class="p"&gt;[],&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;val1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;val2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;val2&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val1&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;val2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;val1&lt;/span&gt;
      &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
      &lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;val2&lt;/span&gt;
      &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nb"&gt;sum&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;A&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;B&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;D&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;算法的空间复杂度为 O(s)，即用来存储 C、D 的空间；时间复杂度为 O(n log
n)。&lt;/p&gt;
&lt;p&gt;============ 并不华丽的分割线&amp;nbsp;============&lt;/p&gt;
&lt;p&gt;最后说个题外的事情。这是最后一篇从以前“钟磬居”网站备份回来的算法文章了。当年的钟磬居有如昙花一现，好多文章都只存在于 Google
Reader 的缓存中了。让我没想到的是，刚才搜一个东西的时候，搜索结果第一条竟然是这篇文章。当然不是你看到的这一篇，而是之前发在钟磬居中被转载出去的。一字不差啊，连我加的粗体都还在，也保留了我当时文章中的一个错误（这里已经修正）。当时的钟磬居跟现在的 GoCalf 一样，看的人不算太少，但没有人评论。想起中学时喜欢的一句话“纵是昙花一现，也有一个月下赏花人，应无所憾”。送给逝去的钟磬居，鼓励一下自己。继续努力。&lt;/p&gt;
&lt;p&gt;再次强调，本文不是转载，是原文，是从已经关闭了的网站中恢复回来的原文。GoCalf 网站中，如无特殊说明，一律原创。&lt;/p&gt;
</content><category term="算法"></category><category term="Interview Question"></category></entry><entry><title>求二叉树中两结点的最小公共祖先</title><link href="https://blog.gocalf.com/least-common-ancestor" rel="alternate"></link><published>2011-10-21T22:34:00+08:00</published><updated>2012-04-05T23:31:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-10-21:/least-common-ancestor</id><summary type="html">&lt;p class="first last"&gt;据说这是微软的一道面试题，谁知道呢。问题描述：找出二叉树上任意两个指定结点的最近共同父结点（LCA，Least Common Ancestor）。&lt;/p&gt;
</summary><content type="html">
&lt;p&gt;据说这是微软的一道面试题，谁知道呢。&lt;/p&gt;
&lt;p&gt;问题描述：找出二叉树上任意两个指定结点的最近共同父结点（LCA，Least
Common Ancestor）。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;这算不上是一道算法题了，主要还是看数据结构基本知识和编程能力。&lt;/p&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id7"&gt;有父指针，方法一&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;首先考虑最简单的情况——二叉树结点数据结构中有父指针。&lt;/p&gt;
&lt;p&gt;这是不是非常简单呢？只要分别从两个结点出发向上走到树根，得到两个结点的分支路径，求出这两条路径相互重合部分的最靠下的结点，就是所求的 LCA。这只需要 O(h) 的时空代价（设 h 是树高，n 是树结点数目，平均情况下 h =
log(n)，最坏情况 h = n）。&lt;/p&gt;
&lt;p&gt;如果想再稍微节省一点儿时间和空间，可以先找出第一条分支路径，并用这些结点建立哈希表，然后从另外一个指定结点开始向上走到树根，每次遇到一个结点就到哈希表中查一下，一旦发现某个结点存在于哈希表中，这个结点就是所求的 LCA。这个方法的代码示意如下：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;FindLCA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Special cases.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;

  &lt;span class="c1"&gt;# Get the first branch path.&lt;/span&gt;
  &lt;span class="n"&gt;ancestors1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ancestors1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

  &lt;span class="c1"&gt;# Check if any ancestor of node2 is in the first branch path.&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ancestors1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;    &lt;span class="c1"&gt;# Got it, the LCA.&lt;/span&gt;
    &lt;span class="n"&gt;node2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

  &lt;span class="c1"&gt;# These two nodes have no common ancestor.&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;时间和空间复杂度都是 O(h)。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id8"&gt;有父指针，方法二（2012-04-02 22:10 添加）&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上面的方法需要至少一个跟深度相当的缓存，在空间上还是有一些浪费的。可以使用更节省空间的方法，就是先计算出两个结点各自的深度，如果深度不同，则将较靠下的一个结点拉上去，直到两个结点在同一深度处。然后同步向根结点前进，首次相遇时则为最小公共祖先。示意代码（python 2.7）如下：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;FindLCA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Special cases.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;

  &lt;span class="c1"&gt;# Gets each node's depth.&lt;/span&gt;
  &lt;span class="n"&gt;depth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;lambda&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;depth1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;depth2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;depth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;# Pulls up the lower node and makes the two nodes in the same depth.&lt;/span&gt;
  &lt;span class="n"&gt;mindepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;depth2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mindepth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;depth2&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;mindepth&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

  &lt;span class="c1"&gt;# Finds the common ancestor.&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;
    &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;
    &lt;span class="n"&gt;node2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parent&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;这样时间复杂度是 O(h)，空间复杂度是 O(1)。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id4"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id9"&gt;没有父指针，方法一&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;通常二叉树结点中并没有父结点指针，这时候就要遍历二叉树找到这两个结点，并找出它们的 LCA。&lt;/p&gt;
&lt;p&gt;在遍历二叉树的时候，很容易就能够记录下根结点到任何结点的分支路径，只要有了分支路径，就可以对比找出 LCA。&lt;/p&gt;
&lt;p&gt;我们采取前序遍历，即 N-L-R 的顺序，使用堆栈来避免递归并且记录完整的分支路径。那么，在二叉树中查找指定结点的算法可以这样写：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Dir&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Undef&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;FindNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nodeSet&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;nodeSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

  &lt;span class="n"&gt;pathDict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
  &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# Go down along left branch&lt;/span&gt;
      &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Left&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodeSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;pathDict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;nodeSet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;nodeSet&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;findAll&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pathDict&lt;/span&gt;
      &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;   &lt;span class="c1"&gt;# Back from right branch&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pathDict&lt;/span&gt;
      &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;dir&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dir&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Right&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;  &lt;span class="c1"&gt;# Trun to right from left&lt;/span&gt;
    &lt;span class="n"&gt;curr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;curr&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pathDict&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;其中 &lt;tt class="docutils literal"&gt;Dir&lt;/tt&gt; 这个类相当于是一个枚举，用来定义当前的分支方向。&lt;tt class="docutils literal"&gt;FindNodes&lt;/tt&gt; 除了需要二叉树根结点外，还需要一个待查找的结点集合。这个函数可以在二叉树中找到所有（或第一个）待查找结点的分支路径，并返回一个字典（结点
--&amp;gt; 路径）。&lt;/p&gt;
&lt;p&gt;可以看出，&lt;tt class="docutils literal"&gt;FindNodes&lt;/tt&gt; 函数按照前序顺序遍历整个二叉树，查找指定结点。每遇到一个结点，首先判断它是不是我们要找的，如果不是就沿着左边的分支下降到底，然后转入右侧分支。&lt;/p&gt;
&lt;p&gt;有了 &lt;tt class="docutils literal"&gt;FindNodes&lt;/tt&gt; 函数的支持，我们就可改写前面的 &lt;tt class="docutils literal"&gt;FindLCA&lt;/tt&gt; 函数，即先遍历二叉树求出两个结点的分支路径，然后比较这两条路径找出 LCA：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;FindLCA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# Special cases.&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;

  &lt;span class="c1"&gt;# Try to find the two nodes in the tree, and get their branch paths.&lt;/span&gt;
  &lt;span class="n"&gt;nodeSet&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
  &lt;span class="n"&gt;pathDict&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FindNodes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;nodeSet&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nodeSet&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;

  &lt;span class="n"&gt;path1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pathDict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
  &lt;span class="n"&gt;path2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pathDict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;

  &lt;span class="c1"&gt;# Compare the two paths, find out the LCA.&lt;/span&gt;
  &lt;span class="n"&gt;lca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;minLen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path2&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;minLen&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;path1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;path2&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;
    &lt;span class="n"&gt;lca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;path1&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;lca&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;遍历二叉树查找所有指定的结点需要 O(n) 时间，O(h) 额外空间；对比两条分支路径需要 O(h) 的时间，因此总的时间代价为 O(n)，空间代价为 O(h)。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id5"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id10"&gt;没有父结点，方法二（2012-04-05 23:31 更新）&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;上面的代码有点儿太啰嗦了，如果不想缓存整条分支路径，或者只是想让代码更简洁一些，也很容易做到，只需要在遍历查找的时候做点儿小小的改动。关于遍历二叉树可以参考后面的一篇文章：&lt;a class="reference external" href="https://blog.gocalf.com/traversing-binary-tree"&gt;程序基本功之遍历二叉树&lt;/a&gt;。这里我将在非递归的前序（N-L-R）遍历基础上修改得到求 LCA 的程序。&lt;/p&gt;
&lt;p&gt;为什么用前序遍历？&lt;/p&gt;
&lt;p&gt;首先考察一下 LCA 的特性，只有两种可能：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;LCA 就是其中的一个结点，而另一个结点是它的子孙；&lt;/li&gt;
&lt;li&gt;两个结点分别位于 LCA 的左子树和右子树中。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;对于第一种可能，前序遍历时首先找到的结点就是 LCA，剩下的事情就是确定第二个结点在它下面。中序和后序也都可以做，但没有这么美妙。&lt;/p&gt;
&lt;p&gt;对于第二种可能，假设在前序遍历过程中，首先找到了一个结点（比如下面的 H），根据非递归前序遍历的算法特性，这时候栈里一定是依次存储了结点 A（根节点）、B、D、G（请自行思考为什么没有 C、E、F），再结合 LCA 的特性，很容易发现，LCA 要么是 H 自身（对应于上面第一种情况），要么就只能是 A、B、D 或 G。剩下的事情就太美妙，继续遍历二叉树，直到找到另外一个结点。这时候看看 A、B、D、G 和 H 中还有谁在栈里，最靠下的那个就是 LCA。怎么判定谁在栈里？怎么判定最靠下？用辅助变量呗。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;    A
   /
  B
 /
C
 \
  D
 /
E
 \
  F
   \
    G
   /
  H
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;示意程序代码：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;FindLCA&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;nodeset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;node1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;   &lt;span class="c1"&gt;# Also supports 3 or more nodes.&lt;/span&gt;
  &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;          &lt;span class="c1"&gt;# A stack to help performing N-L-R traversing.&lt;/span&gt;
  &lt;span class="n"&gt;lca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;      &lt;span class="c1"&gt;# Records the most possible least common ancestor.&lt;/span&gt;
  &lt;span class="n"&gt;mindepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# The depth of lca.&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;nodeset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;nodeset&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mindepth&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="c1"&gt;# Yeah, found the first node. The lca must be itself or already in s.&lt;/span&gt;
          &lt;span class="n"&gt;lca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;
          &lt;span class="n"&gt;mindepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;nodeset&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;
      &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;mindepth&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;lca&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;
        &lt;span class="n"&gt;mindepth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="n"&gt;root&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;root&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nodeset&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="n"&gt;lca&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;可以跟 &lt;a class="reference external" href="https://blog.gocalf.com/traversing-binary-tree"&gt;程序基本功之遍历二叉树&lt;/a&gt; 中的 &lt;strong&gt;非递归前序遍历&lt;/strong&gt; 的程序对比一下，会发现改动之处是非常小的。&lt;/p&gt;
&lt;p&gt;这段程序时间复杂度都是 O(n)，空间复杂度是 O(h)，这些都是遍历二叉树所需的时间和空间消耗。在遍历之外，就只剩下常数量的时空开销了。&lt;/p&gt;
&lt;/div&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Data Structure"></category><category term="Binary Tree"></category></entry><entry><title>任务调度问题：资源占用与释放</title><link href="https://blog.gocalf.com/task-schedule-with-resource" rel="alternate"></link><published>2011-10-15T00:10:00+08:00</published><updated>2011-10-21T18:50:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-10-15:/task-schedule-with-resource</id><summary type="html">&lt;p class="first last"&gt;问题描述：有 n 个任务，第 i 个任务运行时需要使用 R[i] 的资源，运行完毕后需要占用 O[i] 的资源（O[i] &amp;lt;= R[i]），假设现在我们总共有 s 的资源，要求设计一个调度算法，能保证所有任务能顺利执行；如果无法执行完，需要说明理由。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;据说这是 2009 年 Google 暑期实习招聘的笔试题。&lt;/p&gt;
&lt;p&gt;问题描述：有 n 个任务，第 i 个任务运行时需要使用 R[i] 的资源，运行完毕后需要占用 O[i] 的资源（O[i] &amp;lt;=
R[i]），假设现在我们总共有 s 的资源，要求设计一个调度算法，能保证所有任务能顺利执行；如果无法执行完，需要说明理由。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;举例：比如有 n = 2 个任务，R[1] = 10，O[1] = 2，R[2] = 5，O[2] =
3，总资源 s = 10。如果先执行任务 1，剩余资源 10 - 2 =
8，可以执行任务 2；反过来先执行任务 2，剩余资源 10 - 3 = 7，7 &amp;lt; r[1] =
10，无法执行任务 1。&lt;/p&gt;
&lt;p&gt;这道题的解法很简单，按照 R - O 从大到小排序就是可行的调度顺序，如果按照这个顺序无法执行完所有任务，那么其他任何顺序也都不行；反之，如果存在某个顺序使得所有任务能顺利执行，那么这个顺序一定也可以。&lt;/p&gt;
&lt;p&gt;先看算法，稍后再做证明。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;ScheduleTasks&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="c1"&gt;# R: request&lt;/span&gt;
  &lt;span class="c1"&gt;# O: occupancy&lt;/span&gt;
  &lt;span class="c1"&gt;# R - O: temporal = request - occupancy&lt;/span&gt;
  &lt;span class="n"&gt;tasks&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;R&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;O&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
  &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sort&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;itemgetter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;reverse&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;taskId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;occ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;temp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;occ&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;False&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;-=&lt;/span&gt; &lt;span class="n"&gt;occ&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;显然这是贪心法，那么贪心法是否一定能得到可行解呢？&lt;/p&gt;
&lt;p&gt;假设存在一个可行解（任务的执行顺序）为：T1, T2, …,
Tn。设其中存在两个相邻的任务 T&lt;sub&gt;i&lt;/sub&gt; 和 T&lt;sub&gt;i+1&lt;/sub&gt;，满足 R[i] - O[i] &amp;lt; R[i+1] -
O[i+1]。&lt;/p&gt;
&lt;p&gt;设执行任务 j 前的资源剩余量为 s'，因为这是一个可行解，任务 j 和任务 k 都可以顺利执行，因此有：s'
- O[i] &amp;gt;= R[i+1]。&lt;/p&gt;
&lt;p&gt;联列这两个式子可以得到：s' &amp;gt;= R[i+1] + O[i] &amp;gt; R[i] + O[i+1]，即 s' -
O[i+1] &amp;gt; R[i]，可见交换任务 T&lt;sub&gt;i&lt;/sub&gt; 和 T&lt;sub&gt;i+1&lt;/sub&gt; 的执行顺序后依旧是可行解。&lt;/p&gt;
&lt;p&gt;以此类推，对于任何 i（1 &amp;lt;= i &amp;lt; n），如果 R[i] - O[i] &amp;lt; R[i+1] -
O[i+1]，我们都可以将这两个任务的顺序交换，最终得到的执行顺序是可行解。&lt;/p&gt;
&lt;p&gt;综上所述，按照 R - O 从大到小排列所有的任务是可行解。&lt;/p&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Task Schedule"></category><category term="Greedy Algorithm"></category></entry><entry><title>检测单向链表是否存在环</title><link href="https://blog.gocalf.com/circle-of-link-list" rel="alternate"></link><published>2011-10-14T12:56:00+08:00</published><updated>2011-10-14T12:56:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-10-14:/circle-of-link-list</id><summary type="html">&lt;p class="first last"&gt;问题描述：在单向链表中，每个结点都包含一个指向下一个结点的指针，最后一个结点的这个指针被设置为空。但如果把最后一个结点的指针指向链表中存在的某个结点，就会形成一个环，在顺序遍历链表的时候，程序就会陷入死循环。我们的问题就是，如何检测一个链表中是否有环，如果检测到环，如何确定环的入口点（即求出环长，环前面的链长）。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;问题描述：在单向链表中，每个结点都包含一个指向下一个结点的指针，最后一个结点的这个指针被设置为空。但如果把最后一个结点的指针指向链表中存在的某个结点，就会形成一个环，在顺序遍历链表的时候，程序就会陷入死循环。我们的问题就是，如何检测一个链表中是否有环，如果检测到环，如何确定环的入口点（即求出环长，环前面的链长）。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;一种比较耗空间的做法是，从头开始遍历链表，把每次访问到的结点（或其地址）存入一个集合（hashset）或字典（dictionary），如果发现某个结点已经被访问过了，就表示这个链表存在环，并且这个结点就是环的入口点。这需要 O(N) 空间和 O(N) 时间，其中 N 是链表中结点的数目。&lt;/p&gt;
&lt;p&gt;如果要求只是用 O(1) 空间、O(N) 时间，应该怎么处理呢？&lt;/p&gt;
&lt;p&gt;其实很简单，想象一下在跑道上跑步：两个速度不同的人在操场跑道上一圈一圈地跑，他们总会有相遇的时候。因此我们只需要准备两个指针，同时从链表头出发，一个每次往前走一步，另一个每次往前走两步。如果链表没有环，那么经过一段时间，第二个（速度较快的）指针就会到达终点；但如果链表中有环，两个指针就会在环里转圈，并会在某个时刻相遇。&lt;/p&gt;
&lt;p&gt;大家也许会问，这两个指针要在环里转多少圈才能相遇呢？会不会转几千几万圈都无法相遇？实际上，第一个（速度慢的）指针在环里转满一圈之前，两个指针必然相遇。不妨设环长为 L，第一个指针 P1 第一次进入环时，第二个指针 P2 在 P1 前方第 a 个结点处（0
&amp;lt; a &amp;lt; L），设经过 x 次移动后两个指针相遇，那么应该有 0 + x = a + 2x (mod
L)，显然 x = L - a。下面这张图可以清晰地表明这种关系，经过 x =
L - a 次移动，P1 向前移动了 L - a 个位置（相当于后退了 a），到达 P1' 处，而 P2 向前移动了 2L - 2a 个位置（相当于后退了 2a），到达 P2' 处，显然 P1' 和 P2' 是同一点。&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;object data="https://blog.gocalf.com/images/2011/10/two_pointers_in_ring.svg" style="width: 428px;" type="image/svg+xml"&gt;
two_pointers_in_ring&lt;/object&gt;
&lt;p class="caption"&gt;慢指针（P1）转一周之内，必然与快指针（P2）相遇&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;在知道链表内有环后，求环长是一件非常简单的事情，只要从刚才那个相遇点开始，固定 P2，继续移动 P1，直到 P1 与 P2 再次相遇，所经过的步数就是环长。&lt;/p&gt;
&lt;p&gt;怎么求环前面那段子链的长度呢？很简单，让 P1 和 P2 都回到链表起点，然后让 P2 先往前走 L 次（每次往前走一步），然后 P1 和 P2 再同时往前走，当它们再次相遇时，P1 所走的步数就是环前面的子链长度。&lt;/p&gt;
&lt;p&gt;算法示意：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;CheckRing&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;l1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# length of the chain before the ring&lt;/span&gt;
  &lt;span class="n"&gt;l2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;  &lt;span class="c1"&gt;# length of the ring&lt;/span&gt;

  &lt;span class="c1"&gt;# Check if there is a ring.&lt;/span&gt;
  &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
  &lt;span class="n"&gt;pos2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
    &lt;span class="n"&gt;pos2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
    &lt;span class="n"&gt;l1&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;l2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
      &lt;span class="k"&gt;break&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;l2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;l1&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# l2 should be 0&lt;/span&gt;

  &lt;span class="c1"&gt;# Calc the length of the ring.&lt;/span&gt;
  &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
    &lt;span class="n"&gt;l2&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;

  &lt;span class="c1"&gt;# Calc the length of the chain before the ring.&lt;/span&gt;
  &lt;span class="n"&gt;l1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
  &lt;span class="n"&gt;pos2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;pos2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;pos1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos1&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
    &lt;span class="n"&gt;pos2&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pos2&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;
    &lt;span class="n"&gt;l1&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</content><category term="算法"></category><category term="Interview Question"></category><category term="Data Structure"></category><category term="Linked List"></category></entry><entry><title>利用不均匀硬币产生等概率</title><link href="https://blog.gocalf.com/unbalanced-coin" rel="alternate"></link><published>2011-10-08T22:49:00+08:00</published><updated>2011-10-09T15:05:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-10-08:/unbalanced-coin</id><summary type="html">&lt;p class="first last"&gt;问题描述：有一枚不均匀的硬币，已知抛出此硬币后，正面向上的概率为 p（0 &amp;lt; p &amp;lt; 1）。请利用这枚硬币产生出概率相等的两个事件。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;问题描述：有一枚不均匀的硬币，已知抛出此硬币后，正面向上的概率为 p（0
&amp;lt; p &amp;lt; 1）。请利用这枚硬币产生出概率相等的两个事件。&lt;/p&gt;
&lt;p&gt;这个问题跟之前的 &lt;a class="reference external" href="https://blog.gocalf.com/build-rand3-from-rand5"&gt;利用等概率 Rand5 产生等概率 Rand3&lt;/a&gt; 非常像，但却简单的多。几个月前还为这个事情头疼了一下，现在想来真是不应该。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;某一次抛出硬币，正面向上的概率是 p，反面向上的概率是 1 -
p，当 p 不等于 0.5 时，这两个事件的概率就不一样了。怎么能凑出等概率呢？还是要利用概率的加法和乘法法则。这里用乘法，也就是连续的独立事件。&lt;/p&gt;
&lt;p&gt;连续抛两次硬币，正反面的出现有四种情况，概率依次为：&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;两次均为正面：p * p&lt;/li&gt;
&lt;li&gt;第一次正面，第二次反面：p * (1 - p)&lt;/li&gt;
&lt;li&gt;第一次反面，第二次正面：(1 - p) * p&lt;/li&gt;
&lt;li&gt;两次均为反面：(1 - p) * (1 - p)&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;这不，中间两种情况的概率是完全一样的。于是问题的解法就是连续抛两次硬币，如果两次得到的相同则重新抛两次；否则根据第一次（或第二次）的正面反面情况，就可以得到两个概率相等的事件。&lt;/p&gt;
&lt;p&gt;用 Python 程序模拟一下这个过程，首先是一个叫做 &lt;tt class="docutils literal"&gt;UnbalancedCoin&lt;/tt&gt; 的类，用来模拟这枚不均匀的硬币。Flip 方法表示抛一次硬币，返回值 True 代表正面，False 代表反面。根据要求，这个函数返回 True 和 False 的概率分别是 p 和 1
-
p。函数 &lt;tt class="docutils literal"&gt;MakeEqualProb&lt;/tt&gt; 利用参数 &lt;tt class="docutils literal"&gt;coin&lt;/tt&gt;（这枚不均匀硬币）构造出两个事件（依旧用 True 和 False 表示），并且这两个事件的概率都是 0.5。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;UnbalancedCoin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mf"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;invalid p&amp;#39;&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Flip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_p&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;MakeEqualProb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;coin&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Flip&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;对于不同的 p 值，模拟实验十万次，得到如下的（结果为 True 的）概率分布，其中蓝线是不均匀硬币抛出后正面向上的概率，红线是构造出来的两个事件之一（第一次正面向上，第二次反面向上）的概率。&lt;/p&gt;
&lt;div id="coin-prob-chart" class="highcharts" style="height: 400px; width: 600px"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
$(function () {
    $('#coin-prob-chart').highcharts({
        chart: {type: 'line', backgroundColor: null},
        colors: ['#3399ff', '#ff3300'],
        title: {text: null},
        xAxis: {min: 0, max: 1, tickInterval: 0.1},
        yAxis: {min: 0, max: 1, tickInterval: 0.1, title: { text: null} },
        series: [{
            name: 'UnbalancedCoin',
            data: [
                [0.1, 0.09989], [0.2, 0.20059], [0.3, 0.30313], [0.4, 0.40019], [0.5, 0.50013],
                [0.6, 0.59973], [0.7, 0.70198], [0.8, 0.80152], [0.9, 0.90012]
            ]
        }, {
            name: 'MakeEqualProb',
            data: [
                [0.1, 0.49899], [0.2, 0.49846], [0.3, 0.50038], [0.4, 0.49917], [0.5, 0.50181],
                [0.6, 0.50022], [0.7, 0.49805], [0.8, 0.49997], [0.9, 0.49956]
            ]
        }]
    });
});
&lt;/script&gt;&lt;p&gt;如果问题改变一下，把“一枚”改成“一种”，那解决办法就更简单有趣了，把两枚同样的不均匀硬币正面相对粘在一起，就可以得到一个均匀的组合币，抛出这枚组合币就可以得到等概率的两个事件。&lt;/p&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Probability"></category></entry><entry><title>单次遍历，带权随机选取问题（二）</title><link href="https://blog.gocalf.com/weighted-random-selection-2" rel="alternate"></link><published>2011-09-26T11:27:00+08:00</published><updated>2011-09-28T10:05:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-09-26:/weighted-random-selection-2</id><summary type="html">&lt;p class="first last"&gt;本文介绍一个有趣的算法，用来解决带权随机选取问题：有一组数量未知的数据，每个元素有非负权重。要求只遍历一次，随机选取其中的一个元素，任何一个元素被选到的概率与其权重成正比。&lt;/p&gt;
</summary><content type="html">
&lt;p&gt;还是同样的问题：有一组数量未知的数据，每个元素有非负权重。要求只遍历一次，随机选取其中的一个元素，任何一个元素被选到的概率与其权重成正比。&lt;/p&gt;
&lt;p&gt;在 &lt;a class="reference external" href="https://blog.gocalf.com/weighted-random-selection"&gt;前一篇&lt;/a&gt; 文章中介绍了概率分布的理论值，并用比较简洁高效的函数实现了选取一个元素的方法。现在来看一个神奇的算法，以及相关的证明和实现。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;算法很简单：对于任意的 i（1 &amp;lt;= i &amp;lt;=
n），按照如下方法给第 i 个元素分配一个键值 key（其中 r&lt;sub&gt;i&lt;/sub&gt; 是一个 0 到 1 之间等概率分布的随机数）：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
key(i)=r_i^{1/w_i}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;之后，如果要随机选取一个元素，就去 key 最大的那个；如果要选取 m 个元素，就取 key 最大的 m 个。&lt;/p&gt;
&lt;p&gt;真不知道是怎么想出来的这样的方法，不过还是先来关注一下证明的过程。&lt;/p&gt;
&lt;div class="section" id="m-1"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id5"&gt;m=1 证明&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;对于 m=1 的证明过程会介绍得详细些，主要是怕我自己过几天就忘记了。概率达人可以直接秒杀之。&lt;/p&gt;
&lt;p&gt;m=1 时，第 i 个元素被选取到的概率，就等于它所对应的键值 key(i) 是最大值的概率，即：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=p(\forall j\neq i,key(j) &amp;lt; key(i))
\end{equation*}
&lt;/div&gt;
&lt;p&gt;把 key(i) 的计算公式代入，但要注意公式中的 r&lt;sub&gt;i&lt;/sub&gt; 并不是一个固定的数值，而是随机变量。不考虑计算机数值表示的精度，可以假设 r&lt;sub&gt;i&lt;/sub&gt; 是一个在 0 到 1 之间的连续均匀概率分布，因此如果要计算 key(i) 是最大的概率，必须要对 r&lt;sub&gt;i&lt;/sub&gt; 所有的可能值进行概率累加，也就是积分。于是上面的概率表达式就被写成：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=\int_0^1p(\forall j\neq i,r_j^{1/w_j} &amp;lt; r_i^{1/w_i})\mathrm{d}r_i
\end{equation*}
&lt;/div&gt;
&lt;p&gt;再看式子中的 &lt;span class="math"&gt;\(\forall\)&lt;/span&gt;，它表示每一个 j 都要满足后面的条件，而各个 j 之间相互独立，因此可以写成概率乘积，于是得到：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=\int_0^1\prod_{j\neq i}{p(r_j^{1/w_j} &amp;lt; r_i^{1/w_i})}\mathrm{d}r_i
\end{equation*}
&lt;/div&gt;
&lt;p&gt;对于给定的 j，&lt;span class="math"&gt;\(r_j^{1/w_j} &amp;lt; r_i^{1/w_i}\Rightarrow r_j &amp;lt; r_i^{w_j/w_i}\)&lt;/span&gt;，另外 r&lt;sub&gt;j&lt;/sub&gt; 也是个均匀概率分布，将概率密度函数代入可以得到：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p(r_j &amp;lt; r_i^{w_j/w_i})=\int_0^{r_i^{w_j/w_i}}1\mathrm{d}r_j=r_i^{w_j/w_i}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;因此，上面的概率算式就变成（其中 w 就是前一篇文章中提到的所有元素的权重之和）：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=\int_0^1\prod_{j\neq i}{r_i^{w_j/w_i}}\mathrm{d}r_i=\int_0^1r_i^{(w-w_i)/w_i}\mathrm{d}r_i=\frac{w_i}{w}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;最后的结果跟 &lt;a class="reference external" href="https://blog.gocalf.com/weighted-random-selection"&gt;前一篇&lt;/a&gt; 文章中的理论值相等，证明完毕。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id2"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id6"&gt;m&amp;gt;=1 证明&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;当 m 取任意值时，概率公式变得非常复杂，在前一篇文章中使用了第 i 个元素不被选到的概率来简化表达式。现在的证明也从同样的角度进行。&lt;/p&gt;
&lt;p&gt;第 i 个元素不被选到的概率，显然等于这 n 个元素中，至少存在 m 个元素的键值大于 key(i)，与之前的讨论一样，不妨设这 m 个元素的下标（按键值从大到小）依次为 j&lt;sub&gt;1&lt;/sub&gt;,
j&lt;sub&gt;2&lt;/sub&gt;, ..., j&lt;sub&gt;m&lt;/sub&gt;，&lt;span class="math"&gt;\(\forall 1\leq k\leq m,j_k\notin\{i,j_1,j_2,\cdots,j_{k-1}\}\)&lt;/span&gt;，满足 &lt;span class="math"&gt;\(\forall 1\leq t_k\leq n,t_k\notin\{j_1,j_2,\cdots,j_{k}\},key(j_k) &amp;gt; key(t_k)\)&lt;/span&gt;。注意 j&lt;sub&gt;k&lt;/sub&gt; 和 t&lt;sub&gt;k&lt;/sub&gt; 的取值范围，为了简单起见，下面的式子中就不再重复了。&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\bar p_i(m)=p(\exists j_1,j_2,...,j_m\neq i,key(j_1) &amp;gt; key(j_2) &amp;gt; ... &amp;gt; key(j_m) &amp;gt; key(i))
\end{equation*}
&lt;/div&gt;
&lt;p&gt;为了能够进一步求解，必须把这个连等式拆开。这里要非常小心，各个 j&lt;sub&gt;k&lt;/sub&gt; 并不是相互独立的，比如当 j&lt;sub&gt;1&lt;/sub&gt; 改变的时候，j&lt;sub&gt;2&lt;/sub&gt; 的取值范围也会随之变化，依此类推。拆开之后的式子如下：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{array}{rrrl}
\bar p_i(m)=p( &amp;amp; \exists j_1( &amp;amp; &amp;amp; \forall t_1,key(j_1) &amp;gt; key(t_1),\\
&amp;amp; &amp;amp; \exists j_2( &amp;amp; \forall t_2,key(j_2) &amp;gt; key(t_2),\\
&amp;amp; &amp;amp; &amp;amp; ...,\\
&amp;amp; &amp;amp; &amp;amp; \exists j_m(\forall t_m,key(j_m) &amp;gt; key(t_m))\\
&amp;amp; &amp;amp; ) &amp;amp; \\
&amp;amp; ) &amp;amp; &amp;amp; \\
) &amp;amp; &amp;amp; &amp;amp; \end{array}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;看起来还是相当恐怖的，一层套一层。注意等式右边已经没有显式地关于 i 的信息了，这些信息被隐含在 j&lt;sub&gt;k&lt;/sub&gt; 和 t&lt;sub&gt;k&lt;/sub&gt; 的取值范围中，切记。对每个 j&lt;sub&gt;k&lt;/sub&gt;，把 key(j&lt;sub&gt;k&lt;/sub&gt;) 的式子代进去，转换成积分；同时把 &lt;span class="math"&gt;\(\forall t_k\)&lt;/span&gt; 转换为 &lt;span class="math"&gt;\(\prod_{t_k}\)&lt;/span&gt;。这些在 m=1 的证明中都提到过了。新出现的是 &lt;span class="math"&gt;\(\exists j_k\)&lt;/span&gt;，这个显然适用概率加法，因为 j&lt;sub&gt;k&lt;/sub&gt; 取不同的值对应于不同的互斥方案。经过这些变换得到：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{array}{rrrl}
\bar p_i(m)= &amp;amp; \sum_{j_1}( &amp;amp; &amp;amp; \int_0^1\prod_{t_1}p(r_{j_1}^{1/w_{j_1}} &amp;gt; r_{t_1}^{1/w_{t_1}})\mathrm d r_{j_1}\times\\
&amp;amp; &amp;amp; \sum_{j_2}( &amp;amp; \int_0^1\prod_{t_2} p(r_{j_2}^{1/w_{j_2}} &amp;gt; r_{t_2}^{1/w_{t_2}})\mathrm d r_{j_2}\times\\
&amp;amp; &amp;amp; &amp;amp; ...\times\\
&amp;amp; &amp;amp; &amp;amp; \sum_{j_m}(\int_0^1\prod_{t_m} p(r_{j_m}^{1/w_{j_m}} &amp;gt; r_{t_m}^{1/w_{t_m}})\mathrm d r_{j_m})\\
&amp;amp; &amp;amp; ) &amp;amp; \\ &amp;amp; ) &amp;amp; &amp;amp; \\
\end{array}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;其中的积分式在之前已经见过了，其运算过程如下（注意 t&lt;sub&gt;k&lt;/sub&gt; 的取值范围）：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{array}{rl}
&amp;amp; \int_0^1\prod_{t_k}p(r_{j_k}^{1/w_{j_k}} &amp;gt; r_{t_k}^{1/w_{t_k}})\mathrm{d}r_{j_k} \\
&amp;amp; \\
= &amp;amp; \int_0^1\prod_{t_k}r_{j_k}^{w_{t_k}/w_{j_k}}\mathrm{d}r_{j_k} \\
&amp;amp; \\
= &amp;amp; \int_0^1r_{j_k}^{(\sum_{t_k}w_{t_k})/w_{j_k}}\mathrm{d}r_{j_k} \\
&amp;amp; \\
= &amp;amp; \frac{w_{j_k}}{(\sum_{t_k}w_{t_k})+w_{j_k}} \\
&amp;amp; \\
= &amp;amp; \frac{w_{j_k}}{w-(w_{j_1}+w_{j_2}+...+w_{j_{k-1}})}
\end{array}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;最终，概率计算式子变成：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\bar p_i(m)=\sum_{j_1}\left(\frac{w_{j_1}}{w}\sum_{j_2}\left(\frac{w_{j_2}}{w-w_{j_1}}\sum_{j_3}\left(\frac{w_{j_2}}{w-w_{j_1}-w_{j_2}}\cdots\sum_{j_m}\frac{w_{j_m}}{w-\sum_{k=1}^{m-1}w_{j_k}}\right)\right)\right)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;与 &lt;a class="reference external" href="https://blog.gocalf.com/weighted-random-selection"&gt;前一篇&lt;/a&gt; 文章中的理论值完全一样。&lt;/p&gt;
&lt;p&gt;呼，可怕的推导过程。&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="id3"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="#id7"&gt;程序实现&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;虽然证明过程异常恐怖，但实现起来却很简单。实际运算中，只要维持一个大小为 m 的最小堆（没错，是最小堆）来保存当前已知的最大的 m 个键值，每拿到一个新的元素，算出对应的键值，如果它比堆中的最小值大，就可以放入堆中替换掉最小值。Python 实现函数如下：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;heapq&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;WeightedRandomSample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'invalid m'&lt;/span&gt;
  &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;heap&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Outputs the current selection and gets next item&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;continue&lt;/span&gt;
    &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;1.0&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
      &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;heapify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;index&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;heapreplace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;heap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;index&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;每次拿到一个新的元素，通过 &lt;tt class="docutils literal"&gt;key = rand.random() ** (1.0 / weight)&lt;/tt&gt; 产生一个与其权重有关的随机键值 key。当元素个数小于 m 时，直接将新的元素放入堆空间中（但并不建堆），这样只用 O(1) 时间；当遇到第 m 个元素后，堆空间放满了，这时候进行建堆操作（&lt;tt class="docutils literal"&gt;heapify(heap)&lt;/tt&gt;），需要 O(m) 时间；之后每拿到一个新的元素，用 O(1) 时间从堆顶拿出最小值与新元素的键值比较，如果后者更大就用后者替换掉堆顶元素，对堆进行必要的操作（O(log
m) 时间）以保持其结构（&lt;tt class="docutils literal"&gt;heapreplace(heap, (key, index))&lt;/tt&gt;）。&lt;/p&gt;
&lt;p&gt;关于 Python 中的堆可以参考：&lt;a class="reference external" href="http://docs.python.org/library/heapq.html"&gt;http://docs.python.org/library/heapq.html&lt;/a&gt;。&lt;/p&gt;
&lt;p&gt;总体来看，整段程序用时 O(n * log
m)，占用 O(m) 辅助空间。这样的处理比较适用于 m &amp;lt;&amp;lt;
n 的情况。当 m 与 n 接近时，可以用 n 个辅助空间存储所有元素的键值，当遍历结束后用 O(n) 时间对这 n 个元素执行快速选择算法，选出 m 个最大的元素即可，耗时 O(n)，辅助空间 O(n)。&lt;/p&gt;
&lt;p&gt;用同样一组具有等差分布权重的元素调用 &lt;tt class="docutils literal"&gt;WeightedRandomSample&lt;/tt&gt; 十万次，得到如下的概率分布，与理论分布非常接近。&lt;/p&gt;
&lt;div class="highcharts" id="weighted_sample-chart" style="height: 480px; width: 640px"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
$(function () {
    $('#weighted_sample-chart').highcharts({
        chart: {type: 'line', backgroundColor: null},
        title: {text: '用 WeightedRandomSample 函数随机选取 m 个元素，第 i 个元素被选中的概率'},
        xAxis: {categories: ['i=1', 'i=2', 'i=3', 'i=4', 'i=5', 'i=6', 'i=7', 'i=8', 'i=9', 'i=10'] },
        yAxis: {min: 0, max: 1, tickInterval: 0.1, title: { text: null} },
        series: [{
            name: 'm=1',
            data: [0.01824, 0.0371, 0.05426, 0.0723, 0.09161, 0.10988, 0.12501, 0.14523, 0.16448, 0.18189]
        }, {
            name: 'm=2',
            data: [0.03979, 0.07617, 0.11498, 0.15227, 0.18612, 0.22121, 0.25497, 0.28584, 0.32032, 0.34833]
        }, {
            name: 'm=3',
            data: [0.06173, 0.12283, 0.17995, 0.23588, 0.28565, 0.33511, 0.38292, 0.4259, 0.46621, 0.50382]
        }, {
            name: 'm=4',
            data: [0.08874, 0.17467, 0.25423, 0.32381, 0.39314, 0.45378, 0.5103, 0.55865, 0.60438, 0.6383]
        }, {
            name: 'm=5',
            data: [0.1239, 0.23698, 0.33544, 0.42587, 0.50627, 0.57379, 0.63485, 0.68303, 0.72241, 0.75746]
        }, {
            name: 'm=6',
            data: [0.16634, 0.31401, 0.43789, 0.54221, 0.62332, 0.6963, 0.74587, 0.79285, 0.82668, 0.85453]
        }, {
            name: 'm=7',
            data: [0.22243, 0.40975, 0.56211, 0.67063, 0.74944, 0.80965, 0.85354, 0.88449, 0.91023, 0.92773]
        }, {
            name: 'm=8',
            data: [0.31252, 0.54828, 0.71493, 0.8095, 0.87294, 0.91133, 0.93608, 0.95444, 0.96568, 0.9743]
        }, {
            name: 'm=9',
            data: [0.48359, 0.78327, 0.89211, 0.93922, 0.96197, 0.97692, 0.98513, 0.98987, 0.99282, 0.9951]
        }, {
            name: 'm=10',
            data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        }]
    });
});
&lt;/script&gt;&lt;/div&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Probability"></category><category term="Random Sample"></category></entry><entry><title>单次遍历，带权随机选取问题（一）</title><link href="https://blog.gocalf.com/weighted-random-selection" rel="alternate"></link><published>2011-09-21T23:07:00+08:00</published><updated>2011-09-27T13:20:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-09-21:/weighted-random-selection</id><summary type="html">&lt;p class="first last"&gt;问题描述：有一组数量未知的数据，每个元素有非负权重。要求只遍历一次，随机选取其中的一个元素，任何一个元素被选到的概率与其权重成正比。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;在 &lt;a class="reference external" href="https://blog.gocalf.com/random-selection"&gt;单次遍历，等概率随机选取问题&lt;/a&gt; 中已经剧透了今天的内容，那就是带权随机选取（Weighted
Random Sample）问题。&lt;/p&gt;
&lt;p&gt;问题描述：有一组数量未知的数据，每个元素有非负权重。要求只遍历一次，随机选取其中的一个元素，任何一个元素被选到的概率与其权重成正比。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;设元素总数为 n，当然在遍历结束前 n 是未知的。设第 i（1 &amp;lt;= i &amp;lt;=
n）个元素的权重为 w&lt;sub&gt;i&lt;/sub&gt;（&amp;gt;
0），则权重总和为 &lt;span class="math"&gt;\(w=\sum_{i=1}^{n}{w_i}\)&lt;/span&gt;，也是在遍历结束时才知道的。根据题目要求，第 i 个元素被选取的概率应该等于 &lt;span class="math"&gt;\(p_i=\frac{w_i}{w}\)&lt;/span&gt;。&lt;/p&gt;
&lt;p&gt;虽然加了个权重，但解法依旧非常简单，在 &lt;a class="reference external" href="https://blog.gocalf.com/random-selection"&gt;单次遍历，等概率随机选取问题&lt;/a&gt; 中的 &lt;tt class="docutils literal"&gt;RandomSelect&lt;/tt&gt; 函数上稍作修改就得到本问题的解法，依旧是 O(n) 时间，O(1) 辅助空间：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;WeightedRandomSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;totalweight&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Outputs the current selection and gets next item&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;
    &lt;span class="n"&gt;totalweight&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;totalweight&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;weight&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;其中 Python 的 &lt;tt class="docutils literal"&gt;random.random()&lt;/tt&gt; 返回 [0,
1) 之间的随机小数。&lt;/p&gt;
&lt;p&gt;看一下是否满足概率要求。如果最终被选取的是第 i（1 &amp;lt;= i &amp;lt;=
n）个元素，那必须是遍历到它的时候，恰好被选中，即 &lt;tt class="docutils literal"&gt;rand.random()
* totalweight &amp;lt; weight&lt;/tt&gt;，其概率为：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\frac{w_i}{w}=\frac{w_i}{\sum_{k=1}^{i}{w_k}}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;另外，还要在处理以后的任何一个元素时，第 i 个元素都没有被替换掉，即对于任意的 j（i
&amp;lt; j &amp;lt;= n），第 j 个元素都不会被选中，其概率为：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\frac{w-w_j}{w}=\frac{\sum_{k=1}^{j-1}{w_k}}{\sum_{k=1}^{j}{w_k}}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;因此，第 i 个元素最终被选取的概率为：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=\frac{w_i}{\sum_{k=1}^{i}{w_k}}\times\frac{\sum_{k=1}^{i}{w_k}}{\sum_{k=1}^{i+1}{w_k}}\times\frac{\sum_{k=1}^{i+1}{w_k}}{\sum_{k=1}^{i+2}{w_k}}\times\cdots\times\frac{\sum_{k=1}^{n-1}{w_k}}{\sum_{k=1}^{n}{w_k}}=\frac{w_i}{\sum_{k=1}^{n}{w_k}}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;下面这段程序调用 &lt;tt class="docutils literal"&gt;WeightedRandomSelect&lt;/tt&gt; 对一组具有等差数列权重的元素进行选取。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Sample code to use WeightedRandomSelect function&lt;/span&gt;
&lt;span class="c1"&gt;# Use an arithmetic sequence as weights&lt;/span&gt;
&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="c1"&gt;# weights are [1, 2, 3, ..., 10]&lt;/span&gt;
&lt;span class="n"&gt;weights&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;repeat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;
&lt;span class="n"&gt;occurrences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;WeightedRandomSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
  &lt;span class="n"&gt;occurrences&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;occurrences&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;某次运行结果为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[1723, 3644, 5405, 7326, 9027, 10903, 12678, 14784, 16345, 18165]
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;而对于这组权重的概率理论值为：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;1 : 2 : 3 : 4 : 5 : 6 : 7 : 8 : 9 : 10
= 0.0181818 : 0.0363636 : 0.0545455 : 0.0727273 : 0.0909091 : 0.109091 : 0.127273 : 0.145455 : 0.163636 : 0.181818
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可见程序是正确的。&lt;/p&gt;
&lt;div class="section" id="m"&gt;
&lt;h2&gt;扩展：选取 m 个元素，概率理论值&lt;/h2&gt;
&lt;p&gt;来看看选取多个元素的问题。当选取多个元素时，可以认为选取过程是逐步进行的，即无放回的多次选取。每一次选取时，任何一个元素被选中的概率都与其权重成正比，但总的权重则又剩余的元素集合决定。&lt;/p&gt;
&lt;p&gt;当 m=2 的时候，第 i 个元素被选中可以是两种情况：第一次就被选中；第一次未被选中，第二次被选中。可以得到其概率为这两种情况的概率之和，即：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i(2)=\frac{w_i}{w}+\sum_{j\neq i}\left(\frac{w_j}{w}\times\frac{w_i}{w-w_j}\right)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;值得注意的是，即便 w&lt;sub&gt;i&lt;/sub&gt; 和 w 不变，如果其他元素的概率分布不同，最后得到的结果也不同，因此上面这个式子无法把其中的求和化简掉。&lt;/p&gt;
&lt;p&gt;从另一方面来看，第 i 个元素被选中的概率等于 1 减去它不被选中的概率。用 &lt;span class="math"&gt;\(\bar p\)&lt;/span&gt; 表示不被选中的概率，则有：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\bar p_i(2)=\sum_{j\neq i}\left(\frac{w_j}{w}\times\frac{w-w_j-w_i}{w-w_j}\right)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;显然，&lt;span class="math"&gt;\(p_i(2)+\bar p_i(2)=1\)&lt;/span&gt;。&lt;/p&gt;
&lt;p&gt;当 m&amp;gt;2 时，其概率表达式将会变得异常复杂，因为跟概率分布有关，所以算式无法化简。未被选中的概率计算式要稍微简单些，大概是这个样子的：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\bar p_i(m)=\sum_{j_1}\left(\frac{w_{j_1}}{w}\sum_{j_2}\left(\frac{w_{j_2}}{w-w_{j_1}}\sum_{j_3}\left(\frac{w_{j_2}}{w-w_{j_1}-w_{j_2}}\cdots\sum_{j_m}\frac{w_{j_m}}{w-\sum_{k=1}^{m-1}w_{j_k}}\right)\right)\right)
\end{equation*}
&lt;/div&gt;
&lt;p&gt;其中，&lt;span class="math"&gt;\(\forall 1\leq k\leq m,j_k\notin\{i,j_1,j_2,\cdots,j_{k-1}\}\)&lt;/span&gt;。&lt;/p&gt;
&lt;p&gt;对于给定的一组权重，可以用下面这段程序计算出任意 m、i（程序中的 i 是从 0 开始的）对应的概率数值（请无视其 coding
style）：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalweight&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.0&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;totalweight&lt;/span&gt; \
         &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;totalweight&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;times&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;CalcSampleProbability&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;invalid i&amp;#39;&lt;/span&gt;
  &lt;span class="k"&gt;assert&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;&amp;#39;invalid m&amp;#39;&lt;/span&gt;
  &lt;span class="n"&gt;ids&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
  &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;remove&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Foo&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ids&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;weights&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;可惜算法的复杂度非常高，&lt;tt class="docutils literal"&gt;CalcSampleProbability&lt;/tt&gt; 需要 O(n^m) 时间来完成一次计算。期待高手改进。&lt;/p&gt;
&lt;p&gt;来看一下等权重、等差数列权重和等比数列权重的 n 选 m 概率分布图（图中 i 依旧采用 1
&amp;lt;= i &amp;lt;= n 的取值范围）：&lt;/p&gt;
&lt;div id="equal-p-chart" class="highcharts" style="height: 480px; width: 640px"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
$(function () {
    $('#equal-p-chart').highcharts({
        chart: {type: 'line', backgroundColor: null},
        title: {text: '等值权重随机选取 m 个元素，第 i 个元素被选中的概率'},
        xAxis: {categories: ['i=1', 'i=2', 'i=3', 'i=4', 'i=5', 'i=6', 'i=7', 'i=8', 'i=9', 'i=10'] },
        yAxis: {min: 0, max: 1, tickInterval: 0.1, title: { text: null} },
        series: [{
            name: 'm=1',
            data: [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]
        }, {
            name: 'm=2',
            data: [0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2]
        }, {
            name: 'm=3',
            data: [0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3, 0.3]
        }, {
            name: 'm=4',
            data: [0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4, 0.4]
        }, {
            name: 'm=5',
            data: [0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
        }, {
            name: 'm=6',
            data: [0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6, 0.6]
        }, {
            name: 'm=7',
            data: [0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7, 0.7]
        }, {
            name: 'm=8',
            data: [0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8, 0.8]
        }, {
            name: 'm=9',
            data: [0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9]
        }, {
            name: 'm=10',
            data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        }]
    });
});
&lt;/script&gt;&lt;div id="arithmetic-p-chart" class="highcharts" style="height: 480px; width: 640px"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
$(function () {
    $('#arithmetic-p-chart').highcharts({
        chart: {type: 'line', backgroundColor: null},
        title: {text: '等差分布权重随机选取 m 个元素，第 i 个元素被选中的概率'},
        xAxis: {categories: ['i=1', 'i=2', 'i=3', 'i=4', 'i=5', 'i=6', 'i=7', 'i=8', 'i=9', 'i=10'] },
        yAxis: {min: 0, max: 1, tickInterval: 0.1, title: { text: null} },
        series: [{
            name: 'm=1',
            data: [0.0181818, 0.0363636, 0.0545455, 0.0727273, 0.0909091, 0.109091, 0.127273, 0.145455, 0.163636, 0.181818]
        }, {
            name: 'm=2',
            data: [0.0387314, 0.0767641, 0.114058, 0.150568, 0.18625, 0.221051, 0.254916, 0.287787, 0.319597, 0.350277]
        }, {
            name: 'm=3',
            data: [0.0623607, 0.122317, 0.17976, 0.234582, 0.286682, 0.335965, 0.382357, 0.425805, 0.466296, 0.503875]
        }, {
            name: 'm=4',
            data: [0.0901537, 0.174687, 0.253418, 0.326208, 0.392976, 0.453728, 0.508577, 0.55777, 0.601688, 0.640794]
        }, {
            name: 'm=5',
            data: [0.123873, 0.236513, 0.337709, 0.427439, 0.505932, 0.573733, 0.631737, 0.681121, 0.723108, 0.758835]
        }, {
            name: 'm=6',
            data: [0.166654, 0.312339, 0.436946, 0.541013, 0.625933, 0.694068, 0.748498, 0.792059, 0.827084, 0.855406]
        }, {
            name: 'm=7',
            data: [0.224802, 0.410906, 0.558505, 0.670021, 0.750968, 0.809768, 0.852948, 0.885072, 0.909277, 0.927732]
        }, {
            name: 'm=8',
            data: [0.313537, 0.551687, 0.714389, 0.811455, 0.871435, 0.909967, 0.935546, 0.952993, 0.965166, 0.973825]
        }, {
            name: 'm=9',
            data: [0.481584, 0.784844, 0.89107, 0.938902, 0.963394, 0.977002, 0.985015, 0.989947, 0.99309, 0.995152]
        }, {
            name: 'm=10',
            data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        }]
    });
});
&lt;/script&gt;&lt;div id="geometric-p-chart" class="highcharts" style="height: 480px; width: 640px"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
$(function () {
    $('#geometric-p-chart').highcharts({
        chart: {type: 'line', backgroundColor: null},
        title: {text: '等比分布权重随机选取 m 个元素，第 i 个元素被选中的概率'},
        xAxis: {categories: ['i=1', 'i=2', 'i=3', 'i=4', 'i=5', 'i=6', 'i=7', 'i=8', 'i=9', 'i=10'] },
        yAxis: {min: 0, max: 1, tickInterval: 0.1, title: { text: null} },
        series: [{
            name: 'm=1',
            data: [0.000977517, 0.00195503, 0.00391007, 0.00782014, 0.0156403, 0.0312805, 0.0625611, 0.125122, 0.250244, 0.500489]
        }, {
            name: 'm=2',
            data: [0.0025488, 0.00509568, 0.0101837, 0.0203364, 0.0405476, 0.0805822, 0.159009, 0.308474, 0.569214, 0.804008]
        }, {
            name: 'm=3',
            data: [0.00537735, 0.0107444, 0.0214475, 0.0427281, 0.0847763, 0.166729, 0.321243, 0.584598, 0.817744, 0.944612]
        }, {
            name: 'm=4',
            data: [0.0108642, 0.021685, 0.0431948, 0.0856766, 0.1684, 0.324092, 0.588553, 0.82115, 0.946757, 0.989628]
        }, {
            name: 'm=5',
            data: [0.021834, 0.0434907, 0.0862602, 0.169534, 0.326218, 0.592157, 0.823804, 0.947919, 0.989983, 0.9988]
        }, {
            name: 'm=6',
            data: [0.0440274, 0.0873236, 0.171618, 0.330194, 0.599069, 0.828695, 0.949851, 0.990437, 0.998866, 0.999919]
        }, {
            name: 'm=7',
            data: [0.0895401, 0.175965, 0.338491, 0.613516, 0.838615, 0.953664, 0.991303, 0.998981, 0.999928, 0.999997]
        }, {
            name: 'm=8',
            data: [0.18552, 0.356749, 0.645339, 0.859134, 0.961175, 0.992947, 0.999194, 0.999944, 0.999998, 1]
        }, {
            name: 'm=9',
            data: [0.401858, 0.724258, 0.902929, 0.975573, 0.995862, 0.999551, 0.99997, 0.999999, 1, 1]
        }, {
            name: 'm=10',
            data: [1, 1, 1, 1, 1, 1, 1, 1, 1, 1]
        }]
    });
});
&lt;/script&gt;&lt;p&gt;Mathematica 提供了 &lt;tt class="docutils literal"&gt;RandomSample&lt;/tt&gt; 函数，支持带权选取，当然它是在遍历之前就已经知道元素个数的。给它一组等差分布的权重，可以看出十万次随机选取后得到的概率分布与上面的理论分布非常接近。&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;count = 10;
weights = Range[count];
elems = Range[count];
retry = 100000;
map = Table[
    freq = ConstantArray[0, count];
    For [i = 0, i &amp;lt; retry, i++,
        freq += BinCounts[RandomSample[weights -&amp;gt; elems, m], {1, count + 1, 1}]
    ];
    freq, {m, 1, count, 1}
];
ListLinePlot[map / retry, PlotMarkers -&amp;gt; Automatic]
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="figure"&gt;
&lt;img alt="mathematica_random_sample" src="https://blog.gocalf.com/images/2011/09/mathematica_random_sample.png" /&gt;
&lt;p class="caption"&gt;Mathematica &lt;tt class="docutils literal"&gt;RandomSample&lt;/tt&gt; 随机选取 m 个元素，第 i 个元素被选中的概率&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;苦苦思考了好几天，但并没有想到一个直观的方法可以给之前的 &lt;tt class="docutils literal"&gt;RandomSample&lt;/tt&gt; 加上权重处理。因为那概率式子太复杂，实在不知道该怎么去凑。不过在下一篇文章中将会介绍一个神奇的算法（当然不是我想出来的），并且会给出我的证明。&lt;/p&gt;
&lt;p&gt;现在发文章的速度越来越慢了，实在因为能力有限，每次为了一两个式子都要演算很久。再接再厉。&lt;/p&gt;
&lt;/div&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Probability"></category><category term="Random Sample"></category></entry><entry><title>单次遍历，等概率随机选取问题</title><link href="https://blog.gocalf.com/random-selection" rel="alternate"></link><published>2011-09-06T00:24:00+08:00</published><updated>2011-09-23T20:49:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-09-06:/random-selection</id><summary type="html">&lt;p class="first last"&gt;问题描述：假设我们有一堆数据（可能在一个链表里，也可能在文件里），数量未知。要求只遍历一次这些数据，随机选取其中的一个元素，任何一个元素被选到的概率相等。O(n) 时间，O(1) 辅助空间（n 是数据总数，但事先不知道）。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;又是一道概率问题，不过跟之前的题目一样，这也是一道非常简单的题目。&lt;/p&gt;
&lt;p&gt;问题描述：假设我们有一堆数据（可能在一个链表里，也可能在文件里），数量未知。要求只遍历一次这些数据，随机选取其中的一个元素，任何一个元素被选到的概率相等。O(n) 时间，O(1) 辅助空间（n 是数据总数，但事先不知道）。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;如果元素总数为 n，那么每个元素被选到的概率应该是 1/n。然而 n 只有在遍历结束的时候才能知道，在遍历的过程中，n 的值还不知道，可以利用乘法规则来逐渐凑出这个概率值。在 &lt;a class="reference external" href="https://blog.gocalf.com/build-rand3-from-rand5"&gt;《利用等概率 Rand5 产生等概率 Rand3》&lt;/a&gt; 中提到过，如果要通过有限步概率的加法和乘法运算，最终得到分子为 1、分母为 n 的概率，那必须在某一次运算中引入一个 n 在分母上，而分母和分子上其他的因数则通过加法、乘法、约分等规则去除。&lt;/p&gt;
&lt;p&gt;很容易能够想到这样一个简单的式子来凑出 1/n：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=\frac{1}{i}\times\frac{i}{i+1}\times\frac{i+1}{i+2}\times\cdots\times\frac{n-1}{n}=\frac{1}{n}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;下面用一段 Python 程序来实现这个过程，这里设计了一个类，叫做 &lt;tt class="docutils literal"&gt;RandomSelector&lt;/tt&gt;，提供方法 &lt;tt class="docutils literal"&gt;AddItem&lt;/tt&gt;，在遍历数据的时候把每个元素通过这个函数传进来，最后通过 &lt;tt class="docutils literal"&gt;SelectedItem&lt;/tt&gt; 获取随机选择的元素。这么做主要是为了强调事先不知道元素的总数。&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RandomSelector&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_selectedItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;SelectedItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_selectedItem&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Count&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_count&lt;/span&gt;

  &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;AddItem&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_selectedItem&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
    &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;在 Python 2.5 中，&lt;tt class="docutils literal"&gt;yield&lt;/tt&gt; 不仅是个语句，更是一个表达式（详见 &lt;a class="reference external" href="http://www.python.org/dev/peps/pep-0342/"&gt;PEP 342 -- Coroutines via Enhanced Generators&lt;/a&gt;，查阅 Generator 和 Coroutine，中文叫做“生成器”和“协程”），利用 &lt;tt class="docutils literal"&gt;yield&lt;/tt&gt; 可以把程序写的更简洁一些：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;RandomSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Outputs the current selection and gets next item&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;下面这段程序示意了如何调用 &lt;tt class="docutils literal"&gt;RandomSelect&lt;/tt&gt; 函数来测验其随机效果：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="c1"&gt;# Sample code to use RandomSelect function&lt;/span&gt;
&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;
&lt;span class="n"&gt;repeat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;100000&lt;/span&gt;
&lt;span class="n"&gt;occurrences&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;
&lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;repeat&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;selector&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;RandomSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;selector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;occurrences&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="nb"&gt;print&lt;/span&gt; &lt;span class="n"&gt;occurrences&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;十个元素，重复十万次，理论上每个元素会被选中恰好一万次。某次实验结果如下：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;[10020, 10084, 10003, 10008, 9985, 10145, 9987, 9925, 9955, 9888]
&lt;/pre&gt;&lt;/div&gt;
&lt;p&gt;可见每个元素被选中的次数相差不大，是等概率的。&lt;/p&gt;
&lt;p&gt;如果用 C#，就可以利用 &lt;tt class="docutils literal"&gt;IEnumerable&lt;/tt&gt; 来实现，比如：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;static&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="nf"&gt;RandomSelect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;IEnumerable&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Random&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;out&lt;/span&gt; &lt;span class="n"&gt;TSource&lt;/span&gt; &lt;span class="n"&gt;selectedItem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;ArgumentNullException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;&amp;quot;source&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;selectedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSource&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;foreach&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TSource&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Next&lt;/span&gt;&lt;span class="p"&gt;(++&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;==&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;selectedItem&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;核心代码也就那么两三行而已，时间复杂度为 O(n)（并且只遍历一次），空间复杂度为 O(1)。其中 Python 的 &lt;tt class="docutils literal"&gt;random.randint(x, y)&lt;/tt&gt; 返回 [x,
y] 之间的随机整数；C# 的 &lt;tt class="docutils literal"&gt;Random.Next(x)&lt;/tt&gt; 返回 [0,
x) 之间的随机整数。&lt;/p&gt;
&lt;p&gt;看一下概率，如果最终被选取的是第 i 个元素（1 &amp;lt;= i &amp;lt;=
n），那就必须是遍历到它的时候，恰好被选中（&lt;tt class="docutils literal"&gt;random.randint(0, i - 1) == 0&lt;/tt&gt; 或者 &lt;tt class="docutils literal"&gt;Random.Next(i) == 0&lt;/tt&gt;），并且从此之后都恰好再也没有被其他元素替换掉。这些事件彼此独立，计算概率的方法正好是上面提到的式子，最终的概率就是 1/n。&lt;/p&gt;
&lt;p&gt;OK，问题解决了。结束之前再做个简单的扩展，改成等概率随机选取 m 个元素（可知每个元素被选中的概率都是 m/n）。&lt;/p&gt;
&lt;p&gt;解决办法也非常简单，只要在上面的代码中，把 &lt;tt class="docutils literal"&gt;selectedItem&lt;/tt&gt;（&lt;tt class="docutils literal"&gt;selection&lt;/tt&gt;）改成一个长度为 m 的数组，稍作调整就可以了。&lt;/p&gt;
&lt;p&gt;这里就给出 Python 的程序片段：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;RandomSample&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;selection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="c1"&gt;# Outputs the current selection and gets next item&lt;/span&gt;
    &lt;span class="n"&gt;item&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;selection&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;item&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;idx&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;selection&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;idx&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;item&lt;/span&gt;
    &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;时间复杂度 O(n)，空间复杂度 O(m)（不可能是 O(1) 的）。概率的计算方法为：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=\left\{\begin{array}{ll} \frac{m}{i}\times\frac{i}{i+1}\times\frac{i+1}{i+2}\times\cdots\times\frac{n-1}{n}=\frac{m}{n} &amp;amp; i &amp;gt; m \\
1\times\frac{m}{m+1}\times\frac{m+1}{m+2}\times\cdots\times\frac{n-1}{n}=\frac{m}{n} &amp;amp; i \leq m \end{array} \right.
\end{equation*}
&lt;/div&gt;
&lt;p&gt;等概率问题通常都是比较简单的。下一次将会对这个问题做进一步的扩展，变成每个元素都有一个权重，要求任何一个元素被选取的概率正比于其权重。&lt;/p&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Probability"></category><category term="Random Sample"></category></entry><entry><title>等概率随机排列数组（洗牌算法）</title><link href="https://blog.gocalf.com/shuffle-algo" rel="alternate"></link><published>2011-09-01T21:58:00+08:00</published><updated>2011-09-01T22:01:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-09-01:/shuffle-algo</id><summary type="html">&lt;p class="first last"&gt;问题描述：假设有一个数组，包含 n 个元素。现在要重新排列这些数据，要求每个元素被放到任何一个位置的概率都相等（即 1/n），并且直接在数组上重排（in place），不要生成新的数组。用 O(n) 时间、O(1) 辅助空间。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;又是一道跟概率相关的简单问题。话说我的概率学的太差了，趁这个机会也从头开始补习一下。&lt;/p&gt;
&lt;p&gt;问题描述：假设有一个数组，包含 n 个元素。现在要重新排列这些元素，要求每个元素被放到任何一个位置的概率都相等（即 1/n），并且直接在数组上重排（in
place），不要生成新的数组。用 O(n) 时间、O(1) 辅助空间。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;算法是非常简单了，当然在给出算法的同时，我们也要证明概率满足题目要求。&lt;/p&gt;
&lt;p&gt;先想想如果可以开辟另外一块长度为 n 的辅助空间时该怎么处理，显然只要对 n 个元素做 n 次（不放回的）随机抽取就可以了。先从 n 个元素中任选一个，放入新空间的第一个位置，然后再从剩下的 n-1 个元素中任选一个，放入第二个位置，依此类推。&lt;/p&gt;
&lt;p&gt;按照同样的方法，但这次不开辟新的存储空间。第一次被选中的元素就要放入这个数组的第一个位置，但这个位置原来已经有别的（也可能就是这个）元素了，这时候只要把原来的元素跟被选中的元素互换一下就可以了。很容易就避免了辅助空间。&lt;/p&gt;
&lt;p&gt;用 Python 来写一段简单的程序描述这个算法：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;random&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;rand&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;  &lt;span class="c1"&gt;# 逆序遍历 li&lt;/span&gt;
    &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;randint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;              &lt;span class="c1"&gt;# 从剩余数据中随机选取一个&lt;/span&gt;
    &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;li&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;         &lt;span class="c1"&gt;# 将随机选取的元素与当前位置元素互换&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;主要的代码仅仅三行而已，浅显易懂。&lt;/p&gt;
&lt;p&gt;来计算一下概率。如果某个元素被放入第 i（&lt;span class="math"&gt;\(1\leq i\leq n\)&lt;/span&gt;）个位置，就必须是在前 i - 1 次选取中都没有选到它，并且第 i 次选取是恰好选中它。其概率为：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
p_i=\frac{n-1}{n}\times\frac{n-2}{n-1}\times\cdots\times\frac{n-i+1}{n-i+2}\times\frac{1}{n-i+1}=\frac{1}{n}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;可见任何元素出现在任何位置的概率都是相等的。&lt;/p&gt;
&lt;p&gt;实际上 Python 用户一定知道，在 Random 类中就有现成的 shuffle 方法，处理方法与我上面的程序是一样的。顺便也贴在这里学习一下。以下代码来自于
Python 2.5 &lt;tt class="docutils literal"&gt;Lib\random.py&lt;/tt&gt;：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;250
251
252
253
254
255
256
257
258
259
260
261
262&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;shuffle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="sd"&gt;&amp;quot;&amp;quot;&amp;quot;x, random=random.random -&amp;gt; shuffle list x in place; return None.&lt;/span&gt;

&lt;span class="sd"&gt;  Optional arg random is a 0-argument function returning a random&lt;/span&gt;
&lt;span class="sd"&gt;  float in [0.0, 1.0); by default, the standard random.random.&lt;/span&gt;
&lt;span class="sd"&gt;  &amp;quot;&amp;quot;&amp;quot;&lt;/span&gt;

  &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;random&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;
  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;reversed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;xrange&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;
    &lt;span class="c1"&gt;# pick an element in x[:i+1] with which to exchange x[i]&lt;/span&gt;
    &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;</content><category term="算法"></category><category term="Interview Question"></category><category term="Probability"></category></entry><entry><title>在循环有序数组中查找指定元素</title><link href="https://blog.gocalf.com/circularly-ordinal-array" rel="alternate"></link><published>2011-08-19T00:05:00+08:00</published><updated>2012-12-04T17:02:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-08-19:/circularly-ordinal-array</id><summary type="html">&lt;p class="first last"&gt;问题描述：给定一个由 n 个各不相等的元素构成的循环有序数组（Circularly Ordinal Array），用 O(log n) 时间、O(1) 辅助空间在其中查找指定的元素。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;这次的题目跟二分搜索有关，如果还不能写出正确的二分搜索，那就先找出课本温习一下吧。&lt;/p&gt;
&lt;p&gt;问题描述：给定一个由 n 个各不相等的元素构成的循环有序数组（Circularly
Ordinal Array），用 O(log n) 时间、O(1) 辅助空间在其中查找指定的元素。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;所谓循环有序数组，就是把一个排好序（以升序排列为例）的数组从某个（未知）位置处截为两段，把前一段放到后一段的后面，所得到的数组。比如
{8, 9, 10, 0, 1, 2, 3, 4, 5, 6, 7}。如果把数组首尾相接，看成一个环形，那么数组就还是有序的，只不过最小值有可能在任何一个位置。从最小值开始向后，数值逐渐递增，到数组的最后一个元素时再回到第一个元素。&lt;/p&gt;
&lt;p&gt;显然应用于普通的有序数组查找的二分算法已经无法直接使用了。如果已经知道了分界点（数组最小值或最大值）的位置，那问题就简单的多了，只要先判断一下待查元素是在分界点的左侧还是右侧，然后直接对那一侧的半个数组使用二分查找即可。&lt;/p&gt;
&lt;p&gt;那么怎么找分界点呢？它的特点是它左边的所有元素都比它右边的元素大。借鉴二分查找的思想，首先取数组中间位置的元素，拿它跟两端的元素比较，分析出分界点是在左半边还是右半边，然后对包含分界点的那半个数组递归处理。&lt;/p&gt;
&lt;p&gt;数组的第一个元素应该是在分界点左边最小的元素，但又不小于分界点右边的任何元素。那么如果中间位置的元素比它大，分界点就只能在中间元素的右边；反之，如果中间元素比它小，分界点就一定在左半边。由于题目中规定数组元素是个不相等的，这样的判断就足够了。&lt;/p&gt;
&lt;p&gt;如果允许重复的元素，那就有可能遇到第一个元素与中间元素相等情况，这时需要再拿最后一个元素来比较，如果中间元素比最后一个元素大，分界点就在右半边 &lt;span class="raw-html"&gt;&lt;s&gt;；反之，如果中间元素比最后一个元素小，分界点就在左半边 &lt;/s&gt;&lt;/span&gt; （感谢 chasefornone 的提醒，这种情况下，中间元素不会比最后一个元素小）。如果还是相等呢？&lt;/p&gt;
&lt;p&gt;看看下面这张图中的两种情况（A 和 B），显然在第一次二分处理的时候，第一个（下标 0）、中间的（下标 12）和最后一个（下标 24）元素都彼此相等，分界点却有可能在任何一边。这时候就只能分别对两半继续递归处理，时间复杂度可能会变成 O(n)，空间复杂度可能会（不得不用递归或者栈来保存中间状态）变成 O(log
n)。&lt;/p&gt;
&lt;div class="figure"&gt;
&lt;img alt="coa_special_case1.png" src="https://blog.gocalf.com/images/2011/08/coa_special_case1.png" /&gt;
&lt;p class="caption"&gt;有重复元素时的特殊情况：第一个、中间的和最后一个元素彼此相等&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;还是简单点儿，限定元素各不相同吧……&lt;/p&gt;
&lt;p&gt;实际上，如果仔细考量上面的寻找分界点的方法，就会发现它跟二分查找是多么的相似啊。因此另外一种方法就是将二分查找算法修改一下，相当于把找分界点跟搜索指定元素结合起来。在每次二分的时候，除了跟中间值做比较外，也要跟两端的数值做比较，以此来确定对哪一半分治处理。直接写出这种方法下的查找函数算法：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;CycleBSearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
  &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
  &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
  &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;          &lt;span class="c1"&gt;# found val&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# val is in left side&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="c1"&gt;# val is in right side&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;val&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;arr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
        &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;    &lt;span class="c1"&gt;# val is in right side&lt;/span&gt;
      &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;   &lt;span class="c1"&gt;# val is in left side&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;               &lt;span class="c1"&gt;# cannot find val&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;functional&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;&lt;/span&gt;
&lt;span class="k"&gt;using&lt;/span&gt; &lt;span class="k"&gt;namespace&lt;/span&gt; &lt;span class="n"&gt;std&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RandomAccessIterator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;CycleBinarySearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;CycleBinarySearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;less&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// A value, &amp;#39;a&amp;#39;, is considered equivalent to another, &amp;#39;b&amp;#39;, when&lt;/span&gt;
&lt;span class="c1"&gt;// (!comp(a, b) &amp;amp;&amp;amp; !comp(b, a)).&lt;/span&gt;
&lt;span class="k"&gt;template&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;RandomAccessIterator&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Compare&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;
&lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;CycleBinarySearch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                       &lt;span class="n"&gt;Compare&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;right&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;RandomAccessIterator&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;// find value&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// value could be in left side&lt;/span&gt;
                &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// value could be in right side&lt;/span&gt;
                &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;else&lt;/span&gt;
        &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;left&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;comp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// value could be in right side&lt;/span&gt;
                &lt;span class="n"&gt;left&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="k"&gt;else&lt;/span&gt;
            &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// value could be in left side&lt;/span&gt;
                &lt;span class="n"&gt;right&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;mid&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// cannot find value&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;last&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;话说我还是更喜欢 Python 啊。&lt;/p&gt;
</content><category term="算法"></category><category term="Interview Question"></category><category term="Lookup Algorithm"></category></entry><entry><title>利用等概率 Rand5 产生等概率 Rand3</title><link href="https://blog.gocalf.com/build-rand3-from-rand5" rel="alternate"></link><published>2011-08-02T23:10:00+08:00</published><updated>2013-11-07T22:00:00+08:00</updated><author><name>Calf</name></author><id>tag:blog.gocalf.com,2011-08-02:/build-rand3-from-rand5</id><summary type="html">&lt;p class="first last"&gt;问题描述：现在有一个叫做 Rand5 的函数，可以生成等概率的 [0, 5) 范围内的随机整数，要求利用此函数写一个 Rand3 函数（除此之外，不能再使用任何能产生随机数的函数或数据源），生成等概率的 [0, 3) 范围内的随机整数。&lt;/p&gt;
</summary><content type="html">&lt;p&gt;问题本身很明确，但不知道起个什么题目好，姑且先这么说吧。&lt;/p&gt;
&lt;p&gt;问题描述：现在有一个叫做 Rand5 的函数，可以生成等概率的 [0,
5) 范围内的随机整数，要求利用此函数写一个 Rand3 函数（除此之外，不能再使用任何能产生随机数的函数或数据源），生成等概率的 [0,
3) 范围内的随机整数。&lt;/p&gt;
&lt;!-- more --&gt;
&lt;p&gt;我第一次遇到这个问题的时候，着实犯了一回傻，自以为是地证明了这个题目是无解的。其实从概率的角度来看，题目的要求就是，利用一个 1/5 的概率源，通过某种方式产生出 1/3 的概率输出。我们都知道，概率运算法则有加法和乘法，而在我的记忆中，算法是“在有限步骤内求解某一问题所使用的一组定义明确的规则”，算法的一个重要特征就是有穷性，即一个算法必须保证执行有限步之后结束。那么有限多个 1/5 通过加法和乘法是不可能的到 1/3 这个数值的，因为加法和乘法都不会给分母带来新的因子，那么分母中的 3 根本就不可能出现。&lt;/p&gt;
&lt;p&gt;然而我忽略了这样一个式子：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\sum_{i=0}^\infty \left(\frac{2}{5}\right)^i = \frac{1}{1-\frac{2}{5}} = \frac{5}{3}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;基于这个想法，我们来看看这个算法是什么样子的：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Rand3&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Rand5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7
8
9&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;Rand3&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Rand5&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;算法很简单，x 是我们最终要输出的数字，只要它不在 [0,
3) 范围内，就不断地调用 Rand5 来更新它。直观地看，算法输出的数字只有 0、1、2 这三个，而且对任何一个都没有偏袒，那么显然每个数字的概率都是 1/3，那让我们来严格地计算一下。&lt;/p&gt;
&lt;p&gt;以输出 0 为例，看看概率是多少。x 的第一个有效数值是通过 Rand5 得到的。Rand5 返回 0 的概率是 1/5，如果这事儿发生了，我们就得到了 0，否则只有当 Rand5 返回 3 或 4 的时候，我们才有机会再次调用它来得到新的数据。第二次调用 Rand5 之后，又是有 1/5 的概率得到 0，2/5 的概率得到 3 或 4 导致循环继续执行下去，如此反复。因此概率的计算公式为：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{array}{rcl}
p &amp;amp; = &amp;amp; \frac{1}{5}+\frac{2}{5}\times\left(\frac{1}{5}+\frac{2}{5}\times\left(\frac{1}{5}+\frac{2}{5}\times\left(\cdots\right)\right)\right) \\
&amp;amp; = &amp;amp; \frac{1}{5}\times\sum_{i=0}^\infty \left(\frac{2}{5}\right)^i \\
&amp;amp; = &amp;amp; \frac{1}{5}\times\frac{1}{1-\frac{2}{5}} \\
&amp;amp; = &amp;amp; \frac{1}{5}\times\frac{5}{3} \\
&amp;amp; = &amp;amp; \frac{1}{3} \end{array}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;喏，计算表明，Rand3 输出 0 的概率确实是 1/3，对于另外两个数字也是一样的。&lt;/p&gt;
&lt;p&gt;那么这段代码是不是一个算法呢，它是否满足算法的有穷性呢？我不能确定，虽然它不停机的概率是 0，然而这个概率是一个极限值，唉，回去复习极限知识先。&lt;/p&gt;
&lt;p&gt;【2013 年 11 月 7 日添加】今天想到，对于上面那个函数，需要再了解一下它消耗的时间。具体来讲，就是要知道平均每调用一次 Rand3，相当于调用了多少次 Rand5。根据算法可以知道，Rand3 函数执行一次，有 3/5 的概率是只调用一次 Rand5 就能停机；刚好调用两次 Rand5 后停机的概率是 (2/5) * (3/5)。类推下去，刚好调用 k 次 Rand5 后停机的概率应该是 (2/5) ^ (k-1) * (3/5)。根据这个概率分布，可以计算出停机前 Rand5 被调用次数的数学期望，即&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\sum_{k=1}^{\infty}{k\times p(k)}
=\sum_{k=1}^{\infty}k \frac{3}{5} \left(\frac{2}{5}\right)^{k-1}
=\frac{3}{5}\times\frac{1}{\left(1-\frac{2}{5}\right)^2}
=\frac{5}{3}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;可见，Rand3 函数每运行一次，平均需要调用 1.67 次 Rand5。&lt;/p&gt;
&lt;p&gt;更一般地，当我们依据上述算法，将一种分布的随机信号转换成另外一种随机信号时，如果每消耗 m 个源信号，就有 p 的概率可以产生一个目标信号，那么平均来讲，停机前需要使用的源信号数据个数的期望为：&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\sum_{k=1}^{\infty}k\cdot m\cdot p\cdot (1-p)^{k-1}=\frac{m}{p}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;【2013 年 11 月 7 日添加结束】&lt;/p&gt;
&lt;p&gt;改变一下题目，如果要求利用 Rand5 编写 Rand7 怎么办？很简单，用两个 Rand5 可以拼出 Rand25，然后就用前面的方法即可：&lt;/p&gt;
&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;Rand7&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Rand5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Rand5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;table class="highlighttable"&gt;&lt;tr&gt;&lt;td class="linenos"&gt;&lt;div class="linenodiv"&gt;&lt;pre&gt;1
2
3
4
5
6
7
8
9&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;&lt;td class="code"&gt;&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;Rand7&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;do&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Rand5&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;Rand5&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;&lt;p&gt;【2013 年 11 月 7 日】可以直接算出，按照这种方法，平均每运行一次 Rand7，需要调用 Rand5 的次数。这里 m 等于 2，p 等于 21/25，所以最后的结果是 50/21，大约是 2.38。&lt;/p&gt;
</content><category term="算法"></category><category term="Probability"></category><category term="Interview Question"></category></entry></feed>