<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom"><title type="text">博客园_一个程序员的自省</title><subtitle type="text"/><id>http://feed.cnblogs.com/blog/u/15645/rss</id><updated>2012-05-21T16:51:16Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><generator>feed.cnblogs.com</generator><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/"/><link rel="self" type="application/atom+xml" href="http://feed.cnblogs.com/blog/u/15645/rss"/><entry><id>http://www.cnblogs.com/anderslly/archive/2012/04/19/fibonacci-number.html</id><title type="text">迷人的斐波那契数</title><summary type="text">繁殖力超强的兔子说到斐波那契数，我们自然会想到曾经有一群繁殖力超强的兔子。比萨的商人斐波那契（Fibonacci，12-13世纪，称为比萨的列奥那多）接触到阿拉伯数学后，在其著作《Liber Abaci》中，引入了这个著名的兔子问题。但如果向前追溯下去，则可以追溯到古老的印度数学。斐波那契使用了一个理想化了的兔子生长模型进行研究，并假设：第一个月初有一对刚诞生的兔子两个月之后(第三个月初)它们可以生育每月每对可生育的兔子会诞生下一对新兔子兔子永不死去从第一个月开始，兔子的数目（对）依次是：1，1，2，3，5，8。。。这样就形成了一个序列，记为{Fn}，则该序列存在一个递推关系：F(n)=F(n</summary><published>2012-04-18T16:31:00Z</published><updated>2012-04-18T16:31:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2012/04/19/fibonacci-number.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2012/04/19/fibonacci-number.html"/><content type="html">&lt;p&gt;&lt;strong&gt;繁殖力超强的兔子&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;说到斐波那契数，我们自然会想到曾经有一群繁殖力超强的兔子。比萨的商人斐波那契（Fibonacci，12-13世纪，称为比萨的列奥那多）接触到阿拉伯数学后，在其著作《Liber Abaci》中，引入了这个著名的兔子问题。但如果向前追溯下去，则可以追溯到&lt;a href="http://en.wikipedia.org/wiki/Fibonacci_number#Origins" target="_blank"&gt;古老的印度数学&lt;/a&gt;。斐波那契使用了一个理想化了的兔子生长模型进行研究，并假设：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;第一个月初有一对刚诞生的兔子&lt;/li&gt;&lt;li&gt;两个月之后(第三个月初)它们可以生育&lt;/li&gt;&lt;li&gt;每月每对可生育的兔子会诞生下一对新兔子&lt;/li&gt;&lt;li&gt;兔子永不死去&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;从第一个月开始，兔子的数目（对）依次是：1，1，2，3，5，8。。。这样就形成了一个序列，记为{Fn}，则该序列存在一个递推关系：&lt;strong&gt;F(n)=F(n-1)+F(n-2)&lt;/strong&gt;，n &amp;gt;= 3。可以通过如下简单的推断得出：F(n)表示在第n个月时兔子的对数，这些兔子分为两部分，一是在第n-1个月已有的兔子，因为它们都活了下来（实际上会一直活下去），也就是F(n-1)；二是F(n-1)对兔子中可生育的兔子，也就是已经生活了至少二个月的兔子，这个数字恰好是在第n-2个月已有的兔子，即F(n-2)。如果令F(0)=0，则F(n)的定义可推广至所有非负整数（&lt;strong&gt;公式一&lt;/strong&gt;）：&lt;/p&gt;&lt;ul style="list-style-type: disc;"&gt;&lt;li&gt;F(0)=0&lt;/li&gt;&lt;li&gt;F(1)=1&lt;/li&gt;&lt;li&gt;F(n) = F(n-1) + F(n-2)，当n &amp;gt; 1&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;递归求值&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这看起来是个非常简单的递推关系，若要通过程序求值， 很自然地，可通过递归实现（&lt;strong&gt;算法一&lt;/strong&gt;）：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;span style="color: #0000ff;"&gt;&lt;span style="color: #0000ff;"&gt;// 递归算法 by Anders Cui&lt;/span&gt;&lt;br /&gt;public&lt;/span&gt; &lt;span style="color: #0000ff;"&gt;int&lt;/span&gt; Fib(&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; n)&lt;br/&gt;{&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt; (n &amp;lt;= &lt;span style="color: #800080;"&gt;1&lt;/span&gt;) { &lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; n; }&lt;br/&gt;&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt; Fib(n - &lt;span style="color: #800080;"&gt;1&lt;/span&gt;) + Fib(n - &lt;span style="color: #800080;"&gt;2&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br/&gt;}&lt;/span&gt;&lt;/div&gt;&lt;p&gt;很多书籍在讲解递归求解时，也常常会选择斐波那契数作为例子。这个例子是一个很直观的递归应用，也很有趣，但却算不上一个好的应用。比如，求F(5)时可表示为下图：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2012/12089/2012041723551378.png" alt="" /&gt;&lt;/p&gt;&lt;p&gt;即使对5这样小的数进行求值，也可以看到一点儿端倪，F(3)、F(2)都要多次求值，如果n的值很大，那么其中计算的冗余就非常多了。想办法来改进一下，最直接的办法就是要重用每一项之前的项，这样计算每一项时只需要一次加法。比如，要计算F(n)，就创建一个长度为n+1的数组，逐一计算每一项，F(n)的时间复杂度和空间复杂度都是O(n)，这是动态规划的应用。进一步地，按上面的方法，我们创建了一个长度为n+1的数组，但实际上每次运算只会涉及到三个相邻的元素，这就意味着只需要三个变量就可以&lt;strong&gt;维护所需要的值&lt;/strong&gt;了，从而可以将空间复杂度降低到O(1)（&lt;strong&gt;算法二&lt;/strong&gt;）：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;span style="color: #0000ff;"&gt;// 算法二 by Anders Cui&lt;br /&gt;public&lt;/span&gt; &lt;span style="color: #0000ff;"&gt;static&lt;/span&gt; &lt;span style="color: #0000ff;"&gt;int&lt;/span&gt; Fib(&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; n)&lt;br/&gt;{&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt; (n &amp;lt;= &lt;span style="color: #800080;"&gt;1&lt;/span&gt;) { &lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; n; }&lt;br/&gt;&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt; &lt;strong&gt;previousButOne&lt;/strong&gt; = &lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt; &lt;strong&gt;previous&lt;/strong&gt; = &lt;span style="color: #800080;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt; &lt;strong&gt;answer&lt;/strong&gt; = &lt;span style="color: #800080;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br/&gt;&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;for&lt;/span&gt; (&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt; i = &lt;span style="color: #800080;"&gt;2&lt;/span&gt;; i &amp;lt; n; i++&lt;span style="color: #000000;"&gt;)&lt;br/&gt;    {&lt;br/&gt;        previousButOne &lt;/span&gt;=&lt;span style="color: #000000;"&gt; previous;&lt;br/&gt;        previous &lt;/span&gt;=&lt;span style="color: #000000;"&gt; answer;&lt;br/&gt;        answer &lt;/span&gt;= previousButOne +&lt;span style="color: #000000;"&gt; previous;&lt;br/&gt;    }&lt;br/&gt;&lt;br/&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; answer;&lt;br/&gt;}&lt;/span&gt;&lt;/div&gt;&lt;p&gt;那有没有更高效的算法？别忘了，本文的主题是斐波那契序列，在考虑序列时，往往需要分析它的通项公式，如果能找出它的通项公式，那么就可以快速得出答案了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;斐波那契序列的通项公式&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;前面提到F(n)的递推公式是F(n) = F(n-1) + F(n-2)，即F(n) - F(n-1) - F(n-2) = 0，这是一个所谓&amp;ldquo;&lt;strong&gt;带常系数的齐次二阶线性递推式&lt;/strong&gt;&amp;rdquo;，由其特征方程及初始值F(0)和F(1)，可以求出通项公式为（过程略）：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2012/12089/2012041823125060.gif" alt="" /&gt;&lt;/p&gt;&lt;p&gt;这个公式令人称奇之处在于，通过无理数的乘方表示出了一个整数序列的所有元素！通过此公式还可以确定F(n)是呈&lt;strong&gt;指数级增长&lt;/strong&gt;的。不管怎样，根据通项公式可以在O(1)时间内得到答案（算是&lt;strong&gt;算法三&lt;/strong&gt;吧），但计算机不能完全精确地表示无理数，从而&lt;strong&gt;无法保证计算的精度&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;不过一旦有了通项公式，研究兔子序列时就方便多了。下面将讨论算法一（递归算法）的效率。&lt;/p&gt;&lt;p&gt;在使用递归进行计算的时候，算法的基本操作无疑就是加法。用A(n)表示计算F(n)时所需要的加法次数，则在n&amp;gt;1时，有A(n) = A(n-1) + A(n-2) + 1，与F(n)递推式不同，这是一个非齐次递推式，其求解方法有所不同，不过对于这里的A(n)，却可以快速解出。将上式变形为：&lt;/p&gt;&lt;p&gt;[A(n) + 1] - [A(n-1) + 1] - [A(n-2) + 1] = 0，&lt;/p&gt;&lt;p&gt;如果令B(n) = A(n) + 1，则B(n) - B(n-1) - B(n-2) = 0，B(0) = 1，B(1) = 1。到这里可以发现，B(n)与F(n)递推式相同，不过前者从1,1开始，后者从0,1开始，即B(n) = F(n+1)。所以A(n) = F(n+1) - 1，从而&lt;strong&gt;得出A(n)也为指数级&lt;/strong&gt;。这是斐波那契序列的另一个奇妙之处：通过自身的通项公式了解自身的计算特点。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;性质、变形与应用&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;斐波那契序列有为数众多的有趣性质，甚至有专门讨论它的杂志，这里不再赘述。&lt;/p&gt;&lt;p&gt;由本文开头的递推关系给出，其中两个要素是递推关系和初始值。如果对这两个要素进行调整，就可以得到其它相关的序列，如卢卡斯数、反斐波那契数等等。另外，斐波那契数不仅仅是一个数字谜题，它也有很多应用，这里仅给出两个例子。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;爬梯子&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;假设每一步可以爬一格或者两格梯子，爬一部n格梯子一共可以用几种不同的方法？（比如三格的梯子有三种不同的爬法：1-1-1，1-2，2-1）&lt;/p&gt;&lt;p&gt;在分析这个问题的时候，也可以考虑用递归。假设爬n格梯子有A(n)种不同的方法，第一步要么爬一格，此时剩下的格子爬完共有A(n-1)种；要么爬两格，此时剩下的格子爬完共有A(n-2)种，从而得到递推关系A(n) = A(n-1) + A(n-2)，初始值为A(1) = 1，A(2) = 2。可以看出A(n)与F(n)的关系是：A(n) = F(n + 1)，n &amp;gt; 0。&lt;/p&gt;&lt;p&gt;F(n)在很多这样的&lt;strong&gt;组合&lt;/strong&gt;题中都有应用，另外F(n)的一个组合证明可以&lt;a href="http://www.matrix67.com/blog/archives/4891" target="_blank"&gt;看这里&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;欧几里德算法（求最大公约数）的效率分析&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;欧几里德算法据说是史上第一个算法，这两个古老的算法也有重要的交集。首先给出F(n)的一个性质（可通过数学归纳法证明）：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2012/12089/2012041900041073.gif" alt="" /&gt;&lt;/p&gt;&lt;p&gt;然后通过辗转相除法的定义和F(n)的这个性质可以证明：欧几里德算法求解GCD(a, b)时使用的除法次数不大于b的十进制位数的5倍。再由对数的性质可以得出欧几里德算法使用O(logb)次除法就可以求出GCD(a, b)。&lt;/p&gt;&lt;p&gt;（涉及很多公式书写，这里证明从略，具体过程可参考《离散数学及其应用》的3.4节）&lt;/p&gt;&lt;p&gt;&lt;strong&gt;另一个算法&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;F(n-1)、F(n)和F(n+1)的关系也可以用矩阵表示出来：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2012/12089/2012041900164084.gif" alt="" /&gt;&lt;/p&gt;&lt;p&gt;这里n &amp;gt; 0，这样F(n)的计算可以转化为右边矩阵的乘方，而这个过程的时间复杂度是O(logn)，所以这是一个不错的选择。&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;strong&gt;小结&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;斐波那契序列有很多迷人的性质和有趣的应用，本文仅为匆匆一瞥，希望能让你对它多一些兴趣。总之，繁殖力超群的兔子们在欢乐的繁殖着，而&amp;ldquo;兔子问题&amp;rdquo;也没闲着，吸引着人们对它不断地研究，并得到了广泛应用。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://book.douban.com/subject/1968704/" target="_blank"&gt;《算法设计与分析基础》&lt;br /&gt;&lt;/a&gt;&lt;a href="http://book.douban.com/subject/3004255/" target="_blank"&gt;《编程之美》&lt;/a&gt;&lt;br /&gt;&lt;a href="http://zh.wikipedia.org/wiki/%E6%96%90%E6%B3%A2%E9%82%A3%E5%A5%91%E6%95%B0%E5%88%97#.E7.9B.B8.E9.97.9C.E7.9A.84.E6.95.B8.E5.88.97" target="_blank"&gt;斐波那契数&lt;/a&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Fibonacci_number" target="_blank"&gt;Fibonacci Number&lt;br /&gt;&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/2454599.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2012/04/19/fibonacci-number.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2012/04/16/swaping-disks.html</id><title type="text">趣题一则：交替放置的碟子</title><summary type="text">有数量为2n的一排碟子，n黑n白交替放置。现在要把黑碟子都放在右边，白碟子都放在左边，但只允许通过交换相邻碟子的位置来实现。实现这个过程要交换多少次？分析首先把问题转化一下，用1表示黑碟子，0表示白碟子，那么目前的顺序是：1010...1010结果要求1都放在右边，0都放在左边。这个题目看起来很眼熟。看关键字：交换相邻的碟子，排好顺序。嗯，就是经常出现在面试中的冒泡排序了。为便于观察，假设目前有6个碟子：101010。使用冒泡排序，第一次迭代，碟子序列变为：010101，交换3次。在进行第二次迭代之前，观察一下。现在，不仅第一个碟子就位，最后一个也是了，因此第二次迭代只需要对第2到第5个进行排</summary><published>2012-04-16T13:43:00Z</published><updated>2012-04-16T13:43:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2012/04/16/swaping-disks.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2012/04/16/swaping-disks.html"/><content type="html">&lt;p&gt;有数量为2n的一排碟子，n黑n白交替放置。现在要把黑碟子都放在右边，白碟子都放在左边，但只允许通过交换相邻碟子的位置来实现。实现这个过程要交换多少次？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;首先把问题转化一下，用1表示黑碟子，0表示白碟子，那么目前的顺序是：&lt;/p&gt;&lt;p&gt;1010...1010&lt;/p&gt;&lt;p&gt;结果要求1都放在右边，0都放在左边。这个题目看起来很眼熟。看关键字：&lt;strong&gt;交换相邻的碟子&lt;/strong&gt;，&lt;strong&gt;排好顺序&lt;/strong&gt;。嗯，就是经常出现在面试中的&lt;strong&gt;冒泡排序&lt;/strong&gt;了。&lt;/p&gt;&lt;p&gt;为便于观察，假设目前有6个碟子：101010。使用冒泡排序，第一次迭代，碟子序列变为：010101，交换3次。在进行第二次迭代之前，观察一下。&lt;/p&gt;&lt;p&gt;现在，不仅第一个碟子就位，最后一个也是了，因此第二次迭代只需要对第2到第5个进行排序，巧合的是，碟子[2-&amp;gt;5]仍然是10交替出现，不过比上一次少了两个，这样就简单了，可以得到结论：对于2n个碟子，可以使用n次迭代完成，交换的次数分别是：n+(n-1)+...+2+1，即n(n+1)/2。&lt;/p&gt;&lt;p&gt;顺便说一句，对于常见的经典排序算法，要么是简单直观的，如选择排序、插入排序，其实就是平时摸牌时所用的方法；要么是效率较高的，如快速排序、归并排序。我觉得冒泡排序既不直观，也不高效，也许就因为得了一个好名字，就名扬算法界了，所以名字神马的很重要！&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;a href="http://book.douban.com/subject/1968704/" target="_blank"&gt;《算法设计与分析基础》&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/2451119.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2012/04/16/swaping-disks.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2012/04/15/von-neumann-neighborhood.html</id><title type="text">趣题一则：冯&amp;#183;诺依曼邻居问题</title><summary type="text">这里并不是真的要研究cellular automaton的问题，而只是考虑一个非常简单的递推关系，该问题如下图所示：从r=0开始，此时格子上有一个方块，然后逐步演化，每一步都在上一步的基础上添加一圈儿方块，当r=n时，会有多少个方块呢？分析假设初始方块为O，从r=0到r=1时，在O的水平和垂直方向上各增加了两个方块；从r=1到r=2时，也是如此，同时在其它方向上也增加了若干方块。由此，可以把方块的增加分为两部分，一是水平和垂直方向D1，二是其它方向D2。可以看到，每到新的一步，D1方向上增加的都是4个方块，它们都与原图（上一步）的一个边相邻；而D2方向上增加的方块都与原图的两条边相邻。这样，只</summary><published>2012-04-14T16:51:00Z</published><updated>2012-04-14T16:51:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2012/04/15/von-neumann-neighborhood.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2012/04/15/von-neumann-neighborhood.html"/><content type="html">该文只有注册用户登录后才能阅读。&lt;a href='http://www.cnblogs.com/anderslly/archive/2012/04/15/von-neumann-neighborhood.html' target='_blank'&gt;阅读全文&lt;/a&gt;。</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2012/04/10/finding-the-door.html</id><title type="text">趣题一则：寻找那扇门</title><summary type="text">现在出现在你面前的是一堵朝两个方向无限延伸的墙。墙上有一扇门，但你并不确定门离你有多远，也不知道门位于哪个方向（左边或是右边）。你只有在走到门面前才能看到它。假设从当前位置到门要走n步（n大小未知），那么怎样走O(n)步就能找到那扇门？分析这道题让人“左右为难”，因为不确定如何才能走到尽快确定方向和位置。首先想到在错误的方向上走得越远，就意味着离正确的位置越远，因此较为保险的方法是，第一步向右走，看看有没有门；第二步——为了防止在错误的方向上渐行渐远——向左走，走两步，这样就可以确定在最初位置的一步范围内有没有门了。接下来，按照类似的方式左右徘徊，依次确定在最初位置的2，3，...，n有没有门</summary><published>2012-04-10T15:51:00Z</published><updated>2012-04-10T15:51:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2012/04/10/finding-the-door.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2012/04/10/finding-the-door.html"/><content type="html">&lt;p&gt;现在出现在你面前的是一堵朝两个方向无限延伸的墙。墙上有一扇门，但你并不确定门离你有多远，也不知道门位于哪个方向（左边或是右边）。你只有在走到门面前才能看到它。假设从当前位置到门要走n步（n大小未知），那么怎样走O(n)步就能找到那扇门？&lt;/p&gt;&lt;p&gt;&lt;strong&gt;分析&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;这道题让人&amp;ldquo;左右为难&amp;rdquo;，因为不确定如何才能走到尽快确定方向和位置。首先想到在错误的方向上走得越远，就意味着离正确的位置越远，因此较为保险的方法是，第一步向右走，看看有没有门；第二步&amp;mdash;&amp;mdash;为了防止在错误的方向上渐行渐远&amp;mdash;&amp;mdash;向左走，走两步，这样就可以确定在最初位置的一步范围内有没有门了。&lt;/p&gt;&lt;p&gt;接下来，按照类似的方式左右徘徊，依次确定在最初位置的2，3，...，n有没有门了。不过，直觉上就会知道走了很多冤枉路，很可能不是O(n)了。下面来具体计算一下。&lt;/p&gt;&lt;p&gt;假设T(n)为按上述方法确定出左右各n步范围内是否有门所需要的步数，那么&lt;/p&gt;&lt;p&gt;T(1) = 2 + 1&lt;/p&gt;&lt;p&gt;T(2) = T(1) + 2*2 + 1&lt;/p&gt;&lt;p&gt;T(3) = T(2) + 2*3 + 1&lt;/p&gt;&lt;p&gt;...&lt;/p&gt;&lt;p&gt;T(n) = T(n-1) + 2*n + 1&lt;/p&gt;&lt;p&gt;两边相加，得T(n) = n^2 + 2*n，看来这个方法太慢了。&lt;/p&gt;&lt;p&gt;为了更好地分析慢的原因，我们从另一个角度来看上面的方法。将寻找门的过程看作一个个回合，初始位置标记为O，第一回合向右走一步，回到O，向左走一步，回到O；第二回合向右走二步，回到O，向左走二步，回到O；。。。；第n回合向右走n步，回到O，向左走n步，回到O。这样总的步数是4*(1+2+...+n)=2(n^2+n)。每个回合仅比上一回合多一步，造成的结果就是大量的重复。但如果考虑一种极端的情况，第一回合向右走n步，回到O，向左走n步，共n步，由于不知道n的大小，这算不上是一种有效的方法，但它说明，如果我们每个回合间的跨度够大，确实有可能达到O(n)。&lt;/p&gt;&lt;p&gt;在常见的渐进效率类型中，比多项式明显要大的是指数级，如2^n，这里先考察这种情况。即第i回合向右走2^i步，回到O，向左走2^i步，回到O，这里0&amp;lt;=i&amp;lt;=k且2^(k-1)&amp;lt;n&amp;lt;=2^k。这样经过k个回合就可以找到门的位置，总的步数是：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2012/12089/2012041023364316.gif" alt="" /&gt;&lt;/p&gt;&lt;p&gt;可以看到，这种新的回合制是符合要求的。接下来也许可以尝试更大的跨度，如3^n或n!甚至是n*n!，这里先不讨论了。&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;参考：&lt;/p&gt;&lt;p&gt;《&lt;a href="http://book.douban.com/subject/1968704/" target="_blank"&gt;算法设计与分析基础&lt;/a&gt;》&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/2441406.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2012/04/10/finding-the-door.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2011/06/17/hashset-part2.html</id><title type="text">HashSet的实现（下）</title><summary type="text">在HashSet的实现（上）中，简要介绍了散列法（hashing）的内容，并以二次探测法实现了一个简单的HashSet。在本文中，将进一步讨论散列法，尤其是GetHashCode方法的实现，最后给出完整的HashSet实现。</summary><published>2011-06-17T09:19:00Z</published><updated>2011-06-17T09:19:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2011/06/17/hashset-part2.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2011/06/17/hashset-part2.html"/><content type="html">&lt;p&gt;在&lt;a href="http://www.cnblogs.com/anderslly/archive/2011/06/13/hashset-part1.html"&gt;HashSet的实现（上）&lt;/a&gt;中，简要介绍了&lt;b&gt;散列法&lt;/b&gt;（hashing）的内容，并以二次探测法实现了一个简单的HashSet。在本文中，将进一步讨论散列法，尤其是GetHashCode方法的实现，最后给出完整的HashSet实现。&lt;/p&gt;&lt;p&gt;&lt;b&gt;散列法再议&lt;/b&gt;&lt;/p&gt;&lt;p&gt;通过散列法实现的容器，不管是HashSet、Hashtable还是Dictionary，需要支持的基本操作是insert、remove和find，特别是insert和find，三个操作的时间复杂度期望是O(1)。&lt;/p&gt;&lt;p&gt;散列法的使用过程中，主要有两个问题：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;散列函数的实现；&lt;/li&gt;&lt;li&gt;冲突的解决办法；&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;下面将在上篇的基础上进一步讨论这两个问题的解决方案。&lt;/p&gt;&lt;p&gt;&lt;b&gt;散列函数的实现&lt;/b&gt;&lt;/p&gt;&lt;p&gt;一般情况下，要存储的元素（key/k）类型可能有多种，可能取值的集合为K，它的个数为&lt;img src="http://latex.codecogs.com/gif.latex?\left&amp;amp;space;|&amp;amp;space;K&amp;amp;space;\right&amp;amp;space;|" height="19" width="23" /&gt;，实际需要insert的个数为n。现在要把这些元素存放在大小为M的数组内，这样我们就需要一个函数h:K -&amp;gt; {0, 1, &amp;hellip;, M-1}，它可以将元素k映射到数组的一个有效下标，这个函数就是所谓的散列函数（hashfunction），可以记为h(k,M)。一个好的散列函数需要具备如下特性：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;容易计算（主要是指散列函数的时间复杂度为常数时间）。&lt;/li&gt;&lt;li&gt;将元素均匀地映射到数组中，减少冲突的可能；&lt;/li&gt;&lt;/ol&gt;&lt;ul&gt;&lt;/ul&gt;&lt;p&gt;进一步，可以将h(k,M)视为如下两个函数f、g的复合函数：&lt;/p&gt;&lt;p&gt;&lt;img src="http://latex.codecogs.com/gif.latex?\begin{array}{lcl}&amp;amp;space;f:K&amp;amp;space;-&amp;gt;&amp;amp;space;\mathbb{Z}&amp;amp;space;\\&amp;amp;space;g:\mathbb{Z}&amp;amp;space;\rightarrow&amp;amp;space;\left&amp;amp;space;\{&amp;amp;space;{0,1,...,M-1}&amp;amp;space;\right&amp;amp;space;\}&amp;amp;space;\\&amp;amp;space;h&amp;amp;space;=&amp;amp;space;g&amp;amp;space;\circ&amp;amp;space;f&amp;amp;space;\end{array}" height="61" width="189" /&gt;&lt;/p&gt;&lt;p&gt;f和g对h需要具备的两个特性，都会有所影响。&lt;/p&gt;&lt;p&gt;g将f返回的一个int值映射到{0, 1, &amp;hellip;, M-1}，常规方法是&lt;strong&gt;除法散列法&lt;/strong&gt;（DivisionMethod），即g(k)= k % M。在应用除法散列法时，需要主要M值的选取。比如，M不应是2的幂，如果M=&lt;img src="http://latex.codecogs.com/gif.latex?2^p" height="13" width="15" /&gt;，则 k % M的值就是k的二进制表示的p个最低位数字，要满足特性2，可能就会有问题了。可以选做M的值常常是与2的幂不太接近的素数。除了除法散列法，还可以选择&lt;strong&gt;乘法散列法&lt;/strong&gt;（Multiplication Method）： &lt;img src="http://latex.codecogs.com/gif.latex?g(k)&amp;amp;space;=&amp;amp;space;\left&amp;amp;space;\lfloor&amp;amp;space;M(kA\,\bmod\,1)&amp;amp;space;\right&amp;amp;space;\rfloor" height="19" width="186" /&gt;。可分为两步，第一步，用k乘上常数A（0&amp;lt;A&amp;lt;1），取出kA的小数部分，即kA mod 1；第二步，用M乘以小数部分，取其下取整。&lt;/p&gt;&lt;p&gt;相比较而言，f的实现要复杂很多。首先，用作key的值的类型多种多样，包括各种自定义类型，另一方面要考虑h的两个特性。大体上，可以将f对应于object.GetHashCode()方法。默认情况下，每个类型从object（引用类型）或ValueType（值类型）继承了一个GetHashCode的实现。如果我们将h的两个特性与特定的.NET平台结合起来考虑，实现GetHashCode()的时候需要如下几个规则（rule）：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;计算简单；&lt;/li&gt;&lt;li&gt;产生的int值应当是均匀分布的；&lt;/li&gt;&lt;li&gt;如果两个对象相等（由==操作符确定），它们必须产生相同的hashCode；&lt;/li&gt;&lt;li&gt;对于任意一个对象o，o.GetHashCode()的返回值应当是一个实例不变量，即总是返回相同的值。&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;接下来就来详细地讨论如何实现GetHashCode()方法。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;实现GetHashCode()方法&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;既然每个类型都从object（引用类型）或ValueType（值类型）继承了一个GetHashCode的实现，那还有必要自己实现吗？可以确定的是，没有一种完美的实现可以应用于所有类型，否则一个object.GetHashCode()方法就够了。因为object和ValueType对其派生类的可能信息一无所知，所以它们只能提供一个&lt;strong&gt;可用的实现，而未必是最佳的&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;对于引用类型来说，它继承了object.GetHashCode()，该方法返回在AppDomain内可以唯一标识该对象的值，在对象的整个生命周期内，该值是不变的，但经过垃圾收集之后，这个值可以复用到其它的对象。这个值也等于RuntimeHelpers.GetHashCode()方法的返回值，即使我们为某个类型提供了自己的GetHashCode()实现，RuntimeHelpers.GetHashCode()方法的返回值仍然是那个&lt;strong&gt;默认的值&lt;/strong&gt;。&lt;/p&gt;&lt;p&gt;对于引用类型，默认的==会检查两个对象的引用（identity），如果相等，必然会返回相等的值；而一旦重写了==操作符，就需要同时重写GetHashCode，否则编译器会警告你的。现在看一下上面的f要满足的4个规则，object.GetHashCode()可以满足1、3、4，但规则2未必能满足，它产生的值倾向于使用int的较低位（&lt;strong&gt;这个结论来自《Effective C#》，未验证&lt;/strong&gt;），所以说，object.GetHashCode()是可用的，但可能会在使用Hash容器时出现性能问题。当你创建了新的引用类型，并确定会用做Hash容器的key的话，最好重写GetHashCode方法。&lt;/p&gt;&lt;p&gt;对于值类型来说，它继承了ValueType.GetHashCode()，该方法用到了反射，所以比较慢。关于具体的实现，《CLR via C#》中说是对类型的某些实例字段做了XOR操作，《Effective C#》中说是返回了第一个字段的GetHashCode()。经过测试，应该说&lt;strong&gt;ValueType.GetHashCode()是由第一个字段的GetHashCode()决定的，但不相等&lt;/strong&gt;。ValueType的问题要比object更严重。对于规则1，上面说过，由于反射会比较慢；规则2，说一个极端点儿的情况，如果第一个字段是bool类型的，那会出现怎样的悲剧？；规则3，还是一样，如果重写了==，必须要同时重写GetHashCode()。&lt;/p&gt;&lt;p&gt;对于规则4，就很可能有问题了。 对于一个值类型的实例，它的hashCode取决于第一个字段，如果第一个字段是可修改的，一旦修改，hashCode的值也就改变了。当我们把key加入到Hash容器时它的hashCode是h1，如果后来改成h2，就意味着原来的实例找不到了。。。&lt;/p&gt;&lt;p&gt;总结下来，object.GetHashCode()对引用类型来说是一个正确的实现，但不一定会产生均匀分布的hashCode，GetHashCode必须与==同时重写；ValueType.GetHashCode()较慢，而且仅在第一个字段只读、能提供均匀分布的hashCode时可用。如果在计算hashCode时用到了3个字段，这3个字段应该都要参与到==的实现里；参与到hashCode计算的字段或属性最好设置为只读的。&lt;/p&gt;&lt;p&gt;最好还是看看在.NET Framework中GetHashCode是怎么实现的。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;FCL中GetHashCode的实现&lt;/strong&gt;&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #008080;"&gt; 1&lt;/span&gt; &lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; 32位的int&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 2&lt;/span&gt; &lt;span style="color: #008000;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;override&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; GetHashCode()&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 3&lt;/span&gt; &lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 4&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 5&lt;/span&gt; &lt;span style="color: #000000;"&gt;}&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 6&lt;/span&gt; &lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 7&lt;/span&gt; &lt;span style="color: #000000;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; 16位的char&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 8&lt;/span&gt; &lt;span style="color: #008000;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;override&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; GetHashCode()&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 9&lt;/span&gt; &lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;10&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt;)(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;|&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;16&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;11&lt;/span&gt; &lt;span style="color: #000000;"&gt;}&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;12&lt;/span&gt; &lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;13&lt;/span&gt; &lt;span style="color: #000000;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; 64位的long&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;14&lt;/span&gt; &lt;span style="color: #008000;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;override&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; GetHashCode()&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;15&lt;/span&gt; &lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;16&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;^&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt;)((&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;32&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;17&lt;/span&gt; &lt;span style="color: #000000;"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;整数类型的实现是最简单的，需要在int内均匀分布，所以像char这样的，要补足32位（通过&amp;lt;&amp;lt;），同时要用到所有的位（像long这样的实现，会用到64位的每一位）。对于复合的值类型，可考虑如下方法：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #008080;"&gt;1&lt;/span&gt; &lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; 值类型：System.Drawing.Point&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;2&lt;/span&gt; &lt;span style="color: #008000;"&gt;&amp;nbsp;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;override&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; GetHashCode()&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;3&lt;/span&gt; &lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;4&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt;.x &lt;/span&gt;&lt;span style="color: #000000;"&gt;^&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;this&lt;/span&gt;&lt;span style="color: #000000;"&gt;.y;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;5&lt;/span&gt; &lt;span style="color: #000000;"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;对若干字段进行^操作。&lt;/p&gt;&lt;p&gt;对于最常用作key的string类型，它的GetHashCode实现比较复杂，具体的分析可以看&lt;a target="_blank" href="http://www.dotnetperls.com/gethashcode"&gt;这里&lt;/a&gt;。唔，关于散列函数h的讨论就先到这里了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;冲突的解决办法&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;上篇曾介绍过两种方法：&lt;strong&gt;线性探测法&lt;/strong&gt;和&lt;strong&gt;二次探测法&lt;/strong&gt;，它们和更好的&lt;strong&gt;双重散列法&lt;/strong&gt;都属于&lt;strong&gt;开放寻址法&lt;/strong&gt;的范畴，只是探测序列的分布有所不同。在开放寻址法中，所有的元素（key）都放在散列数组本身中，插入新元素时，通过探测序列找到它的合适位置。&lt;/p&gt;&lt;p&gt;而在&lt;strong&gt;分离链接法&lt;/strong&gt;中，把散列到同一槽（slot）的元素都放在一个链表中，这样散列数组存放的只是相对应链表的指针而已，借助于链表，分离链接法实现起来其实更简单，它不需要花费很多心思在那个探测序列的选择上。&lt;/p&gt;&lt;p&gt;可以看到，要对散列法、散列函数有全面的认识，为类型提供良好的GetHashCode()实现都不是容易的事情。最好的办法还是借助于ILSpy这样的反编译工具去看一下FCL中HashSet&amp;lt;T&amp;gt;、Dictionary&amp;lt;K, V&amp;gt;和Hashtable的实现，园子里有些这方面的文章。关于Hash，就到这里，余下的部分将讨论ISet的实现。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;ISet接口简介&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/12089/2011061716553298.jpg" /&gt;&lt;/p&gt;&lt;p&gt;ISet&amp;lt;T&amp;gt;继承了IEnumerable&amp;lt;T&amp;gt;和ICollection&amp;lt;T&amp;gt;，前面讨论过的insert、find、remove操作对应ICollection&amp;lt;T&amp;gt;的Add、Contains和Remove方法。ISet&amp;lt;T&amp;gt;本身的方法几乎都是（除了Add）关于两个集合间的操作的，比如求交集、并集、差集，判断是否是另一集合的子集等等，这些实现起来不难，差不多就是把集合操作的定义搬过来就行了，这里就不再赘述了。&lt;/p&gt;&lt;p&gt;根据《数据结构与问题求解Java语言描述》和FCL的HashSet&amp;lt;T&amp;gt;代码写了一个简单的&lt;a target="_blank" href="http://files.cnblogs.com/anderslly/HashSet.zip"&gt;HashSet实现&lt;/a&gt;，没有经过严格的测试，可以当作伪代码来参考一下:) &lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;《&lt;a target="_blank" href="http://book.douban.com/subject/1866825/"&gt;数据结构与问题求解&lt;/a&gt;》&lt;br /&gt;《&lt;a target="_blank" href="http://book.douban.com/subject/1885170/"&gt;算法导论&lt;/a&gt;》&lt;br /&gt;《&lt;a target="_blank" href="http://book.douban.com/subject/1547071/"&gt;CLR via C#&lt;/a&gt;》&lt;br /&gt;《&lt;a target="_blank" href="http://book.douban.com/subject/4741796/"&gt;Effective C#&lt;/a&gt;》&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Hash_function"&gt;Hash Function&lt;/a&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Hash_table"&gt;Hash Table&lt;/a&gt;&lt;br /&gt;&lt;a target="_blank" href="http://www.dotnetperls.com/gethashcode"&gt;string.GetHashCode()&lt;/a&gt;&lt;br /&gt;&lt;a target="_blank" href="http://wiki.sharpdevelop.net/ILSpy.ashx"&gt;ILSpy&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/2083451.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2011/06/17/hashset-part2.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2011/06/13/hashset-part1.html</id><title type="text">HashSet的实现（上）</title><summary type="text">本文主要讨论了散列法和散列函数的基本概念以及解决散列冲突的两种方法：线性探测法和二次探测法，并以二次探测法简单地实现了HashSet的Hash这一部分。</summary><published>2011-06-12T18:03:00Z</published><updated>2011-06-12T18:03:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2011/06/13/hashset-part1.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2011/06/13/hashset-part1.html"/><content type="html">&lt;p&gt;要理解HashSet，可以按字面上把它分解为两部分，一方面它表示一个集合（Set），另一方面，它的实现使用了散列法（Hashing）。&lt;/p&gt;&lt;p&gt;&lt;b&gt;集合（&lt;/b&gt;&lt;b&gt;Set&lt;/b&gt;&lt;b&gt;）&lt;/b&gt;&lt;/p&gt;&lt;p&gt;还记得吗？在中学里曾经学过，集合是某些指定对象的全体，集合的三个性质是确定性、互异性和无序性。本文提到的集合正是这个数学概念在计算机中的实现。&lt;/p&gt;&lt;p&gt;说到集合，可能你会想到Collection这个词，以及.NET中的ICollection&amp;lt;T&amp;gt;接口，它们通常也被解释为&amp;ldquo;集合&amp;rdquo;，不过它们和Set有明显的不同。&lt;a href="http://en.wikipedia.org/wiki/Collection_(computing)"&gt;Collection&lt;/a&gt;可以看作是一组数据项的&lt;b&gt;容器（&lt;/b&gt;&lt;b&gt;&lt;a href="http://en.wikipedia.org/wiki/Container_(data_structure)"&gt;Container&lt;/a&gt;&lt;/b&gt;&lt;b&gt;）&lt;/b&gt;，它与数学中的集合概念无关。同时，&lt;a href="http://en.wikipedia.org/wiki/Set_(computer_science)"&gt;Set&lt;/a&gt;是一种特殊的容器。也就是说，Set的概念更强一些。在本文余下的部分，用集合表示Set，容器表示Collection和Container。&lt;/p&gt;&lt;p&gt;这里比较值得关注的是互异性，在开发中有些时候需要这样的一个容器。我们经常使用的数组和List&amp;lt;T&amp;gt;本身不能满足该性质。.NET3.5之前没有一个类型是直接提供这种支持的，一种方案是采用Hashtable或Dictionary，在存储数据项的时候，只使用key，value忽略。.NET3.5（以及以上）提供了HashSet&amp;lt;T&amp;gt;泛型类，终于可以满足集合的要求了。&lt;/p&gt;&lt;p&gt;集合作为一种数据结构，它的主要操作很简单，最重要的是&lt;b&gt;添加和移除元素，以及查询&lt;/b&gt;（判断某个值是否在集合中，枚举集合中的所有元素）。要实现一个集合类型，首先可以使用简单的数组，但是数组元素查询的时间复杂度是O(n)；比数组好得多的另一个选择是二叉搜索树，它本身就提供了上述的三个操作，而且速度都很快：O(logn)，这种实现一般称为TreeSet。二叉搜索树在操作的时候需要维护元素之间的相对顺序，而集合对顺序是没有要求的，是否可以提供更好的时间复杂度呢？&lt;/p&gt;&lt;p&gt;答案就是HashSet，相关的方法是散列法（Hashing），这也是本文的主要内容。关于集合更详细的讨论（泛型接口ISet&amp;lt;T&amp;gt;，集合之间的操作，如交集、并集等）将在下篇中进行。&lt;/p&gt;&lt;p&gt;&lt;b&gt;散列法&lt;/b&gt;&lt;/p&gt;&lt;p&gt;现在再来明确一下目标。我们要实现的Set类要满足元素的互异性，但不关心元素的顺序如何；要支持的操作是&lt;b&gt;添加&lt;/b&gt;&lt;b&gt;(insert)&lt;/b&gt;元素、&lt;b&gt;移除&lt;/b&gt;&lt;b&gt;(remove)&lt;/b&gt;元素、&lt;b&gt;查询&lt;/b&gt;&lt;b&gt;(find)&lt;/b&gt;（判断某个值是否在集合中）；每种操作的时间复杂度要比O(logn)好。&lt;/p&gt;&lt;p&gt;对于数组来说，如果知道了下标，那么访问该元素的时间复杂度为O(1)，现在看能否利用这一点。考虑一种简单情况，我们要处理的集合包含若干小的整数，范围在0到99之间，并且事先可以确定集合的元素个数不会超过100。此时，可以创建一个长度为100的int数组a，其元素全部初始化为0。对于一个数i，要完成insert(i)操作，只要执行a[i] = 1；对于remove(i)操作，只需要a[i] = 0；find(i)操作，可以返回a[i] == 1（或a[i] &amp;gt; 0）。&lt;/p&gt;&lt;p&gt;三种操作的时间复杂度都是O(1)，实现起来也很简单，看起来不错。不过它有两个明显的问题，如果要保存的整数不是一个小范围内的值，而是所有int，那么&lt;b&gt;数组&lt;/b&gt;&lt;b&gt;a&lt;/b&gt;&lt;b&gt;的长度要超过&lt;/b&gt;&lt;b&gt;40&lt;/b&gt;&lt;b&gt;亿&lt;/b&gt;，太不现实；另外，我们要实现的是泛型类，可以容纳各种类型的值，如果不是int，而是string，&lt;b&gt;元素本身的值不能用作索引&lt;/b&gt;。&lt;/p&gt;&lt;p&gt;上面的两个问题也是散列法的两个核心问题。先来看第二个问题：对于非整型的值，如何获得它对应的数组下标。以string为例，假设它包含的字符都是ASCII字符，对应的int值范围是0-127。对于&amp;ldquo;junk&amp;rdquo;，可以这样来计算：'j'*128^3 + 'u'*128^2 + 'n'*128^1 + 'k'*128^0，这样就把一个string值转换为int值了。不过这种方法会产生巨大的整数，对于&amp;ldquo;junk&amp;rdquo;这样简单的字符串，产生的值就高达224229227，可以想见，对于复杂一点儿的字符串将是不能接受的。于是又回到了&lt;b&gt;上段中的第一个问题，如何避免使用过于巨大的数组&lt;/b&gt;？&lt;/p&gt;&lt;p&gt;如果集合的元素是int类型，虽然int的可能值超过40亿，但是在一个特定的问题中，集合的元素个数却可能很有限。如果元素个数不超过1000，那么只需要一个长度为1000的数组就够了，如果一个值超过了数组下标的上限，可以通过一个函数将该值映射到0-999，这样的函数称为散列函数（hash function）。&lt;/p&gt;&lt;p&gt;&lt;b&gt;散列函数&lt;/b&gt;&lt;/p&gt;&lt;p&gt;根据上面的分析，对于一个集合S，用来存放元素的数组长度为tableSize，其中的任一元素e，散列函数h将e映射到0到tableSize之间的一个整数（即数组的一个有效索引），可以记为：&lt;/p&gt;&lt;p&gt;index = h(e, tableSize)&lt;/p&gt;&lt;p&gt;首先针对元素e，根据某种特定的方法计算出它的int值hashCode，然后返回hashCode % tableSize，这样返回值就会落在0到tableSize-1之间。显然，在散列函数的实现中，最重要的部分就是计算hashCode。你也许已经想到了一个方法，那就是object.GetHashCode()，这里需要的正是它。该方法是virtual的，这样它的每个子类都可以提供自己的实现。比如下面的例子：&lt;/p&gt;&lt;p&gt;&lt;img height="186" width="242" src="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130126286584.png" alt="hash-function" border="0" title="hash-function" style="display: inline; border: 0px;" /&gt; &lt;/p&gt;&lt;p&gt;（图片来自&lt;a href="http://en.wikipedia.org/wiki/Hash_function"&gt;wikipedia&lt;/a&gt;，将string类型的值映射到tableSize为16的数组上。）&lt;/p&gt;&lt;p&gt;前面提到过，很多情况下，元素e的取值范围要比tableSize大得多，这样就有可能遇到一种情况，对于不同的两个元素e1和e2，有h(e1, tableSize) == h(e2, tableSize)，此时它们被映射到同一个数组索引，这种复杂的情况称为&lt;b&gt;冲突&lt;/b&gt;（&lt;b&gt;collision&lt;/b&gt;），在本文后面会讨论如何解决冲突问题。&lt;/p&gt;&lt;p&gt;一般地，一个好的散列函数需要满足：&lt;/p&gt;&lt;ul&gt;&lt;li&gt;容易计算（主要是指散列函数的时间复杂度为常数时间）。 &lt;/li&gt;&lt;li&gt;将元素均匀地映射到数组中，减少冲突的可能； &lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果元素类型是基元类型，可以使用它们内置的GetHashCode实现，对于自定义类型的实现，将会在下篇中进行讨论。&lt;/p&gt;&lt;p&gt;&lt;b&gt;当冲突发生时&lt;/b&gt;&lt;/p&gt;&lt;p&gt;现在我们已经有了散列函数，接下来需要确定当冲突发生时如何化解它。具体来说，如果一个元素e，经过散列函数求值，被散列到的位置已经被另一个元素占据，那么e应该放在哪里？&lt;/p&gt;&lt;p&gt;最简单的方法是，&lt;strong&gt;下一个位置&lt;/strong&gt;。在数组中顺序搜索，直到找到一个空位置。为利用数组的所有空位置，搜索进行到数组尾部时，可以绕回到第一个位置。这种方法就是&lt;b&gt;线性探测法&lt;/b&gt;（linear probing）。&lt;/p&gt;&lt;p&gt;考虑一个例子，数组长度tableSize = 10，要添加的元素是89、18、49、58和9。散列函数的返回结果为：&lt;/p&gt;&lt;p&gt;hash(89, 10) &amp;rarr; 9    &lt;br /&gt;hash(18, 10) &amp;rarr; 8     &lt;br /&gt;hash(49, 10) &amp;rarr; 9     &lt;br /&gt;hash(58, 10) &amp;rarr; 8     &lt;br /&gt;hash(9, 10) &amp;rarr; 9&lt;/p&gt;&lt;table width="441" cellpadding="2" cellspacing="0" border="1"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;&lt;p&gt;索引&lt;/p&gt;&lt;/td&gt;&lt;td width="81" valign="top"&gt;&lt;p&gt;insert(89)&lt;/p&gt;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&lt;p&gt;insert(18)&lt;/p&gt;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&lt;p&gt;insert(49)&lt;/p&gt;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&lt;p&gt;insert(58)&lt;/p&gt;&lt;/td&gt;&lt;td width="70" valign="top"&gt;&lt;p&gt;insert(9)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;0&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;49&lt;/td&gt;&lt;td width="77" valign="top"&gt;49&lt;/td&gt;&lt;td width="71" valign="top"&gt;49&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;1&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="77" valign="top"&gt;58&lt;/td&gt;&lt;td width="72" valign="top"&gt;58&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;2&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="73" valign="top"&gt;9&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;3&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="73" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;4&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="73" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;5&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="73" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;6&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="73" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;7&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="82" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="73" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;8&lt;/td&gt;&lt;td width="81" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;18&lt;/td&gt;&lt;td width="82" valign="top"&gt;18&lt;/td&gt;&lt;td width="77" valign="top"&gt;18&lt;/td&gt;&lt;td width="73" valign="top"&gt;18&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;9&lt;/td&gt;&lt;td width="81" valign="top"&gt;89&lt;/td&gt;&lt;td width="80" valign="top"&gt;89&lt;/td&gt;&lt;td width="82" valign="top"&gt;89&lt;/td&gt;&lt;td width="77" valign="top"&gt;89&lt;/td&gt;&lt;td width="74" valign="top"&gt;89&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;在添加89和18时，没有冲突。添加49时，与89冲突，寻找下一个空位置，由于到达数组尾部，绕回，在索引0处存放49；接下来的58和9与此类似。只要数组足够大，就可以找到一个空位置。但问题是，&amp;ldquo;探测&amp;rdquo;的时间可能会很长，如果只有一个空位置，那么要搜遍整个数组才能找到，平均下来要搜索半个数组，这与我们的每个操作都是常数时间的期望已经相去甚远了。&lt;/p&gt;&lt;p&gt;再来看find操作，它的探测路径与insert相同。要找58，从索引8（hash的返回值）开始，这个位置有一个元素，但不是58；继续向下看索引9，仍然不是，直到索引1，找到了匹配的元素。要找19，从索引9开始，历经9、0、1、2，最后到了一个空位置3，这说明数组中没有与之匹配的项。&lt;/p&gt;&lt;p&gt;最后是remove操作，不能直接将元素删除（将该位置置空）。从上面分析可以看到，insert和find操作都需要一条探测路径，如果直接删除，就会打断这条路径，从而使find操作失效。如果把89直接删除，那么49、58和9就都找不到了。&lt;/p&gt;&lt;p&gt;&lt;b&gt;线性探测法的简单分析&lt;/b&gt;&lt;/p&gt;&lt;p&gt;在上面的例子中，向空的数组添加元素时没有冲突，到后来冲突越来越多，这也是符合我们的直觉的。公交车上越拥挤，冲突发生的可能性就越大。在散列法中有一个专门的术语：&lt;b&gt;负载因子（&lt;/b&gt;&lt;b&gt;load factor&lt;/b&gt;&lt;b&gt;）&lt;/b&gt;。探测散列表的&lt;b&gt;负载因子&lt;/b&gt;&lt;b&gt;&amp;lambda;&lt;/b&gt;&lt;b&gt;是表被装满的比例&lt;/b&gt;，范围从0（空）到1（满）。&lt;/p&gt;&lt;p&gt;使用线性探测法时，比较容易产生连续被占据的块，如果一个元素被散列到这个块中，就需要连续尝试以找到下一个可用位置（看上面添加9时）。这种连续聚集的块称为&lt;b&gt;初始聚集（&lt;/b&gt;&lt;b&gt;primary clustering&lt;/b&gt;&lt;b&gt;）&lt;/b&gt;。用线性探测法添加项时，需要尝试的平均单元数大约是&lt;img src="http://latex.codecogs.com/gif.latex?(1+1/(1-\lambda )^2)/2" /&gt; 。&lt;/p&gt;&lt;p&gt;如果&amp;lambda;=0.5，尝试次数为2.5，还是可以的；如果&amp;lambda;=0.9，那么尝试次数就多达50次了（这还是平均数，某些情况比这更糟）。&lt;/p&gt;&lt;p&gt;对于find操作来说，可以分为两种情况：不成功的和成功的。不成功的情况与insert操作一样。如果查找成功，需要检查的平均单元数约为&lt;img src="http://latex.codecogs.com/gif.latex?\frac{1}{2}(1+\frac{1}{(1-\lambda )})" /&gt; 。&lt;/p&gt;&lt;p&gt;如果&amp;lambda;=0.5，尝试次数为1.5；如果&amp;lambda;=0.9，尝试次数为5.5。&lt;/p&gt;&lt;p&gt;看来&amp;lambda;=0.5是个不错的选择，&lt;b&gt;平均&lt;/b&gt;探测次数很少，也不会需要大量的空闲位置，而且很容易实现。不过，为了减少探测数，我们需要一种能避免初始聚集的方法，一种选择就是二次探测法。&lt;/p&gt;&lt;p&gt;&lt;b&gt;二次探测法（&lt;/b&gt;&lt;b&gt;quadratic probing&lt;/b&gt;&lt;b&gt;）&lt;/b&gt;&lt;/p&gt;&lt;p&gt;在线性探测法中，当发现冲突产生时，我们会从冲突位置开始顺序探测下一个可用位置，这正是产生初始聚集的原因。如果散列函数hash的返回值为h，那么探测的位置将依次是h + 1，h + 2，...，h + i。探测的位置连续，当然就容易产生聚集了。既然这样，就不要探测连续的位置了，记为&lt;img src="http://latex.codecogs.com/gif.latex?f(i) = i^2" /&gt; ，现在改为依次探测h + f(1)，h + f(2)，...，h + f(i)。下面来看二次探测法的使用，还是使用前面的那个例子。&lt;/p&gt;&lt;p&gt;hash(89, 10) &amp;rarr; 9    &lt;br /&gt;hash(18, 10) &amp;rarr; 8     &lt;br /&gt;hash(49, 10) &amp;rarr; 9     &lt;br /&gt;hash(58, 10) &amp;rarr; 8     &lt;br /&gt;hash(9, 10) &amp;rarr; 9&lt;/p&gt;&lt;table width="448" cellpadding="2" cellspacing="0" border="1"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;&lt;p&gt;索引&lt;/p&gt;&lt;/td&gt;&lt;td width="77" valign="top"&gt;&lt;p&gt;insert(89)&lt;/p&gt;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&lt;p&gt;insert(18)&lt;/p&gt;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&lt;p&gt;insert(49)&lt;/p&gt;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&lt;p&gt;insert(58)&lt;/p&gt;&lt;/td&gt;&lt;td width="84" valign="top"&gt;&lt;p&gt;insert(9)&lt;/p&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;0&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;49&lt;/td&gt;&lt;td width="78" valign="top"&gt;49&lt;/td&gt;&lt;td width="84" valign="top"&gt;49&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;1&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="84" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;2&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;58&lt;/td&gt;&lt;td width="84" valign="top"&gt;58&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;3&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="84" valign="top"&gt;9&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;4&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="84" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;5&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="84" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;6&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="84" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;7&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="80" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="84" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;8&lt;/td&gt;&lt;td width="77" valign="top"&gt;&amp;nbsp;&lt;/td&gt;&lt;td width="78" valign="top"&gt;18&lt;/td&gt;&lt;td width="80" valign="top"&gt;18&lt;/td&gt;&lt;td width="78" valign="top"&gt;18&lt;/td&gt;&lt;td width="84" valign="top"&gt;18&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td width="49" valign="top"&gt;9&lt;/td&gt;&lt;td width="77" valign="top"&gt;89&lt;/td&gt;&lt;td width="78" valign="top"&gt;89&lt;/td&gt;&lt;td width="80" valign="top"&gt;89&lt;/td&gt;&lt;td width="78" valign="top"&gt;89&lt;/td&gt;&lt;td width="84" valign="top"&gt;89&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;在添加49时，与89冲突，49的最终位置是(9 + 1) % 10 = 0；添加58时，与18冲突，而(8 + 1)位置与89冲突，所以58的最终位置是(8 + 2^2) % 10 = 2；同理，9的位置是3。这里有个相当重要的地方是，hash值为8的元素和hash值为9的元素探测路径不同，添加58不会影响到后面添加9，这是比线性探测好的地方。&lt;/p&gt;&lt;p&gt;在编码之前，我们再来考虑几个问题，这几个问题解决后，你会发现二次探测法的实现灰常简单。这几个问题是：&lt;/p&gt;&lt;ol&gt;&lt;li&gt;&lt;p&gt;使用线性探测法，虽然它可能会比较慢，但它可以保证的是，只要数组不满，就可以插入新的值，二次探测法也可以做到这一点吗？另外，探测路径中的位置会不会被重复探测到？&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;线性探测法实现只需要简单的计算，二次探测法则需要乘法和模运算，这会不会造成太大的负担？&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;如果&amp;lambda;过高怎么办？能否动态扩展数组？&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;对于问题1，如果tableSize为素数，而且&amp;lambda;不超过0.5，就可以解决，即下面的定理：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;定理1&lt;/strong&gt;：如果采用二次探测法并且表（数组）的大小是素数，表至少有一半是空的（即&amp;lambda; &amp;lt;= 0.5），那么新的元素总能插入，插入过程中没有一个位置会被探测两次。&lt;/p&gt;&lt;p&gt;证明：设表的大小为M，M为大于3的奇素数。现在考虑探测序列的前&lt;img height="19" width="47" src="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130137439409.gif" alt="ceil-M div 2" border="0" title="ceil-M div 2" style="display: inline; border: 0px;" /&gt; 项（包括初始项），可以证明它们彼此是不同的。这里使用反证法。假设这些项的某两个项是&lt;img height="20" width="131" src="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130137435506.gif" alt="h-i2-mod-M" border="0" title="h-i2-mod-M" style="display: inline; border: 0px;" /&gt;和&lt;img height="20" width="134" src="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130137439966.gif" alt="h-j2-mod-M" border="0" title="h-j2-mod-M" style="display: inline; border: 0px;" /&gt;，其中&lt;img src="http://latex.codecogs.com/gif.latex?0 \leqslant i, j \leqslant \lceil M/2 \rceil" /&gt; 。若这两项相同且&lt;img src="http://latex.codecogs.com/gif.latex?i \neq j" /&gt; ，那么&lt;/p&gt;&lt;p&gt;&lt;img height="86" width="279" src="http://latex.codecogs.com/gif.latex?\begin{array}{lcl}&amp;amp;space;H&amp;amp;plus;i^2&amp;amp;space;&amp;amp;&amp;amp;space;\equiv&amp;amp;space;&amp;amp;&amp;amp;space;H&amp;amp;plus;j^2&amp;amp;space;\pmod&amp;amp;space;M&amp;amp;space;\\&amp;amp;space;i^2&amp;amp;space;&amp;amp;&amp;amp;space;\equiv&amp;amp;space;&amp;amp;&amp;amp;space;j^2&amp;amp;space;\pmod&amp;amp;space;M&amp;amp;space;\\&amp;amp;space;i^2-j^2&amp;amp;space;&amp;amp;&amp;amp;space;\equiv&amp;amp;space;&amp;amp;&amp;amp;space;0&amp;amp;space;\pmod&amp;amp;space;M&amp;amp;space;\\&amp;amp;space;(i-j)(i&amp;amp;plus;j)&amp;amp;space;&amp;amp;&amp;amp;space;\equiv&amp;amp;space;&amp;amp;&amp;amp;space;0&amp;amp;space;\pmod&amp;amp;space;M&amp;amp;space;\end{array}" /&gt;&lt;/p&gt;&lt;p&gt;因为M是素数，所以i-j或i+j可以被M整除，因为i和j不同且它们的和小于M，这两种情况都不可能。可以得出，这两项是不同的，由于&amp;lambda;&amp;gt;0.5（M为奇数），所以这&lt;img height="19" width="47" src="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130137439409.gif" alt="ceil-M div 2" border="0" title="ceil-M div 2" style="display: inline; border: 0px;" /&gt;项中必然有一项是空位置，这样元素总能插入，且不会有位置被探测两次。&lt;/p&gt;&lt;p&gt;对于问题2，线性探测法需要的计算需要一次简单的加法（加1）、一个确定是否需要绕回表头的测试，一个很少出现的减法（返回表头）。二次探测法需要做一次加法（i &amp;ndash; 1到i）、一次乘法（i * i）、一次加法和一次取模，看起来开销很大。通过下面的定理：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;定理2&lt;/strong&gt;：不需要昂贵的乘法和取模就能实现二次探测法。&lt;/p&gt;&lt;p&gt;简单说一下证明。探测序列中的相邻两项是&lt;img src="http://latex.codecogs.com/gif.latex?H_i" /&gt; 和&lt;img src="http://latex.codecogs.com/gif.latex?H_{i-1}" /&gt; ，那么&lt;img height="19" width="229" src="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130138247672.gif" alt="hi-hi-1-relation" border="0" title="hi-hi-1-relation" style="display: inline; border: 0px;" /&gt; 。&lt;/p&gt;&lt;p&gt;问题3的出现是因为，要解决问题1，我们需要保证&amp;lambda;不超过0.5，随着元素的增加，需要动态扩展数组，这个过程称为&lt;strong&gt;重散列（rehashing）&lt;/strong&gt;。扩展为新的数组后，不能简单的将元素拷贝过去，因为新的tableSize意味着新的散列函数，这里的做法是重新检查原有数组的每个元素，将它们添加到新数组中。&lt;/p&gt;&lt;p&gt;现在可以来考虑HashSet的具体实现了，首先来看HashSet的Hash部分，通过散列法实现Set的三种基本操作。HashSet的类图大致如下：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130124197963.jpg"&gt;&lt;img height="520" width="235" src="http://images.cnblogs.com/cnblogs_com/anderslly/201106/201106130124194616.jpg" alt="hashSetClassDiagram" border="0" title="hashSetClassDiagram" style="display: inline; border: 0px;" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;这里通过嵌套类HashEntry来存放集合中的值，前面提到过，不能够直接删除某个项，而是通过一个IsActive属性进行标识。对于主要的三个操作Add（insert）、Remove和Contains（find）来说，它们都需要一个探测序列来找到insert的位置或是查询是否包含某个值。这个类似的探测过程都放在了FindPos方法中：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #008080;"&gt; 1&lt;/span&gt; &lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; FindPos(T item)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 2&lt;/span&gt; &lt;span style="color: #000000;"&gt;{&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 3&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; collisionNum &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 4&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; pos &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; (item.IsNull()) &lt;/span&gt;&lt;span style="color: #000000;"&gt;?&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt; : Math.Abs(item.GetHashCode() &lt;/span&gt;&lt;span style="color: #000000;"&gt;%&lt;/span&gt;&lt;span style="color: #000000;"&gt; array.Length);&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 5&lt;/span&gt; &lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 6&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;while&lt;/span&gt;&lt;span style="color: #000000;"&gt; (array[pos].IsNotNull())&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 7&lt;/span&gt; &lt;span style="color: #000000;"&gt;    {&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 8&lt;/span&gt; &lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (item.IsNull())&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt; 9&lt;/span&gt; &lt;span style="color: #000000;"&gt;        {&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;10&lt;/span&gt; &lt;span style="color: #000000;"&gt;            &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (array[pos].Item.IsNull())&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;11&lt;/span&gt; &lt;span style="color: #000000;"&gt;            {&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;12&lt;/span&gt; &lt;span style="color: #000000;"&gt;                &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;break&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;13&lt;/span&gt; &lt;span style="color: #000000;"&gt;            }&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;14&lt;/span&gt; &lt;span style="color: #000000;"&gt;        }&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;15&lt;/span&gt; &lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;else&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (item.Equals(array[pos].Item))&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;16&lt;/span&gt; &lt;span style="color: #000000;"&gt;        {&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;17&lt;/span&gt; &lt;span style="color: #000000;"&gt;            &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;break&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;18&lt;/span&gt; &lt;span style="color: #000000;"&gt;        }&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;19&lt;/span&gt; &lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;20&lt;/span&gt; &lt;span style="color: #000000;"&gt;        pos &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;2&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;*&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;collisionNum) &lt;/span&gt;&lt;span style="color: #000000;"&gt;-&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;21&lt;/span&gt; &lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (pos &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; array.Length)&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;22&lt;/span&gt; &lt;span style="color: #000000;"&gt;        {&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;23&lt;/span&gt; &lt;span style="color: #000000;"&gt;            pos &lt;/span&gt;&lt;span style="color: #000000;"&gt;-=&lt;/span&gt;&lt;span style="color: #000000;"&gt; array.Length;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;24&lt;/span&gt; &lt;span style="color: #000000;"&gt;        }&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;25&lt;/span&gt; &lt;span style="color: #000000;"&gt;    }&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;26&lt;/span&gt; &lt;span style="color: #000000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;27&lt;/span&gt; &lt;span style="color: #000000;"&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; pos;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #008080;"&gt;28&lt;/span&gt; &lt;span style="color: #000000;"&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;第4行获得初始散列值，第20行使用的是定理2中的公式。&lt;p&gt;有了FindPos方法，三个操作的代码都变得简单了：&lt;/p&gt;&lt;div onclick="cnblogs_code_show('8cddc486-ea28-48d7-a7bf-1b00c8e35979')" style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" class="code_img_closed" id="code_img_closed_8cddc486-ea28-48d7-a7bf-1b00c8e35979" /&gt;&lt;img src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" onclick="cnblogs_code_hide('8cddc486-ea28-48d7-a7bf-1b00c8e35979',event)" class="code_img_opened" id="code_img_opened_8cddc486-ea28-48d7-a7bf-1b00c8e35979" style="display: none;" /&gt;&lt;div class="cnblogs_code_hide" id="cnblogs_code_open_8cddc486-ea28-48d7-a7bf-1b00c8e35979"&gt;&lt;div&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;bool&lt;/span&gt;&lt;span style="color: #000000;"&gt; Add(T item)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; pos &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; FindPos(item);&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (IsActive(pos))&lt;br /&gt;    {&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; Item exists, just return.&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;false&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    array[pos] &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;new&lt;/span&gt;&lt;span style="color: #000000;"&gt; HashEntry&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;T&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;(item, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;true&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;    currentSize&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    occupied&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    modCount&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (occupied &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; array.Length &lt;/span&gt;&lt;span style="color: #000000;"&gt;/&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;2&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;    {&lt;br /&gt;        Rehash();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;true&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;bool&lt;/span&gt;&lt;span style="color: #000000;"&gt; Remove(T item)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; pos &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; FindPos(item);&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #000000;"&gt;!&lt;/span&gt;&lt;span style="color: #000000;"&gt;IsActive(pos))&lt;br /&gt;    {&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;false&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    array[pos].IsActive &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;false&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    currentSize&lt;/span&gt;&lt;span style="color: #000000;"&gt;--&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    modCount&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (currentSize &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; array.Length &lt;/span&gt;&lt;span style="color: #000000;"&gt;/&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;8&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;    {&lt;br /&gt;        Rehash();&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;true&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;bool&lt;/span&gt;&lt;span style="color: #000000;"&gt; Contains(T item)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; IsActive(FindPos(item));&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;bool&lt;/span&gt;&lt;span style="color: #000000;"&gt; IsActive(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; pos)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; array[pos].IsNotNull() &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style="color: #000000;"&gt; array[pos].IsActive;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;另外几个需要注意的地方是，默认的tableSize = DEFAULT_TABLE_SIZE = 101，二次探测法要求tableSize为素数。在需要Rehash（扩展和收缩）的时候，也需要保持tableSize为素数。完整的代码在&lt;a href="http://files.cnblogs.com/anderslly/HashSet.zip"&gt;这里&lt;/a&gt;(IsNull和IsNotNull只是两个简单的扩展方法，判断某个变量是否为null)。&lt;p&gt;到这里，我们使用二次探测法实现了HashSet的三个基本操作，不过关于GetHashCode方法和ISet接口的实现，这里还没有提及，这些留在下一篇中进行讨论。&lt;/p&gt;&lt;p&gt;&lt;b&gt;其它冲突解决方法&lt;/b&gt;&lt;/p&gt;&lt;p&gt;还有一种流行的方法是分离链接法（separate chaining）：&lt;/p&gt;&lt;p&gt;&lt;img src="http://pic002.cnblogs.com/images/2011/12089/2011061301445849.png" style="border: 1px solid black;" /&gt;&lt;/p&gt;&lt;p&gt;（图片来自&lt;a href="http://en.wikipedia.org/wiki/Hash_table"&gt;wikipedia&lt;/a&gt;）&lt;/p&gt;&lt;p&gt;如上图，John Smith和Sandra Dee都散列到了152位置，它们对应的两个值将形成一个链表，也就是它们被放在了同一个桶（bucket）里，这样的好处是不会大量的&amp;ldquo;空桶&amp;rdquo;，从而节省了空间。理解了二次探测法，这个也比较容易理解。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;小结&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;本文主要讨论了散列法和散列函数的基本概念以及解决散列冲突的两种方法：线性探测法和二次探测法，并以二次探测法简单地实现了HashSet的Hash这一部分。在下篇中将会讨论GetHashCode方法在自定义类型中的实现以及给HashSet添加对ISet接口的实现，那样才算是一个真正的Set。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;参考&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;《&lt;a target="_blank" href="http://book.douban.com/subject/1866825/"&gt;数据结构与问题求解&lt;/a&gt;》&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Hash_function"&gt;Hash Function&lt;/a&gt;&lt;br /&gt;&lt;a href="http://en.wikipedia.org/wiki/Hash_table"&gt;Hash Table&lt;/a&gt;&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/2079308.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2011/06/13/hashset-part1.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2011/06/03/2070192.html</id><title type="text">趣题一则：如何快速过桥？</title><summary type="text">有四个人打算过桥，他们都在桥的某一端，他们的任务是在17分钟内全部到达桥的另一头。当时是晚上，他们只有一只手电筒，最多只能有两人同时过桥，因为伸手不见五指，必须要携带手电筒。必须步行将手电筒带来带去，即扔来扔去是不行的。每个人走路的速度是不同的：甲过桥需要1分钟，乙需要2分钟，丙需要5分钟，丁需要10分钟。两个人一起走的速度等于其中较慢的人的速度。例如，如果第一次让甲和丁一起过桥，当他们到桥的另一端时，已经用了10分钟，如果由丁带回手电筒，那么20分钟就过去了，于是任务失败。那他们到底该怎样过去呢？如何完成任务？第一次甲和乙同行，然后乙回去送手电筒，用掉4分钟；下一次丙和丁同行，甲回去，用掉1</summary><published>2011-06-02T16:31:00Z</published><updated>2011-06-02T16:31:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2011/06/03/2070192.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2011/06/03/2070192.html"/><content type="html">&lt;p&gt;有四个人打算过桥，他们都在桥的某一端，他们的任务是在17分钟内全部到达桥的另一头。当时是晚上，他们只有一只手电筒，最多只能有两人同时过桥，因为伸手不见五指，必须要携带手电筒。必须步行将手电筒带来带去，即扔来扔去是不行的。每个人走路的速度是不同的：甲过桥需要1分钟，乙需要2分钟，丙需要5分钟，丁需要10分钟。两个人一起走的速度等于其中较慢的人的速度。例如，如果第一次让甲和丁一起过桥，当他们到桥的另一端时，已经用了10分钟，如果由丁带回手电筒，那么20分钟就过去了，于是任务失败。那他们到底该怎样过去呢？&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;" onclick="cnblogs_code_show('3cd7cc11-dc65-420f-acd9-7c4c8fd7e852')"&gt;&lt;div id="cnblogs_code_open_3cd7cc11-dc65-420f-acd9-7c4c8fd7e852" class="cnblogs_code_hide"&gt;&lt;div&gt;&lt;span style="color: #000000;"&gt;第一次甲和乙同行，然后乙回去送手电筒，用掉4分钟；下一次丙和丁同行，甲回去，用掉11分钟；最后甲和乙再次同行，用掉2分钟。任务完成。&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/2070192.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2011/06/03/2070192.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2011/05/03/create-wikipedia-book.html</id><title type="text">制作自己的wikibook</title><summary type="text">wikipedia.org是我特别喜欢的网站，首先是它的信息量极为丰富，不管是文章本身的内容，还是它所引用的外部资源；而且文章的风格也很让人喜欢，简约的文本，配以适量的图片，很对我的口味；另外一个重要的地方是，文章内容比较可靠。如果看到一篇喜欢的wiki文章，想打印出来，那么看页面的左边侧栏： 可以使用Printable版本打印，也可以保存为本地的PDF文件。最有趣的地方是“Create a book”：一旦开始书籍的创建，在页面的页首部分就会出现一个功能区域： 当我们把需要的文章都添加进来后，点击Show book，即进入书籍的管理页面，甚至还可以组织书籍的章节！然后，可以保存为本地文件，格</summary><published>2011-05-03T13:38:00Z</published><updated>2011-05-03T13:38:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2011/05/03/create-wikipedia-book.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2011/05/03/create-wikipedia-book.html"/><content type="html">&lt;p&gt;wikipedia.org是我特别喜欢的网站，首先是它的信息量极为丰富，不管是文章本身的内容，还是它所引用的外部资源；而且文章的风格也很让人喜欢，简约的文本，配以适量的图片，很对我的口味；另外一个重要的地方是，文章内容比较可靠。&lt;/p&gt;&lt;p&gt;如果看到一篇喜欢的wiki文章，想打印出来，那么看页面的左边侧栏：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/anderslly/201105/201105032135123357.png"&gt;&lt;img style="display: inline; border: 0px;" title="printwikipage" alt="printwikipage" src="http://images.cnblogs.com/cnblogs_com/anderslly/201105/201105032135121372.png" width="140" border="0" height="95" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;可以使用Printable版本打印，也可以保存为本地的PDF文件。最有趣的地方是&amp;ldquo;Create a book&amp;rdquo;：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/anderslly/201105/201105032135157453.png"&gt;&lt;img style="display: inline; border: 0px;" title="wikibookcreator" alt="wikibookcreator" src="http://images.cnblogs.com/cnblogs_com/anderslly/201105/201105032135182663.png" width="244" border="0" height="49" /&gt;&lt;/a&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;一旦开始书籍的创建，在页面的页首部分就会出现一个功能区域：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/anderslly/201105/201105032135227840.png"&gt;&lt;img style="display: inline; border: 0px;" title="wikibook-creator-panel" alt="wikibook-creator-panel" src="http://images.cnblogs.com/cnblogs_com/anderslly/201105/201105032135228363.png" width="562" border="0" height="64" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;当我们把需要的文章都添加进来后，点击Show book，即进入书籍的管理页面，甚至还可以组织书籍的章节！然后，可以保存为本地文件，格式可以是PDF、ODT和ZIM。&lt;/p&gt;&lt;p&gt;此外，可以想见，很可能有其他人已经创建了你需要的书籍，wikipedia也提供了搜索功能来搜索书籍，有时可以节省很多时间:)&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/2035890.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2011/05/03/create-wikipedia-book.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2011/03/20/word-search-puzzle.html</id><title type="text">玩儿一下Word Search Puzzle</title><summary type="text">单词搜索迷宫（Word Search Puzzle）问题的输入是一个二维的字符数组和一组单词，目标是找出字符数组网格中的所有单词。本文讨论了它的实现，以及Array.BinarySearch方法的一个值得注意的地方。</summary><published>2011-03-19T16:40:00Z</published><updated>2011-03-19T16:40:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2011/03/20/word-search-puzzle.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2011/03/20/word-search-puzzle.html"/><content type="html">&lt;p&gt;&lt;strong&gt;单词搜索迷宫&lt;/strong&gt;（Word Search Puzzle）问题的输入是一个二维的字符数组和一组单词，目标是找出字符数组网格中的所有单词。这些单词可以是水平的、垂直的或者是任意的对角线方向，所以需要查找8个不同的方向。如下图的网格：&lt;/p&gt;&lt;table border="1" cellpadding="2" cellspacing="0" width="133"&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td valign="top" width="27"&gt;&amp;nbsp;&lt;/td&gt;&lt;td valign="top" width="27"&gt;0&lt;/td&gt;&lt;td valign="top" width="27"&gt;1&lt;/td&gt;&lt;td valign="top" width="27"&gt;2&lt;/td&gt;&lt;td valign="top" width="27"&gt;3&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="27"&gt;0&lt;/td&gt;&lt;td valign="top" width="27"&gt;t&lt;/td&gt;&lt;td valign="top" width="27"&gt;h&lt;/td&gt;&lt;td valign="top" width="27"&gt;i&lt;/td&gt;&lt;td valign="top" width="27"&gt;s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="27"&gt;1&lt;/td&gt;&lt;td valign="top" width="27"&gt;w&lt;/td&gt;&lt;td valign="top" width="27"&gt;a&lt;/td&gt;&lt;td valign="top" width="27"&gt;t&lt;/td&gt;&lt;td valign="top" width="27"&gt;s&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="27"&gt;2&lt;/td&gt;&lt;td valign="top" width="27"&gt;o&lt;/td&gt;&lt;td valign="top" width="27"&gt;a&lt;/td&gt;&lt;td valign="top" width="27"&gt;h&lt;/td&gt;&lt;td valign="top" width="27"&gt;g&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td valign="top" width="27"&gt;3&lt;/td&gt;&lt;td valign="top" width="27"&gt;f&lt;/td&gt;&lt;td valign="top" width="27"&gt;g&lt;/td&gt;&lt;td valign="top" width="27"&gt;d&lt;/td&gt;&lt;td valign="top" width="27"&gt;t&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;p&gt;这里面共有4个单词：this（[0, 0]-[0, 3]）、two（[0, 0]-[2, 0]）、fat（[3, 0]-[1, 2]）和that（[3, 3]-[0, 0]），其它更短的单词此处没有列出。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;问题分析&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;最直接的方法就是，使用蛮力算法逐一检查每个可能的字符串是否在单词表中，就像我们用眼睛去检查那样，可以描述为：&lt;/p&gt;&lt;p&gt;for each row R   &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; for each column C    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; for each direction D    &lt;br /&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; check if word W in wordList&lt;/p&gt;&lt;p&gt;假设单词列表wordList有40个单词，16&amp;times;16的网格，需要大约80000（16*16*8*40）次检查，这种方法还是可行的。但如果wordList扩展到整部字典，比如有40000个单词，那工作量不小了（考虑到每个方向上，可能的单词都会有多个；而且每次都要在40000个单词里查找）。此时再采用线性查找，就不靠谱了，所以考虑将wordList按字典顺序存储，采用二分查找，这样对于每个可能的单词最多16次比较就可以了。&lt;/p&gt;&lt;p&gt;进一步观察，假定某方向上检查到字符串qx，词典里没有以qx开头的单词，此时可以确定这个方向不需要进一步检查了。&lt;/p&gt;&lt;p&gt;剩下的问题是，怎样检查8个方向的单词呢？来看位于[0, 0]处的字符t（这里忽略掉所有的单字符单词，所以一个字符本身不会构成单词），向右检查，终点的位置分别是[0, 1]，[0, 2]，[0, 3]，即行索引不变，列索引递增；向右下方向检查，终点的位置分别是[1, 1]，[2, 2]，[3, 3]，即行索引、列索引同时递增。每个方向都是如此，这样我们找出每个方向上行索引、列索引的变化规律来，就容易实现了。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;实现&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;现在通过上面分析到的二分查找、前缀检查和方向检查，基本上可以按思路直接写出来。创建类WordSearchPuzzle来完成功能，它的基本方法有：&lt;/p&gt;&lt;p&gt;&lt;a href="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103200018498908.png"&gt;&lt;img style="display: inline; border: 0px;" title="WordSearchPuzzle" alt="WordSearchPuzzle" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/20110320001849511.png" border="0" height="196" width="187" /&gt;&lt;/a&gt; &lt;/p&gt;&lt;p&gt;在构造函数里，读取puzzle网格board和单词列表words，然后通过Solve方法进行检查，它会借助于SolveDirection和PrefixSearch方法。Solve的代码是：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; Solve()&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; matches &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;for&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; rowIndex &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;; rowIndex &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; rows; rowIndex&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;    {&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;for&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; colIndex &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;; colIndex &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; columns; colIndex&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;        {&lt;br /&gt;            &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;for&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; rd &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;-&lt;/span&gt;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;; rd &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;; rd&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;            {&lt;br /&gt;                &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;for&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; cd &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;-&lt;/span&gt;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;; cd &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;1&lt;/span&gt;&lt;span style="color: #000000;"&gt;; cd&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;                {&lt;br /&gt;                    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (rd&lt;/span&gt;&lt;span style="color: #000000;"&gt;!=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;||&lt;/span&gt;&lt;span style="color: #000000;"&gt; cd &lt;/span&gt;&lt;span style="color: #000000;"&gt;!=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;)&lt;br /&gt;                    {&lt;br /&gt;                        matches &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; SolveDirection(rowIndex, colIndex, rd, cd);&lt;br /&gt;                    }&lt;br /&gt;                }&lt;br /&gt;            }&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; matches;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;这里通过rd和cd来表示行和列的变化方式，如当rd=0，cd=1，表示向右检查；当rd=-1，cd=-1，表示向左上方检查。rd和cd的取值都是-1、0、1，共有9中组合，除了(0, 0)，正好可以表示8个方向。&lt;/p&gt;&lt;p&gt;SolveDirection返回在指定位置的某个方向上找到了几个单词：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; SolveDirection(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseRow, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseCol, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; rowDelta, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; colDelta)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; charSequence &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; theBoard[baseRow, baseCol].ToString();&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; matches &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; searchResult &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;for&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; i &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseRow &lt;/span&gt;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; rowDelta, j &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseCol &lt;/span&gt;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; colDelta; &lt;br /&gt;            i &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style="color: #000000;"&gt; j &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style="color: #000000;"&gt; i &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; rows &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style="color: #000000;"&gt; j &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; columns;&lt;br /&gt;            i &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; rowDelta, j &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; colDelta)&lt;br /&gt;    {&lt;br /&gt;        charSequence &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; theBoard[i, j];&lt;br /&gt;        searchResult &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; PrefixSearch(words, charSequence);&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (searchResult &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;||&lt;/span&gt;&lt;span style="color: #000000;"&gt; searchResult &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; words.Length)&lt;br /&gt;        {&lt;br /&gt;            &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; No word with this prefix&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;            &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;break&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;        }&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (words[searchResult].Equals(charSequence))&lt;br /&gt;        {&lt;br /&gt;            matches&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;            Console.WriteLine(&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #800000;"&gt;Found '{0}' at [{1}, {2}] to [{3}, {4}]&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;br /&gt;                charSequence, baseRow, baseCol, i, j);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; matches;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;通过for循环来检查这个方向上的所有单词，这里使用了前面提到的前缀检查，来避免不必要的查找。&lt;/p&gt;&lt;p&gt;对于PrefixSearch来说，虽然从名字来看，它查找的是前缀，但也包含了完全匹配的情况：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;static&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; PrefixSearch(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;[] a, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; x)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; index &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; Array.BinarySearch(a, x, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;new&lt;/span&gt;&lt;span style="color: #000000;"&gt; StringPrefixComparer());&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; index;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;这里调用了Array.BinarySearch方法，如果不提供一个comparer，它会使用默认的字符串比较，所以这里创建一个StringPrefixComparer：&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #0000ff;"&gt;internal&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;class&lt;/span&gt;&lt;span style="color: #000000;"&gt; StringPrefixComparer : IComparer&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; Compare(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; x, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; y)&lt;br /&gt;    {&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (x.StartsWith(y))&lt;br /&gt;        {&lt;br /&gt;            &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;        }&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;else&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;        {&lt;br /&gt;            &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; x.CompareTo(y);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;如果以此为前缀的单词不存在，直接结束循环；如果以此为前缀的单词存在，就顺便检查下该前缀是否恰好匹配一个单词。至此，这个小puzzle就完成了。类的&lt;a target="_blank" href="http://files.cnblogs.com/anderslly/WordSearchPuzzle.zip"&gt;完整代码在这里&lt;/a&gt;。&lt;/p&gt;&lt;p&gt;单词列表文件里包含下面几个单词：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;" onclick="cnblogs_code_show('65b9fc3f-0425-485d-9807-86896b3991d9')"&gt;&lt;img src="http://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif" class="code_img_closed" id="code_img_closed_65b9fc3f-0425-485d-9807-86896b3991d9" /&gt;&lt;img src="http://images.cnblogs.com/OutliningIndicators/ExpandedBlockStart.gif" class="code_img_opened" id="code_img_opened_65b9fc3f-0425-485d-9807-86896b3991d9" onclick="cnblogs_code_hide('65b9fc3f-0425-485d-9807-86896b3991d9',event)" style="display: none;" /&gt;&lt;div id="cnblogs_code_open_65b9fc3f-0425-485d-9807-86896b3991d9" class="cnblogs_code_hide"&gt;&lt;div&gt;&lt;span style="color: #000000;"&gt;above&lt;br /&gt;fat&lt;br /&gt;flow&lt;br /&gt;test&lt;br /&gt;that&lt;br /&gt;this&lt;br /&gt;tweak&lt;br /&gt;two&lt;br /&gt;wcg&lt;br /&gt;wow&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;可以这样调用Solve方法：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #000000;"&gt;WordSearchPuzzle puzzle &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;new&lt;/span&gt;&lt;span style="color: #000000;"&gt; WordSearchPuzzle(&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #800000;"&gt;puzzle.txt&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;, &lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #800000;"&gt;words.txt&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;puzzle.Solve();&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;那么对于上面的网格，返回结果是：&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #000000;"&gt;Found 'this' at [0, 0] to [0, 3]&lt;br /&gt;Found 'two' at [0, 0] to [2, 0]&lt;br /&gt;Found 'fat' at [3, 0] to [1, 2]&lt;br /&gt;Found 'that' at [3, 3] to [0, 0]&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;strong&gt;&lt;br /&gt;Array.BinarySearch方法&lt;/strong&gt;&lt;p&gt;实际上，上面的StringPrefixComparer是可以省略的。直接使用Array.BinarySearch本身就够了。将SolveDirection和PrefixSearch改为：&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;static&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; PrefixSearch(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;[] a, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; x)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; index &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; Array.BinarySearch(a, x);&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #ff00ff;"&gt;&lt;strong&gt; (index &amp;gt;= 0) ? index : ~&lt;/strong&gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;span style="color: #ff00ff;"&gt;&lt;strong&gt;index;&lt;/strong&gt;&lt;/span&gt;&lt;br /&gt;}&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; SolveDirection(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseRow, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseCol, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; rowDelta, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; colDelta)&lt;br /&gt;{&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; charSequence &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; board[baseRow, baseCol].ToString();&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; matches &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; searchResult &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;for&lt;/span&gt;&lt;span style="color: #000000;"&gt; (&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; i &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseRow &lt;/span&gt;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; rowDelta, j &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; baseCol &lt;/span&gt;&lt;span style="color: #000000;"&gt;+&lt;/span&gt;&lt;span style="color: #000000;"&gt; colDelta; &lt;br /&gt;            i &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style="color: #000000;"&gt; j &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style="color: #000000;"&gt; i &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; rows &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span style="color: #000000;"&gt; j &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; columns;&lt;br /&gt;            i &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; rowDelta, j &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; colDelta)&lt;br /&gt;    {&lt;br /&gt;        charSequence &lt;/span&gt;&lt;span style="color: #000000;"&gt;+=&lt;/span&gt;&lt;span style="color: #000000;"&gt; board[i, j];&lt;br /&gt;        searchResult &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; PrefixSearch(words, charSequence);&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #008000;"&gt;//&lt;/span&gt;&lt;span style="color: #008000;"&gt; No words with this prefix.&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;strong&gt;&lt;span style="color: #ff00ff;"&gt;        if (searchResult == words.Length)&lt;br /&gt;        {&lt;br /&gt;            break;&lt;br /&gt;        }&lt;br /&gt;        if (!words[searchResult].StartsWith(charSequence))&lt;br /&gt;        {&lt;br /&gt;            break&lt;/span&gt;&lt;/strong&gt;&lt;span style="color: #000000;"&gt;&lt;strong&gt;&lt;span style="color: #ff00ff;"&gt;;&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;&lt;strong&gt;&lt;span style="color: #ff00ff;"&gt;        }&lt;/span&gt;&lt;/strong&gt;&lt;br /&gt;&lt;br /&gt;        &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;if&lt;/span&gt;&lt;span style="color: #000000;"&gt; (words[searchResult].Equals(charSequence))&lt;br /&gt;        {&lt;br /&gt;            matches&lt;/span&gt;&lt;span style="color: #000000;"&gt;++&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;            Console.WriteLine(&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #800000;"&gt;Found '{0}' at [{1}, {2}] to [{3}, {4}]&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;,&lt;br /&gt;                charSequence, baseRow, baseCol, i, j);&lt;br /&gt;        }&lt;br /&gt;    }&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; matches;&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;br /&gt;也可以完成功能。原因是对于Array.BinarySearch方法来说，如果没有查找到，它不会直接返回-1。看上面的单词列表，如果查找this，会返回5，因为它刚好有匹配值；如果查找th，将返回-5，这个-5是列表中第一个大于th的项（that）的索引（4）的按位取补；如果查找的是wt，则返回-10，wt大于列表中所有项，-11即最后一项的索引（9）加一的按位取补。这样如果BinarySearch的结果是[0-9]，说明找到了匹配值；如果小于零，则按位取补返回，取补的结果为列表长度，说明该字符串大于每个单词，不可能有匹配，返回，否则检查该字符串是否为前缀。&lt;p&gt;以前没有注意到Array.BinarySearch的这个特性，原来它还有此用处:) &lt;/p&gt;&lt;p&gt;好了，现在可以放松一下，玩玩游戏了。&lt;/p&gt;&lt;p&gt;参考：&lt;/p&gt;&lt;p&gt;《&lt;a href="http://book.douban.com/subject/1866825/" target="_blank"&gt;数据结构与问题求解-Java语言描述（第3版）&lt;/a&gt;》&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/1989182.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2011/03/20/word-search-puzzle.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry><entry><id>http://www.cnblogs.com/anderslly/archive/2011/03/14/integer-expansion.html</id><title type="text">整数的展开</title><summary type="text">简单讨论了整数的b进制展开和康托尔展开。</summary><published>2011-03-13T16:33:00Z</published><updated>2011-03-13T16:33:00Z</updated><author><name>Anders Cui</name><uri>http://www.cnblogs.com/anderslly/</uri></author><link rel="alternate" href="http://www.cnblogs.com/anderslly/archive/2011/03/14/integer-expansion.html"/><link rel="alternate" type="text/html" href="http://www.cnblogs.com/anderslly/archive/2011/03/14/integer-expansion.html"/><content type="html">&lt;p&gt;&lt;b&gt;十进制计数&lt;/b&gt;&lt;/p&gt;&lt;p&gt;日常生活中整数最常见的表示形式为十进制。如965，它的值是9*10^2+6*10+5。从最开始接触数字，我们就在用十进制计数，可以确信对于任何一个非负整数n，都可以唯一地表示为如下的形式：&lt;/p&gt;&lt;p&gt;&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m945cbdf" alt="数的展开_html_m945cbdf" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028508283.gif" border="0" height="23" hspace="8" width="240" /&gt;（公式1）&lt;/p&gt;&lt;p&gt;其中，k是非负整数，&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m82111c2" alt="数的展开_html_m82111c2" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028501315.gif" border="0" height="21" hspace="8" width="120" /&gt;是小于10的非负整数（也即0到9）。可以将这个数字的表示简化为&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m405528d" alt="数的展开_html_m405528d" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/20110314002851759.gif" border="0" height="21" hspace="8" width="97" /&gt;，这也正是我们所习惯的形式，一般把n称为（k+1）位数，如965是一个3位数。虽然根据经验可以得出，任何一个正整数n，都可以唯一地表示为上面的十进制形式，这里还是简单地证明一下（对下面的另外一种展开会有帮助）。&lt;/p&gt;&lt;p&gt;这里的证明需要分为两部分，一是存在性，二是唯一性。&lt;/p&gt;&lt;p&gt;&lt;b&gt;存在性&lt;/b&gt;：对于&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m7f04a07d" alt="数的展开_html_m7f04a07d" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/20110314002851202.gif" border="0" height="21" hspace="8" width="97" /&gt;来说，它所能表示的最大数为&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_6304d6b" alt="数的展开_html_6304d6b" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028519646.gif" border="0" height="21" hspace="8" width="64" /&gt;，如果可以证明它可以表示所有0到&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_4d8d9eff" alt="数的展开_html_4d8d9eff" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028519297.gif" border="0" height="21" hspace="8" width="65" /&gt;范围内的整数，就可以证明存在性了。这里采用归纳法：&lt;/p&gt;&lt;p&gt;基础步骤：k=0时，&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_54d8fb0d" alt="数的展开_html_54d8fb0d" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028517104.gif" border="0" height="21" hspace="8" width="23" /&gt;可以表示0-9（即0到&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m160f3b8" alt="数的展开_html_m160f3b8" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028511772.gif" border="0" height="21" hspace="8" width="64" /&gt;）范围内的所有整数；&lt;/p&gt;&lt;p&gt;归纳步骤：假设对于整数m来说，命题成立，这样&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_12c49e5d" alt="数的展开_html_12c49e5d" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028511216.gif" border="0" height="21" hspace="8" width="101" /&gt;可以表示0到&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_35e153a" alt="数的展开_html_35e153a" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/20110314002851659.gif" border="0" height="21" hspace="8" width="66" /&gt;范围内的所有整数。考虑整数n，它满足&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m70595db4" alt="数的展开_html_m70595db4" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/20110314002852103.gif" border="0" height="21" hspace="8" width="138" /&gt;，注意到&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_6f606342" alt="数的展开_html_6f606342" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028523135.gif" border="0" height="21" hspace="8" width="212" /&gt;，所以存在整数&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m352cd680" alt="数的展开_html_m352cd680" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028522578.gif" border="0" height="21" hspace="8" width="38" /&gt;（&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_22c1566" alt="数的展开_html_22c1566" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028522022.gif" border="0" height="21" hspace="8" width="81" /&gt;），使得&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_18caf09f" alt="数的展开_html_18caf09f" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028527005.gif" border="0" height="23" hspace="8" width="205" /&gt;，也就是说&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m316fbae" alt="数的展开_html_m316fbae" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028521673.gif" border="0" height="21" hspace="8" width="101" /&gt;可以表示所有0到&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m3e2eb306" alt="数的展开_html_m3e2eb306" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028527529.gif" border="0" height="21" hspace="8" width="86" /&gt;范围内的整数。&lt;/p&gt;&lt;p&gt;这样，对于任意正整数n，总可以找到一个十进制数&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m405528d" alt="数的展开_html_m405528d" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028526972.gif" border="0" height="21" hspace="8" width="97" /&gt;来表示，其中k=&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m47dbf7a3" alt="数的展开_html_m47dbf7a3" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028521640.gif" border="0" height="19" hspace="8" width="40" /&gt;（[]为下取整函数）。&lt;/p&gt;&lt;p&gt;&lt;b&gt;唯一性&lt;/b&gt;：可以用反证法证明，这里从略。&lt;/p&gt;&lt;p&gt;&lt;b&gt;n&lt;/b&gt;&lt;b&gt;的&lt;/b&gt;&lt;b&gt;b&lt;/b&gt;&lt;b&gt;进制展开&lt;/b&gt;&lt;/p&gt;&lt;p&gt;在计算机系统当中，使用更多的还是二进制、八进制和十六进制。上面十进制表示的结论也可以推广到任意的正整数b（b&amp;gt;1），也就是说，对于任意一个非负整数n，都可以唯一地表示为如下的形式：&lt;/p&gt;&lt;p&gt;&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m146f58ff" alt="数的展开_html_m146f58ff" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028539448.gif" border="0" height="23" hspace="8" width="220" /&gt;（公式2）&lt;/p&gt;&lt;p&gt;这个形式可以称为n以b为基数（base）的展开，或者n的b进制展开，记为&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m828b487" alt="数的展开_html_m828b487" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028534115.gif" border="0" height="21" hspace="8" width="113" /&gt;。当b=2时，也就是我们非常熟悉的二进制展开了，很多时候会遇到这方面的面试题：如何表示一个整数，二进制展开中有多少个1等等。那现在来考虑给定一个正整数n和基数b（b&amp;gt;1），如何求得n的b进制展开呢？通过公式2可以看到，迭代地使用带余除法，直到商为0即可。&lt;/p&gt;&lt;div style="background-color: #F5F5F5;border: 1px solid #CCCCCC;padding:10px;"&gt;&lt;div&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;static&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;readonly&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; digitTable &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #800000;"&gt;0123456789abcdef&lt;/span&gt;&lt;span style="color: #800000;"&gt;"&lt;/span&gt;&lt;span style="color: #000000;"&gt;;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;private&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;static&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;readonly&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; maxBase &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; digitTable.Length;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #808080;"&gt;///&lt;/span&gt;&lt;span style="color: #008000;"&gt; &lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;lt;summary&amp;gt;&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #808080;"&gt;///&lt;/span&gt;&lt;span style="color: #008000;"&gt; Base b expansion.&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #808080;"&gt;///&lt;/span&gt;&lt;span style="color: #008000;"&gt; &lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;lt;/summary&amp;gt;&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #808080;"&gt;///&lt;/span&gt;&lt;span style="color: #008000;"&gt; &lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;lt;param name="n"&amp;gt;&lt;/span&gt;&lt;span style="color: #008000;"&gt;n &amp;gt;= 0&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #808080;"&gt;///&lt;/span&gt;&lt;span style="color: #008000;"&gt; &lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;lt;param name="b"&amp;gt;&lt;/span&gt;&lt;span style="color: #008000;"&gt;b in [2, 16]&lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;lt;/param&amp;gt;&lt;/span&gt;&lt;span style="color: #008000;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #808080;"&gt;///&lt;/span&gt;&lt;span style="color: #008000;"&gt; &lt;/span&gt;&lt;span style="color: #808080;"&gt;&amp;lt;returns&amp;gt;&amp;lt;/returns&amp;gt;&lt;/span&gt;&lt;span style="color: #808080;"&gt;&lt;br /&gt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;public&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;static&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt; Expansion(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; n, &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;int&lt;/span&gt;&lt;span style="color: #000000;"&gt; b)&lt;br /&gt;{&lt;br /&gt;    Stack&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; remainders &lt;/span&gt;&lt;span style="color: #000000;"&gt;=&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;new&lt;/span&gt;&lt;span style="color: #000000;"&gt; Stack&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;lt;&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt;();&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;do&lt;/span&gt;&lt;span style="color: #000000;"&gt;&lt;br /&gt;    {&lt;br /&gt;        remainders.Push((n &lt;/span&gt;&lt;span style="color: #000000;"&gt;%&lt;/span&gt;&lt;span style="color: #000000;"&gt; b).ToString());&lt;br /&gt;        n &lt;/span&gt;&lt;span style="color: #000000;"&gt;/=&lt;/span&gt;&lt;span style="color: #000000;"&gt; b;&lt;br /&gt;    } &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;while&lt;/span&gt;&lt;span style="color: #000000;"&gt; (n &lt;/span&gt;&lt;span style="color: #000000;"&gt;&amp;gt;&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #800080;"&gt;0&lt;/span&gt;&lt;span style="color: #000000;"&gt;);&lt;br /&gt;&lt;br /&gt;    &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;return&lt;/span&gt;&lt;span style="color: #000000;"&gt; &lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;.Join(&lt;/span&gt;&lt;span style="color: #0000ff;"&gt;string&lt;/span&gt;&lt;span style="color: #000000;"&gt;.Empty, remainders.ToArray());&lt;br /&gt;}&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;&lt;p&gt;如果n=165，b=8，则结果为245。&lt;/p&gt;&lt;p&gt;&lt;b&gt;其它的展开形式&lt;/b&gt;&lt;/p&gt;&lt;p&gt;有趣的是，在对一个整数进行展开时，除了上面提到的b进制展开，还有其它形式的展开。比如&lt;b&gt;康托尔（&lt;/b&gt;&lt;b&gt;&lt;a href="http://en.wikipedia.org/wiki/Georg_Cantor"&gt;Cantor&lt;/a&gt;&lt;/b&gt;&lt;b&gt;）展开&lt;/b&gt;，它是说对于任意一个非负整数n可以唯一地表示为如下的形式：&lt;/p&gt;&lt;p&gt;&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_mb061b83" alt="数的展开_html_mb061b83" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028535511.gif" border="0" height="20" hspace="8" width="240" /&gt;，其中&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_4a726c85" alt="数的展开_html_4a726c85" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028536034.gif" border="0" height="21" hspace="8" width="22" /&gt;为整数，且&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m44d5da31" alt="数的展开_html_m44d5da31" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028535478.gif" border="0" height="21" hspace="8" width="63" /&gt;，i=1,2,...,n。&lt;/p&gt;&lt;p&gt;这个看起来有点儿不可思议。对于十进制展开来说，我们可以相信它可以连续地表示所有非负整数，而康托尔展开就没那么明显了。现在再次观察十进制展开&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m7ed99a95" alt="数的展开_html_m7ed99a95" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028536873.gif" border="0" height="23" hspace="8" width="221" /&gt;，它为何能够&lt;b&gt;连续地&lt;/b&gt;表示整数呢？一个k位数能够表示0到&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_6304d6b" alt="数的展开_html_6304d6b" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028531541.gif" border="0" height="21" hspace="8" width="64" /&gt;范围内的整数，而&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_6304d6b" alt="数的展开_html_6304d6b" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028549349.gif" border="0" height="21" hspace="8" width="64" /&gt;的下一个整数整数&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m167c4539" alt="数的展开_html_m167c4539" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028542380.gif" border="0" height="21" hspace="8" width="43" /&gt;正好是一个（k+1）位数的最小值，这是关键之处。而它能够&lt;b&gt;唯一地&lt;/b&gt;表示整数，在于如果i与j不相等，那么一个i位数和一个j位数就不可能相等，这样可以将所有整数按段来分隔了。&lt;/p&gt;&lt;p&gt;下面来考虑康托尔展开是否具有上段提到的两个性质。对于&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_76bf8322" alt="数的展开_html_76bf8322" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028545412.gif" border="0" height="21" hspace="8" width="225" /&gt;，可以记为&lt;img style="display: inline; border-width: 0px;" title="数的展开_html_m476637db" alt="数的展开_html_m476637db" src="http://images.cnblogs.com/cnblogs_com/anderslly/201103/201103140028544855.gif" border="0" height="21" hspace="8" width="81" /&gt;，这样仍然可以用一位数、两位数来称呼它们。对于一位数，即k=1时，康托尔展开可以表示的数是0、1；对于两位数，即k=2时，康托尔展开可以表示的数是2、3、4、5；而三位数则可以表示6、7、...、23。看来至少在k不大于3时是可以连续、唯一地表示整数的。一般地，等式&lt;img src="http://pic002.cnblogs.com/images/2011/12089/2011031402351113.gif" /&gt;成立（可以用数学归纳法证明），而这一等式使得康托尔展开就跟上段提到的十进制展开的两个性质非常类似，这样就可以初步确定康托尔展开可以唯一地表示任意的非负整数了，详细的证明过程与十进制展开的证明类似，不再赘述。&lt;/p&gt;&lt;p&gt;参考：&lt;br /&gt;《&lt;a target="_blank" href="http://book.douban.com/subject/2130743/"&gt;离散数学及其应用&lt;/a&gt;》&lt;br /&gt;《&lt;a target="_blank" href="http://book.douban.com/subject/1866825/"&gt;数据结构与问题求解&lt;/a&gt;》&lt;/p&gt;&lt;img src="http://www.cnblogs.com/anderslly/aggbug/1983104.html?type=1" width="1" height="1" alt=""/&gt;&lt;p&gt;&lt;a href="http://www.cnblogs.com/anderslly/archive/2011/03/14/integer-expansion.html" target="_blank"&gt;本文链接&lt;/a&gt;&lt;/p&gt;</content></entry></feed>
