本文被华丽的分割线分为了四段。对于O(nlogn)的排序算法,我们详细介绍归并排序并证明归并排序的时间复杂度,然后简单介绍堆排序,之后给出快速排序的基本思想和复杂度证明。最后我们将证明,O(nlogn)在理论上已经达到了最优。学过OI的人一般都学过这些很基础的东西,大多数OIer们不必看了。为了保持系列文章的完整性,我还是花时间写了一下。
首先考虑一个简单的问题:如何在线性的时间内将两个有序队列合并为一个有序队列(并输出)?
A队列:1 3 5 7 9
B队列:1 2 7 8 9
看上面的例子,AB两个序列都是已经有序的了。在给出数据已经有序的情况下,我们会发现很多神奇的事,比如,我们将要输出的第一个数一定来自于这两个序列各自最前面的那个数。两个数都是1,那么我们随便取出一个(比如A队列的那个1)并输出:
A队列:1 3 5 7 9
B队列:1 2 7 8 9
输出:1
注意,我们取出了一个数,在原数列中删除这个数。删除操作是通过移动队首指针实现的,否则复杂度就高了。
现在,A队列打头的数变成3了,B队列的队首仍然是1。此时,我们再比较3和1哪个大并输出小的那个数:
A队列:1 3 5 7 9
B队列:1 2 7 8 9
输出:1 1
接下来的几步如下:
A队列:1 3 5 7 9 A队列:1 3 5 7 9 A队列:1 3 5 7 9 A队列:1 3 5 7 9
B队列:1 2 7 8 9 ==> B队列:1 2 7 8 9 ==> B队列:1 2 7 8 9 ==> B队列:1 2 7 8 9 ……
输出:1 1 2 输出:1 1 2 3 输出:1 1 2 3 5 输出:1 1 2 3 5 7
我希望你明白了这是怎么做的。这个做法显然是正确的,复杂度显然是线性。
归并排序(Merge Sort)将会用到上面所说的合并操作。给出一个数列,归并排序利用合并操作在O(nlogn)的时间内将数列从小到大排序。归并排序用的是分治(Divide and Conquer)的思想。首先我们把给出的数列平分为左右两段,然后对两段数列分别进行排序,最后用刚才的合并算法把这两段(已经排过序的)数列合并为一个数列。有人会问“对左右两段数列分别排序时用的什么排序”么?答案是:用归并排序。也就是说,我们递归地把每一段数列又分成两段进行上述操作。你不需要关心实际上是怎么操作的,我们的程序代码将递归调用该过程直到数列不能再分(只有一个数)为止。
初看这个算法时有人会误以为时间复杂度相当高。我们下面给出的一个图将用非递归的眼光来看归并排序的实际操作过程,供大家参考。我们可以借助这个图证明,归并排序算法的时间复杂度为O(nlogn)。
[3] [1] [4] [1] [5] [9] [2] [7]
\ / \ / \ / \ /
[1 3] [1 4] [5 9] [2 7]
\ / \ /
[1 1 3 4] [2 5 7 9]
\ /
[1 1 2 3 4 5 7 9]
上图中的每一个“ \ / ”表示的是上文所述的线性时间合并操作。上图用了4行来图解归并排序。如果有n个数,表示成上图显然需要O(logn)行。每一行的合并操作复杂度总和都是O(n),那么logn行的总复杂度为O(nlogn)。这相当于用递归树的方法对归并排序的复杂度进行了分析。假设,归并排序的复杂度为T(n),T(n)由两个T(n/2)和一个关于n的线性时间组成,那么T(n)=2*T(n/2)+O(n)。不断展开这个式子我们可以同样可以得到T(n)=O(nlogn)的结论,你可以自己试试。如果你能在线性的时间里把分别计算出的两组不同数据的结果合并在一起,根据T(n)=2*T(n/2)+O(n)=O(nlogn),那么我们就可以构造O(nlogn)的分治算法。这个结论后面经常用。我们将在计算几何部分举一大堆类似的例子。
如果你第一次见到这么诡异的算法,你可能会对这个感兴趣。分治是递归的一种应用。这是我们第一次接触递归运算。下面说的快速排序也是用的递归的思想。递归程序的复杂度分析通常和上面一样,主定理(Master Theory)可以简化这个分析过程。主定理和本文内容离得太远,我们以后也不会用它,因此我们不介绍它,大家可以自己去查。有个名词在这里的话找学习资料将变得非常容易,我最怕的就是一个东西不知道叫什么名字,半天找不到资料。
归并排序有一个有趣的副产品。利用归并排序能够在O(nlogn)的时间里计算出给定序列里逆序对的个数。你可以用任何一种平衡二叉树来完成这个操作,但用归并排序统计逆序对更方便。我们讨论逆序对一般是说的一个排列中的逆序对,因此这里我们假设所有数不相同。假如我们想要数1, 6, 3, 2, 5, 4中有多少个逆序对,我们首先把这个数列分为左右两段。那么一个逆序对只可能有三种情况:两个数都在左边,两个数都在右边,一个在左一个在右。在左右两段分别处理完后,线性合并的过程中我们可以顺便算出所有第三种情况的逆序对有多少个。换句话说,我们能在线性的时间里统计出A队列的某个数比B队列的某个数大有多少种情况。
A队列:1 3 6 A队列:1 3 6 A队列:1 3 6 A队列:1 3 6 A队列:1 3 6
B队列:2 4 5 ==> B队列:2 4 5 ==> B队列:2 4 5 ==> B队列:2 4 5 ==> B队列:2 4 5 ……
输出: 输出:1 输出:1 2 输出:1 2 3 输出:1 2 3 4
每一次从B队列取出一个数时,我们就知道了在A队列中有多少个数比B队列的这个数大,它等于A队列现在还剩的数的个数。比如,当我们从B队列中取出2时,我们同时知道了A队列的3和6两个数比2大。在合并操作中我们不断更新A队列中还剩几个数,在每次从B队列中取出一个数时把当前A队列剩的数目加进最终答案里。这样我们算出了所有“大的数在前一半,小的数在后一半”的情况,其余情况下的逆序对在这之前已经被递归地算过了。
============================华丽的分割线============================
堆排序(Heap Sort)利用了堆(Heap)这种数据结构(什么是堆?)。堆的插入操作是平均常数的,而删除一个根节点需要花费O(log n)的时间。因此,完成堆排序需要线性时间建立堆(把所有元素依次插入一个堆),然后用总共O(nlogn)的时间不断取出最小的那个数。只要堆会搞,堆排序就会搞。堆在那篇日志里有详细的说明,因此这里不重复说了。
============================华丽的分割线============================
快速排序(Quick Sort)也应用了递归的思想。我们想要把给定序列分成两段,并对这两段分别进行排序。一种不错的想法是,选取一个数作为“关键字”,并把其它数分割为两部分,把所有小于关键字的数都放在关键字的左边,大于关键字的都放在右边,然后递归地对左边和右边进行排序。把该区间内的所有数依次与关键字比较,我们就可以在线性的时间里完成分割的操作。完成分割操作有很多有技巧性的实现方法,比如最常用的一种是定义两个指针,一个从前往后找找到比关键字大的,一个从后往前找到比关键字小的,然后两个指针对应的元素交换位置并继续移动指针重复刚才的过程。这只是大致的方法,具体的实现还有很多细节问题。快速排序是我们最常用的代码之一,网上的快速排序代码五花八门,各种语言,各种风格的都有。大家可以随便找一个来看看,我说过了我们讲算法但不讲如何实现。NOIp很简单,很多人NOIp前就背了一个快速排序代码就上战场了。当时我把快速排序背完了,抓紧时间还顺便背了一下历史,免得晚上听写又不及格。
不像归并排序,快速排序的时间复杂度很难计算。我们可以看到,归并排序的复杂度最坏情况下也是O(nlogn)的,而快速排序的最坏情况是O(n^2)的。如果每一次选的关键字都是当前区间里最大(或最小)的数,那么这样将使得每一次的规模只减小一个数,这和插入排序、选择排序等平方级排序没有区别。这种情况不是不可能发生。如果你每次选择关键字都是选择的该区间的第一个数,而给你的数据恰好又是已经有序的,那你的快速排序就完蛋了。显然,最好情况是每一次选的数正好就是中位数,这将把该区间平分为两段,复杂度和前面讨论的归并排序一模一样。根据这一点,快速排序有一些常用的优化。比如,我们经常从数列中随机取一个数当作是关键字(而不是每次总是取固定位置上的数),从而尽可能避免某些特殊的数据所导致的低效。更好的做法是随机取三个数并选择这三个数的中位数作为关键字。而对三个数的随机取值反而将花费更多的时间,因此我们的这三个数可以分别取数列的头一个数、末一个数和正中间那个数。另外,当递归到了一定深度发现当前区间里的数只有几个或十几个时,继续递归下去反而费时,不如返回插入排序后的结果。这种方法同时避免了当数字太少时递归操作出错的可能。
下面我们证明,快速排序算法的平均复杂度为O(nlogn)。不同的书上有不同的解释方法,这里我选用算法导论上的讲法。它更有技巧性一些,更有趣一些,需要转几个弯才能想明白。
看一看快速排序的代码。正如我们提到过的那种分割方法,程序在经过若干次与关键字的比较后才进行一次交换,因此比较的次数比交换次数更多。我们通过证明一次快速排序中元素之间的比较次数平均为O(nlogn)来说明快速排序算法的平均复杂度。证明的关键在于,我们需要算出某两个元素在整个算法过程中进行过比较的概率。
我们举一个例子。假如给出了1到10这10个数,第一次选择关键字7将它们分成了{1,2,3,4,5,6}和{8,9,10}两部分,递归左边时我们选择了3作为关键字,使得左部分又被分割为{1,2}和{4,5,6}。我们看到,数字7与其它所有数都比较过一次,这样才能实现分割操作。同样地,1到6这6个数都需要与3进行一次比较(除了它本身之外)。然而,3和9决不可能相互比较过,2和6也不可能进行过比较,因为第一次出现在3和9,2和6之间的关键字把它们分割开了。也就是说,两个数A(i)和A(j)比较过,当且仅当第一个满足A(i)<=x<=A(j)的关键字x恰好就是A(i)或A(j) (假设A(i)比A(j)小)。我们称排序后第i小的数为Z(i),假设i<j,那么第一次出现在Z(i)和Z(j)之间的关键字恰好就是Z(i)或Z(j)的概率为2/(j-i+1),这是因为当Z(i)和Z(j)之间还不曾有过关键字时,Z(i)和Z(j)处于同一个待分割的区间,不管这个区间有多大,不管递归到哪里了,关键字的选择总是随机的。我们得到,Z(i)和Z(j)在一次快速排序中曾经比较过的概率为2/(j-i+1)。
现在有四个数,2,3,5,7。排序时,相邻的两个数肯定都被比较过,2和5、3和7都有2/3的概率被比较过,2和7之间被比较过有2/4的可能。也就是说,如果对这四个数做12次快速排序,那么2和3、3和5、5和7之间一共比较了12*3=36次,2和5、3和7之间总共比较了8*2=16次,2和7之间平均比较了6次。那么,12次排序中总的比较次数期望值为36+16+6=58。我们可以计算出单次的快速排序平均比较了多少次:58/12=29/6。其实,它就等于6项概率之和,1+1+1+2/3+2/3+2/4=29/6。这其实是与期望值相关的一个公式。
同样地,如果有n个数,那么快速排序平均需要的比较次数可以写成下面的式子。令k=j-i,我们能够最终得到比较次数的期望值为O(nlogn)。

这里用到了一个知识:1+1/2+1/3+...+1/n与log n增长速度相同,即Σ(1/n)=Θ(log n)。它的证明放在本文的最后。
在三种O(nlogn)的排序算法中,快速排序的理论复杂度最不理想,除了它以外今天说的另外两种算法都是以最坏情况O(nlogn)的复杂度进行排序。但实践上看快速排序效率最高(不然为啥叫快速排序呢),原因在于快速排序的代码比其它同复杂度的算法更简洁,常数时间更小。
快速排序也有一个有趣的副产品:快速选择给出的一些数中第k小的数。一种简单的方法是使用上述任一种O(nlogn)的算法对这些数进行排序并返回排序后数组的第k个元素。快速选择(Quick Select)算法可以在平均O(n)的时间完成这一操作。它的最坏情况同快速排序一样,也是O(n^2)。在每一次分割后,我们都可以知道比关键字小的数有多少个,从而确定了关键字在所有数中是第几小的。我们假设关键字是第m小。如果k=m,那么我们就找到了答案——第k小元素即该关键字。否则,我们递归地计算左边或者右边:当k<m时,我们递归地寻找左边的元素中第k小的;当k>m时,我们递归地寻找右边的元素中第k-m小的数。由于我们不考虑所有的数的顺序,只需要递归其中的一边,因此复杂度大大降低。复杂度平均线性,我们不再具体证了。
还有一种算法可以在最坏O(n)的时间里找出第k小元素。那是我见过的所有算法中最没有实用价值的算法。那个O(n)只有理论价值。
============================华丽的分割线============================
我们前面证明过,仅仅依靠交换相邻元素的操作,复杂度只能达到O(n^2)。于是,人们尝试交换距离更远的元素。当人们发现O(nlogn)的排序算法似乎已经是极限的时候,又是什么制约了复杂度的下界呢?我们将要讨论的是更底层的东西。我们仍然假设所有的数都不相等。
我们总是不断在数与数之间进行比较。你可以试试,只用4次比较绝对不可能给4个数排出顺序。每多进行一次比较我们就又多知道了一个大小关系,从4次比较中一共可以获知4个大小关系。4个大小关系共有2^4=16种组合方式,而4个数的顺序一共有4!=24种。也就是说,4次比较可能出现的结果数目不足以区分24种可能的顺序。更一般地,给你n个数叫你排序,可能的答案共有n!个,k次比较只能区分2^k种可能,于是只有2^k>=n!时才有可能排出顺序。等号两边取对数,于是,给n个数排序至少需要log2(n!)次。注意,我们并没有说明一定能通过log2(n!)次比较排出顺序。虽然2^5=32超过了4!,但这不足以说明5次比较一定足够。如何用5次比较确定4个数的大小关系还需要进一步研究。第一次例外发生在n=12的时候,虽然2^29>12!,但现已证明给12个数排序最少需要30次比较。我们可以证明log(n!)的增长速度与nlogn相同,即log(n!)=Θ(nlogn)。这是排序所需要的最少的比较次数,它给出了排序复杂度的一个下界。log(n!)=Θ(nlogn)的证明也附在本文最后。
这篇日志的第三题中证明log2(N)是最优时用到了几乎相同的方法。那种“用天平称出重量不同的那个球至少要称几次”一类题目也可以用这种方法来解决。事实上,这里有一整套的理论,它叫做信息论。信息论是由香农(Shannon)提出的。他用对数来表示信息量,用熵来表示可能的情况的随机性,通过运算可以知道你目前得到的信息能够怎样影响最终结果的确定。如果我们的信息量是以2为底的,那信息论就变成信息学了。从根本上说,计算机的一切信息就是以2为底的信息量(bits=binary digits),因此我们常说香农是数字通信之父。信息论和热力学关系密切,比如熵的概念是直接从热力学的熵定义引申过来的。和这个有关的东西已经严重偏题了,这里不说了,有兴趣可以去看《信息论与编码理论》。我对这个也很有兴趣,半懂不懂的,很想了解更多的东西,有兴趣的同志不妨加入讨论。物理学真的很神奇,利用物理学可以解决很多纯数学问题,我有时间的话可以举一些例子。我他妈的为啥要选文科呢。
后面将介绍的三种排序是线性时间复杂度,因为,它们排序时根本不是通过互相比较来确定大小关系的。
附1:Σ(1/n)=Θ(log n)的证明
首先我们证明,Σ(1/n)=O(log n)。在式子1+1/2+1/3+1/4+1/5+...中,我们把1/3变成1/2,使得两个1/2加起来凑成一个1;再把1/5,1/6和1/7全部变成1/4,这样四个1/4加起来又是一个1。我们把所有1/2^k的后面2^k-1项全部扩大为1/2^k,使得这2^k个分式加起来是一个1。现在,1+1/2+...+1/n里面产生了几个1呢?我们只需要看小于n的数有多少个2的幂即可。显然,经过数的扩大后原式各项总和为log n。O(logn)是Σ(1/n)的复杂度上界。
然后我们证明,Σ(1/n)=Ω(log n)。在式子1+1/2+1/3+1/4+1/5+...中,我们把1/3变成1/4,使得两个1/4加起来凑成一个1/2;再把1/5,1/6和1/7全部变成1/8,这样四个1/8加起来又是一个1/2。我们把所有1/2^k的前面2^k-1项全部缩小为1/2^k,使得这2^k个分式加起来是一个1/2。现在,1+1/2+...+1/n里面产生了几个1/2呢?我们只需要看小于n的数有多少个2的幂即可。显然,经过数的缩小后原式各项总和为1/2*logn。Ω(logn)是Σ(1/n)的复杂度下界。
附2:log(n!)=Θ(nlogn)的证明
首先我们证明,log(n!)=O(nlogn)。显然n!<n^n,两边取对数我们得到log(n!)<log(n^n),而log(n^n)就等于nlogn。因此,O(nlogn)是log(n!)的复杂度上界。
然后我们证明,log(n!)=Ω(nlogn)。n!=n(n-1)(n-2)(n-3)....1,把前面一半的因子全部缩小到n/2,后面一半因子全部舍去,显然有n!>(n/2)^(n/2)。两边取对数,log(n!)>(n/2)log(n/2),后者即Ω(nlogn)。因此,Ω(nlogn)是log(n!)的复杂度下界。
今天写到这里了,大家帮忙校对哦
Matrix67原创
转贴请注明出处
17 条回复
您也随便说几句吧:

沙发
p.s大牛MSN在线么,我刚申请了一个加您,给我加到MSN的群里面吧
you're cool...
过几天等着看我的《DD的USACO心得》(今天已写了第一篇,全写好了一块发布)。
赞个!
我想问在qsort怎么用一个随机数作为关键字?这样会让qsort更稳定吧.
ps:刚才验证码打错了,让我写的一大堆话都没了.汗~~~~~~~
回复:Sorry, 那个地方我说的不清楚,现在改了。
Although merge sort runs in Θ(n lg n) worst-case time and insertion sort runs in Θ(n2) worst-case time, the constant factors in insertion sort make it faster for small n. Thus, it makes sense to use insertion sort within merge sort when subproblems become sufficiently small. Consider a modification to merge sort in which n/k sublists of length k are sorted using insertion sort and then merged using the standard merging mechanism, where k is a value to be determined.
Show that the n/k sublists, each of length k, can be sorted by insertion sort in Θ(nk) worst-case time.
Show that the sublists can be merged in Θ(n lg (n/k) worst-case time.
Given that the modified algorithm runs in Θ(nk + n lg (n/k)) worst-case time, what is the largest asymptotic (Θnotation) value of k as a function of n for which the modified algorithm has the same asymptotic running time as standard merge sort?
How should k be chosen in practice?
请您解答一下(这是算法导论上的一个问题。。。)
回复:
1. n/k*(k^2)=nk,结论显然成立
2. 递归有log(n/k)层,根据本文内容结论显然成立
3. k=Θ(logn),否则nk就超过nlogn了
4. 实践中k=O(logn)。当k=log(n/k)时显然最优
大牛写得太精彩了!
实在是厉害!!
学习~
[...] 在这篇文章里,我们从信息论的角度证明了,基于比较的排序算法需要的比较次数至少为log2(n!),而log(n!)=Θ(nlogn),这给出了比较排序的一个下界。但那里我们讨论的只是最理想的情况。一个事件本身所含的信息量是有大小之分的。看到这篇文章之后,我的思路突然开阔了不少:信息论是非常强大的,它并不只是一个用来分析理论最优决策的工具。从信息论的角度来分析算法效率是一件很有趣的事,它给我们分析排序算法带来了一种新的思路。 [...]
顶!!!!!
太精辟了。。。
我看导论看到头都爆了还没看懂,一看这儿就看懂了!。
。。。膜拜之余弱弱的问一句。。。。
您为啥选文科呢?。。。
还有一种算法可以在最坏O(n)的时间里找出第k小元素。那是我见过的所有算法中最没有实用价值的算法。那个O(n)只有理论价值。
说说看那个算法吧
hao
[...] 第四名:快速排序 快速排序算法是 1960 年由英国计算机科学家 C.A.R. Hoare 发明的,是一种既高效又简洁的排序方法,现在已是学习算法的必修内容之一。快速排序的思想并不复杂,妙就妙在那个线性的数据分割过程,而真正最牛 B 的则是对整个算法的时间复杂度分析。我曾写过一个快速排序平均 O(n log n) 的证明,分析过程绝对值得欣赏。 [...]
[...] B 的则是对整个算法的时间复杂度分析。我曾写过一个快速排序平均 O(n log n) 的证明,分析过程绝对值得欣赏。 第三名:并查集 [...]
[...] 第四名:快速排序 快速排序算法是 1960 年由英国计算机科学家 C.A.R. Hoare 发明的,是一种既高效又简洁的排序方法,现在已是学习算法的必修内容之一。快速排序的思想并不复杂,妙就妙在那个线性的数据分割过程,而真正最牛 B 的则是对整个算法的时间复杂度分析。我曾写过一个快速排序平均 O(n log n) 的证明,分析过程绝对值得欣赏。 [...]
[...] B 的则是对整个算法的时间复杂度分析。我曾写过一个快速排序平均 O(n log n) 的证明,分析过程绝对值得欣赏。 第三名:并查集 [...]
快速排序部分中提到的“关键字”应该叫做“关键值”更准确吧。关键字是key, 而这里应该是key value的意思。
太精彩了,膜拜!
[...] matrix67大牛的blog今天三周年了~~这是他推出的三周年的精选回顾全是精华阿 看看下面的这些文章,谁能看出这是个文科生(传说中去北大中文系泡妞?!)原文的地址: http://www.matrix67.com/blog/archives/5581. 原创科普说明文:递归假期的一篇作文,叫我们写任一说明文。我把这篇作文发到了我的Blog上。这可能是我Blog最早的技术文章,它确定了我以后类似文章的写作风格。2. 非常奇妙的证明:图形必在格点之外翻译的cut-the-knot上的一篇文章。这是我所见到的最elegant的证明之一,在饭桌上提到证明问题时我经常会举这个例子。几个好友很早就开始阅读我的Blog,他们一致认为这是最令人难忘的一篇日志。3. 几个把平面几何问题的辅助线做到空间去的数学趣题也是翻译的cut-the-knot上的系列文章,当时觉得确实非常神奇。后来的学习发现,其实射影几何中有更多有趣的例子。4. 追溯羊与车:Monty Hall Dilemma问题的故事我们数学老师提到了Monty Hall问题,他的说法是错误的,因此才写下这篇文章。当时写这篇文章主要是给我的同学看,因为那时这个Blog几乎只有我们同学才上。5. 几个很强的数列这是我在The On-Line Encyclopedia of Integer Sequences找到的,非常强。不是经常有考什么数列找规律的么?从这里面随便挑一个来,不查数列百科全书的话别人几乎不可能找出规律来。6. 爱的方程式惊奇函数图像系列文章的第一辑。后来渐渐有了3D桃心函数、阴阳图函数、公式生成的色情图片等一系列的东西。7. 什么是P问题、NP问题和NPC问题这可能是我写的最长的一篇原创文章了。很多网友都说,在类似的文章中这一篇是讲解最清楚、最通俗易懂的。8. KMP算法详解可能是这个Blog最经典的文章了。不少朋友最初都是找KMP算法找到这个Blog来的。9. 位运算讲解系列文章应该是这个Blog里第二经典的文章了。10. 无限小却无限大的集合 & 阶梯状的连续函数前段时间我和一帮人在饭桌上提到了诡异的函数,比如处处连续处处不可导的函数、除常函数外没有最小正周期的周期函数、导数为正却找不出单增区间的函数、平面上任意小的范围内均能找到一点的单值函数、在有理点处处不连续在无理点处处连续的函数(俗称爆米花函数)。但处处连续的阶梯函数,很多人可能还是第一次听说。挺好玩的。11. 令人称奇的简单证明:五种方法证明根号2是无理数牛!这个牛!想看一些精妙的证明,体会到数学证明的神奇之处的话,从这里开始是一个不错的选择。12. 从零开始学算法:十种排序算法介绍(上)这个也牛!同样地,如果想看一些精妙的算法和复杂度的分析证明,体会CS的乐趣,从这里开始是一个不错的选择。13. 从零开始学算法:十种排序算法介绍(中)这篇日志里讲了快速排序的平均复杂度的分析和证明,很强大很科学。初学算法的人经常会忽略复杂度分析这一步,学过一段时间后回过头来看看经典算法的复杂度分析是很有益的。14. 从零开始学算法:十种排序算法介绍(下)没啥好介绍的……以后有些没什么特别背景的日志我就不附加文字介绍了,不然写着好累。15. 无题 于2007年5月16日现在我已经很少在自己的Blog里写我的感情生活了。这是我在19岁生日那天写的。16. 数论部分第一节:素数与素性测试17. 神奇的分形艺术(一):无限长的曲线可能围住一块有限的面积分形艺术系列的开篇。分形这个东西其实挺好玩的。18. 十大另类程序语言(上)19. 十大另类程序语言(下)哈哈,这个好玩!!有几个语言相当搞笑,挺佩服老外的想象力的。20. 令人敬畏的十维空间出人意料的结论。对应的几何图像太难以想像了。我一直想写一篇描述四维几何形状的文章,至今仍未动键盘。21. 十个有趣的英文文字游戏(上)22. 十个有趣的英文文字游戏(下)很早就对英文文字游戏感兴趣,看到过不少,记了各种性质独特的英文句子。有一天突然想整理出来写一下,于是有了这两篇日志。中文其实也有很多好玩的东西,比如对联啊,灯谜啊,拆字啊,断句啊,回文句啊等等。23. 神奇的分形艺术(四):Julia集和Mandelbrot集还是在饭桌上,每次提到数学神秘得令人恐惧时我都会讲起这个。真是太神奇了,一个如此简单的过程竟然可以生成这般复杂玄妙的分形图形。24. Tupper自我指涉公式:图象里竟然包含式子本身数学中的魔术,非常有意思。本以为非常神奇,揭秘之后恍然大悟——不过如此。25. 编辑距离、拼写检查与度量空间:一个有趣的数据结构26. Poincaré圆盘模型:一个神奇的双曲世界进北大时恰逢数学文化节十周年,数院开了一系列精彩的讲座。我去听了其中三个讲座,这是我听过的最精彩一次。看《什么是数学》时看到了相关的内容,再结合这里的一些东西仔细品味了一下,真是科学得无与伦比。以后我还将引用到这篇日志。27. 等高线模式:解决极大极小问题的另类策略Pólya的《数学与猜想》确实是一本好书。我在这本书里学到了好多东西,其中一个最主要的收获就是这套诡异的极大极小问题解决办法。28. 趣题:直觉 VS 理性思考 经典概率问题29. 另类搞笑:自我指涉例句不完全收集AboutMe里就提到,我喜欢带有递归和自我指涉的句子。一直收集着很多这样的句子,终于决定整理出来和大家分享。30. 物理方法解决数学问题(二):Archimedes与球体积公式31. 趣题:n为奇数时,正n边形的三角形剖分内有且仅有一个锐角三角形从EagleFantasy那里挖来的,是我目前最喜欢的“一句话证明”。跟别人提到“一句话证明”时我必然会拿它当例子。32. 物理方法解决数学问题(四):Fermat-Torricelli问题33. 证明实数区间不可数的新方法我喜欢讲课,喜欢听每次揭晓“谜底”后下面的人恍然大悟的感叹声,喜欢从基本结论出发一步步推出不可思议的结论。在所有科学的东西里,我最爱讲的,最具悬念,最有戏剧性,结论最令人惊讶,最能颠覆传统观念就是对无穷集合势的分析了。从有限到无限,从可数到不可数,以及直线和平面上的点一一对应等等,每一个证明都令人拍案叫绝。这里提到了实数区间不可数与博弈游戏的关系,从一个全新的角度来看连续统,分析证明过程实在巧妙。34. 趣题:一个与Hamilton回路有关的问题35. 100囚犯问题、100囚犯问题加强版与选择公理(上)36. 100囚犯问题、100囚犯问题加强版与选择公理(下)37. 趣题:构造函数使得平面上任意小的圆内均包含函数上的点有一天突发奇想,到豆瓣的数学小组去逛了一圈,然后发现了这个神奇的东西。38. 很诡异的博弈问题分析方法39. 趣题:猜帽子游戏与Hamming编码40. 趣题:构造游戏初始状态使得后行者必胜41. 物理方法解决数学问题(五):一个与椭圆有关的性质42. 趣题:量子计算机、另类编程语言和幂函数的解释43. 趣题:对数字进行编码使其按字典序排列后仍然有序这两篇日志是相当科学的算法题。很长时间没看到这么经典有趣的题目了,特别是前面那篇。44. 神奇的锈规作图:单用一个只能画单位圆的圆规如何作等边三角形这个精彩,强烈推荐一下。45. 矩阵、随机化与分形图形某留学Stetson大学的MM一次在校内上发日志链接到了我的Blog,我回访回去时认识了她。我和Stetson MM网恋了一段时间,发国际短信花了我不少钱。后来Stetson MM有了男朋友了,这段故事就此告终。这告诉我们:建网站来吸引MM终究是不可靠的。Stetson MM是我所见过的最适合我的MM,其思维的相似程度达到了令人惊讶的地步,世界上居然有一个如此像我的异性真是不可思议!这篇日志与Stetson MM在线性代数课上的一个课题研究有关。Stetson MM告诉我,她一个同学看到这篇日志后问她我Blog里的Stetson MM是不是指的她,她惊呼“你也订阅了这个Blog”呀。Small world。当时这篇日志在抓虾上的推荐数很是让我吃惊。46. 分享一些有趣的面试智力题(上)47. 分享一些有趣的面试智力题(下)48. 20年的时间里你可以做些什么?今年我20岁生日时写下的一篇特别的日志。神奇的是,这篇日志的ID正好也是我的生日——516。49. 趣题:空间四边形外切于给定球,求证四切点共面50. 趣题:直尺不够长时如何作出连接两点的直线?《什么是数学》中与射影几何相关的一个习题。当时我曾经在古汉课上大叫“太科学了”。 M12 Puzzle 传Google2亿美元收购Digg [...]