Jul 13

    Proofs from THE BOOK的第六章相当精彩,这一章循序渐进地介绍了多个无理性证明。先证明e是无理数,证明方法和高数课本上的基本相同;试图用类似的办法证明e^2也是无理数时,这一章的内容开始牛B了起来,一些巧妙的变换就让原来的办法继续适用于e^2的证明;加上一些更有趣的技巧,我们还能继续证明e^4也是无理数;当证明对除0外的所有有理数r,e^r都是无理数时,全章达到了高潮。
    这一章还提到了pi^2是无理数的证明方法。这个证明建立在Ivan Niven于1947年提出的“pi是无理数”的经典证明的基础上:仅仅是在原证明过程中加了一些微妙的变化就得到了pi^2也是无理数的结论。注意到,“pi^2是无理数”是一个比“pi是无理数”更强的结论。由于有理数的平方还是有理数,因此证到了pi^2是无理数也就说明了pi必然是无理数;但反过来却不行,因为无理数的平方不一定也是无理数,比如根号2的平方就不是无理数。

    证明过程用到了一个函数,其中n是一个任取的大于等于1的常数。可以想像,这个函数的分子部分展开后是一个关于x的整系数多项式,最低次数为n,最高次数为2n。我们将用到这个函数的两个性质:首先,当0<x<1时,显然有0 < f(x) < 1/n!;其次,函数f及其任意阶导数在x=0和x=1处都是整数。为了证明后一个结论,首先注意到当x=0时,不管是多少阶的导数,除了常数项以外其余项都是0;常数项只可能在n<=k<=2n时出现(k表示k阶导数),但此时它等于一个整系数乘以k!/n!,显然也是个整数。另外,由于f(x)=f(1-x),根据复合函数的微分法我们立即得到对任意x都成立,当然也就有

查看更多 »

Jun 18

    又回来更新啦!虽然还有两门课没考,但今天已经轻松了不少。梦魇般的现代文学史总算是结束了。抱了两天两夜的佛脚,结果考试时一看卷子,仍然没一道会的题目。不定项选择多选少选均不得分,都是些文学常识题,给四篇我从没见过的小说名字问哪些是第一人称叙事,或者给四个人名字问哪些是笔名之类的。天哪……以后的古代文学史咋办啊。
    先强烈推荐一本好书。前几天在TopLanguage看到有牛人推荐Proofs from THE BOOK这本书,当即决定买了下来。这几天复习累了我都在看这本书,真的是很好很强大,里面汇集了很多著名问题的经典证明,包括很多我一直想找但没找到的证明。好了不多废话了,下面进入正题。

    很早以前,我们曾经研究过质数,证明了质数有无穷多个。后来,我们又学到了另外两种证明质数无穷多的方法。这两种方法的基本思路相同:寻找一个无穷大的集合,里面的数两两互质。只用有限个质数明显不能得到无穷多个两两互质的数,于是我们立即可知质数必然有无穷多个。今天,我们将证明两个比质数无穷多更强的定理。这两个证明都出自Proofs from THE BOOK的第一章。

    定义函数π(x)为“小于等于x的质数有多少个”。无妨规定x为一个正整数。我们将用初等微积分方法证明当x趋于无穷时π(x)也趋于无穷并给出π(x)的一个下界。我们将说明,对于所有x,π(x)>=log(x)-1,即x以内的质数至少有log(x)-1个。
    为了说明这一点,让我们考虑所有不超过x的质数的倒数的等比级数(1 + 1/p + 1/p^2 + ..)的乘积,即
    回忆等比级数的公式,则我们有:

  

    第二行的一些变换非常巧妙。第二行中间的不等号是一个关键,用到了一个基本事实:第k个质数显然比k大。最后的连乘中前一项的分子和后一项的分母正好抵消,最后消完了就只剩了一个π(x)+1。
    另一方面,想像一下把(1+1/2+1/4+...)(1+1/3+1/9+...)(1+1/5+1/25+...)...展开的样子,很显然展开后的每一项都是一个所有质因子都不大于x的数的倒数,即Σ(1/m),其中m取所有仅含1..x范围内的质因子的数。显然,原本就比x小的数,其质因子当然不可能超过x,这就是说从1到x的所有正整数都是属于m的。利用一些微积分的基本知识,我们可以立即得出Σ(1/m) >= 1+1/2+1/3+...+1/x >= log(x)。地球人都知道,log(x)是没有上界的,于是质数的个数也没有上界。
    这里还有一个类似的问题,大家可以对照着看看。

查看更多 »

May 22

    今天,我们将从一系列公理开始,从自然数的产生一直说到实数理论的完善。你或许会对数学的“科学性”有一个新的认识。注意,本文的很大一部分内容并非直接来源《什么是数学》,这篇文章可以看作是《什么是数学》中有关章节的一个扩展。

    自然数是数学界中最自然的数,它用来描述物体的个数,再抽象一些就是集合的元素个数。在人类文明的最早期,人们就已经很自然地用到了自然数。可以说,自然数是天然产生的,其余的一切都是从自然数出发慢慢扩展演变出来的。数学家Kronecker曾说过,上帝创造了自然数,其余的一切皆是人的劳作。 (God made the natural numbers; all else is the work of man.)
    随着一些数学理论的发展,我们迫切地希望对自然数本身有一个数学描述。从逻辑上看,到底什么是自然数呢?历史上对自然数的数学描述有过很多的尝试。数学家Giuseppe Peano提出了一系列用于构造自然数算术体系的公理,称为Peano公理。Peano公理认为,自然数是一堆满足以下五个条件的符号:
   1. 0是一个自然数;
   2. 每个自然数a都有一个后继自然数,记作S(a);
   3. 不存在后继为0的自然数;
   4. 不同的自然数有不同的后继。即若a≠b,则S(a)≠S(b);
   5. 如果一个自然数集合S包含0,并且集合中每一个数的后继仍在集合S中,则所有自然数都在集合S中。(这保证了数学归纳法的正确性)

    形象地说,这五条公理规定了自然数是一个以0开头的单向有序链表。
    自然数的加法和乘法可以简单地使用递归的方法来定义,即对任意一个自然数a,有:
a + 0 = a
a + S(b) = S(a+b)
a · 0 = 0
a · S(b) = a + (a·b)

    其它运算可以借助加法和乘法来定义。例如,减法就是加法的逆运算,除法就是乘法的逆运算,“a≤b”的意思就是存在一个自然数c使得a+c=b。交换律、结合率和分配率这几个基本性质也可以从上面的定义出发推导出来。
    Peano公理提出后,多数人认为这足以定义出自然数的运算,但Poincaré等人却开始质疑Peano算术体系的相容性:是否有可能从这些定义出发,经过一系列严格的数学推导,最后得出0=1之类的荒谬结论?如果一系列公理可以推导出两个互相矛盾的命题,我们就说这个公理体系是不相容的。Hilbert的23个问题中的第二个问题就是问,能否证明Peano算术体系是相容的。这个问题至今仍有争议。

查看更多 »

Apr 18

    期中告一段落。除了下下星期要交的现文史论文以外,最近似乎又清闲了不少,又有功夫在这里写点东西了。当然,我宝贵的时间也没有荒废在论文、作业和考试上。几乎每一堂古汉课和现文史课我都在读《什么是数学》,进度算是相当快了。这可能是我近几年读的所有书中给我带来的收获最大的一本。最近好几个人问我,有什么牛B一点的数学书没。我毫不犹豫地脱口而出,《什么是数学》。如果我要去一个荒岛上,只能带三本书,我会选择《算法导论》、《组合数学》和《什么是数学》。如果叫我舍弃一样,我估计会扔掉《组合数学》。如果还得再丢弃一本,我只好忍痛丢下《算法导论》了。
    读《什么是数学》的收获太多了。在这里,我只更新一些我原来不知道,又很有趣的东西。如果你希望迅速对此书有一个全面的了解,千万不要错过dd牛的《什么是数学》笔记

    阅读《什么是数学》的前面几章时,你经常会跟随着书中的文字重新看待一个显而易见的结论,然后对这个结论有了一个全新的认识。比如,书中曾提到,为什么数学归纳法是合理的?我已经知道n=1时结论成立,也知道若n=k成立则n=k+1结论也成立,那么对于任意一个给定的正整数t,n=t时的结论是成立的,因为经过有限次迭代后最终我们总可以到达n=t的情况。但是,为什么我们敢断言对所有这无穷多个n,结论都是成立的?显然,你不能说“我们可以迭代无穷多次”,一个有限的证明过程当然不允许有无限多个步骤。因此,为了说明对于所有正整数n结论都成立,我们不得不使用反证法把“无穷”变成“有穷”。我们假设对于某些n,这个结论是不成立的。那么,这里面一定存在一个最小的数p,它使得结论不成立。由于我们已知n=1时结论成立,因此p一定是大于1的。但n=p是“最早”使结论不成立的情形,因此n=p-1时结论一定为真。这就与我们已知的第二个条件“若n=k成立则n=k+1结论也成立”矛盾了。因此我们说,对于所有正整数n,结论都是成立的。
    这个推理过程中用到了另一个显而易见的结论:对于一个非空的正整数集C,C中一定存在一个最小的元素。这又是为什么呢?你可能会说,废话,把所有元素拿出来两个两个的比,一定能比出一个最小的数来。这种说法是错误的。注意到集合C有可能有无穷多个元素,你是比不完的。为了更清晰地认识这个结论,我们只需要注意到,如果把条件换成“有理数集C”或者“实数集C”,结论就不再成立了,因为集合{1, 1/2, 1/3, 1/4, ...}显然不存在一个最小的数。可以看到,上述结论是否成立是和数的稠密性紧紧相关的。事实上,为了说明正整数集C中存在最小元素,我们任意从集合中取出一个元素n,那么1, 2, ..., n这有限多个数当中一定存在一个最小的数,它在这个集合C中。它就是整个集合的最小数。对于稠密的有理数点和实数点,这个证明显然不再适用。
查看更多 »

Nov 1

    每个领域里都有那么几本经典的读物,比如科幻界的圣经就是世界科幻之父Jules Verne的一系列作品,或是家喻户晓的科幻经典Hitchhiker's Guide to the Galaxy;信息学界则有Knuth的The Art of Computer Programming,或是每个搞OI/ACM的都看过或者听说过的Introduction to Algorithms。前段时间有数学爱好者在这里留言问到有没有什么好的数学书,在这里我和大家分享几本数学界的经典读物。

    有史以来最为经典的数学书当属What Is Mathematics? An Elementary Approach to Ideas and Methods(中译:什么是数学?对思想和方法的基本研究)。这本书由Courant和Herbert Robbins所写,后来Ian Stewart做了修订,增加了一些Courant和Robbins时代还没有的新进展。这本书非常牛B,Einstein曾说,这本书是“对整个数学领域中的基本概念及方法的透彻清晰的阐述”。
    Ian Stewart自己也写过不少书,他似乎很喜欢把数学和其它自然科学联系在一起,比较经典的是Nature's Numbers(中译:大自然的数学游戏);另一本经典则是Does God Play Dice,不过我既没找到中译本,也没找到影印版。

    前几天数学课上我们老师推荐了Pólya的几本经典著作:

  • How to Solve It: A New Aspect of Mathematical Method
  • Mathematics and Plausible Reasoning: Volume I Induction and Analogy in Mathematics
  • Mathematics and Plausible Reasoning: Volume II Patterns of Plausible Inference


    这三本书现在都有译本,中译名分别如下:

  • 怎样解题:数学思维的新方法
  • 数学与猜想(第一卷):数学中的归纳和类比
  • 数学与猜想(第二卷):合情推理模式


    现在我正在看《数学与猜想》,里面的习题设置是一大亮点。读完一章后你一定要认真完成所有的习题,不然你这本书就白买了。每一章的习题都是前后连贯,一气呵成的。前面的题可以帮助你完成后面的题,带你一步一步进行发现、归纳、猜想、验证和证明。依次完成这一章的所有习题后你会体会到探索数学奥秘的乐趣。以后只要我发现这些书里有什么我从没见过的新奇玩意儿,我都会放到Blog上。

    探索数学世界,看一本数学史书是很有必要的。我曾专门去书店找过,这一类的书有很多,比如机械工业出版社就有一本Victor J.Katz的数学简史。早以前我买了一本《数学悖论与三次数学危机》,这本书则从一个特殊的角度纵观数学发展史,切入点非常有意思。

    国外还有很多非常流行的数学科普读物,这些书曾经非常畅销。例如,John Allen Paulos的Once Upon a Number: The Hidden Mathematical Logic of Stories(中译:从前有个数:故事中的数学逻辑)就是一本很流行的大众化数学读物;而另类科幻小说Flatland(中译:神奇的二维国)则以一个特殊的形式向大家介绍了数学中的维度概念。

最后,欢迎大家在下面留言,给我和其它网友也推荐一些好的数学书
Matrix67原创,转贴请注明出处

Dec 15

    近来有很多问该看什么书的。我说一下个人意见(仅代表个人意见)。大家可以按照这样的顺序来阅读这些书(时间足够的话):

    《算法导论》
    《数据结构与算法分析——C语言描述》
    《组合数学》

    这三本书必看,都是机械工业出版社出版的,翻译质量嘛——尽管有些别扭(翻译的东西都这样),但肯定看得懂。
    第一本的中译本是才出版的,比原来那个盗版的要好得多。
    第二本是Mark Allen Weiss写的,第二版。
    第三本是Richard A.Brualdi写的,第四版。
    如果你英文好的话,最好看原版。
    有人会问我为什么喜欢国外的教材。这是因为,国外的教材各个章节安排得很好,体系性更强,看起来更轻松(保证你能看懂),而且更具有启发性。这些教材的习题安排得很好,绝对是可以经过独立思考想出来的题目。和国内很多教材扔出一大堆概念和公式不同,阅读国外教材是循序渐进的一个学习过程。

    以下两本书的话,有兴趣就看吧。
    《离散数学》,第六版,Richard Johnsonbaugh,电子工业出版社。
    《How to Ace Calculus: The Streetwise Guide》系列,中译本叫做“微积分之XXXX”,湖南科学技术出版社。当成看小说吧,很有意思,是我见过的最不像教材的教材了。

    最后需要看的是刘汝佳和黄亮的《算法艺术与信息学竞赛》。这里面有很多概念上的讲解是错误的,但是题目讲解的资源很丰富。当前面的书看完了后,拿最后这一本当作题库来实战演练吧。书里的概念讲解部分就不必看了,直接消化里面的例题,一道一道地消化。第三部分的计算几何可以仔细学习一下,因为这部分内容之前的书好像涵盖得不多。

    还有,选择什么样的题库。个人首推USACO。大家可以自己了解一下这个与众不同的OJ,它基本上是一个“个人的教练”,并不参与网络排名。你大概需要话半年的时间完成所有的题目。做USACO需要你的认真态度和耐心。千万别看中译和别人的解答。整个USACO的任务完成之后,你基本上就无敌了。

Jun 4

     有一个女人的男人很幸福。事实上,这是片面的。应该说,有不止一个女人的男人更幸福。但是,这样会坏了我的人品,而且被女的知道了也不好。两个耍得好的女人话很多,秘密在女人中传得很快。于是,我打算不同时和两个耍得好的女的耍朋友。后来我意识到,这样也不行。女人太无敌了,即使A与B耍得好,B与C耍得好,A和C的消息也是互通的。哪怕只有一个朋友关系也能把两群人联系在一起。我不得不改变策略,使得我的女朋友之间没有任何渠道传递信息。也就是说,在上面的A、B、C三个人中,虽然A和C没有直接的联系,但我也不能同时和A、C耍。不久后,我想知道,某两个女人是否可以通过某条“朋友链”传递信息。这就是所谓的等价关系——基本上算是判断一个无向图的连通性。就像很多个集合,每次选两个并成一个,而且我们随时想知道某两个元素经过前面的合并后是否在同一个集合内。怎么办呢?后来有一天,我发现那些小女生喜欢玩些认亲戚的游戏,什么谁是谁妈,谁是谁姐,谁是谁女儿之类的(不知道为什么这些疯女人喜欢搞这些)。我突然恍然大悟,我的问题可以用树结构来完成。亲戚的亲戚还是亲戚,但有一点总相同:所有亲戚的始祖总是一样的。始祖一样的都是一伙的。因此,把两个集合并在一起,只要让其中一个集合的根成为另一个集合中的某个元素的一个儿子就行了,这种家谱关系的改变将使前面的集合中所有的元素拥有和后面那个集合一样的鼻祖,而这将成为这些元素的“标志”。这个想法的灵感是来自女人世界的,因此女人还是有一定的作用。
    这就叫并查集,又叫不相交集。它可以合并两个集合并且查询两个元素是否在同一集合。我们有一个很有效的剪枝:递归时顺便把路上经过的祖祖辈辈全部变成根的儿子。这样的程序只用2行来解决。
function find_set(x:integer):integer;
   begin
   if x<>p[x] then p[x]:=find_set(p[x]);
   exit(p[x]);
end;

    p[x]表示元素x的父亲的位置。一开始,p[x]都等于x自己,表示自己一个人是一个集合。函数find_set(x)将返回x所在集合(一棵树)的根。
    并查集还有些其它的剪枝和一些很复杂的效率分析问题,这里不多说了。

    写到这里,《数据结构与算法分析》中的几个大块内容算是说清楚了。由于本文的叙述调整了原书各章节的顺序且至此还没有涉及书里的一些小问题,因此这里想把遗漏下的一些小东西提一下。
    有一些树结构可能要求同时满足多个要求。比如一个简单的问题:如果要求构造一个堆使得既能查找最小元素又能查找最大元素怎么办?这时,我们可以用一个特殊的方法来实现:树的单数层满足一种性质,树的双数层满足另一种性质。我们用一个叫做最小-最大堆的东西来实现前面说的问题。这个堆的双数层的数据小于它爸大于它爸的爸,单数层的数据反过来,大于它爸小于它爸的爸。用类似的方法,我们还可以设计一个二叉查找树,使得它能够支持含有2种不同类型元素的数据。在单数层按其中一种操作,在双数层按另一种操作,这样可以方便的查找同时位于两个不同类元素的指定区间内的数据。这种二叉查找树叫做2-d树。扩展2-d 树,我们可以得到k-d树。这些数据结构的具体实现方法这里不说了,书上本来也是作为一个习题介绍的。
    书里的第7章花了近50页介绍并分析各种排序算法,分析得很全。其中第11节花了10页介绍外部排序。所谓外部排序,就是说怎样快速地把一个根本无法全部读入内存的大文件进行排序。很多排序之所以可行是因为它们可以随意读写任意一个指定的数。但在大文件里,我们无法实现“第1234567890个元素和第 123个元素交换位置”,更无法实现递归之类的操作,而只能像磁带一样“过一遍”,从头到尾扫一遍,由于文件太大内存不能接受,因此必须要读一截扔一截。于是,外部排序产生了。不要以为这个限制会把排序速度拖得很慢。事实上,外部排序同样突破了O(n^2)的界限。它借助了归并排序中的“合并两个已经有序的数组”的思想,因为这个操作可以边读就边做。把文件先拆成两个文件,再把每个文件处理成一段一段的等长有序序列(一段多大取决于内存能一次处理多大),然后不断从两个文件中各取一段出来合并。可以看到,每段有序序列的长度变长了,变成了2倍长。过不了几次,这个长度将变成文件的总长。注意,我们必须要让每次合并时为下次合并做好准备(就是说合并后的结果仍然要是两个分了段的文件)。一个好的方法是将合并的结果交替存在两个不同的新文件中。
    第9章讲图论算法。讲了图的遍历(广搜和深搜)、AOV、AOE、Dijkstra、网络流、Prim、Kruskal和NP问题。在讲深搜时,我学到了两个新东西,用线性时间查找割点(去掉了的话图就不连通了的点)和强分支(有向图中的一个分支满足其中任两个点之间都可以互相到达)。后来发现黑书上也有,又觉得这个东西很不好说,因此这里不想说了。说到了黑书还想顺便补一句:黑书真的看不得——太多错误了。不是说LRJ怎么了,LRJ在真正的大问题上有他的思想和经验,但很多细节的概念他也是昏的,这不利于初学者接受知识。不信哪天我还要写一篇日志纠正黑书的错误。引用政治书上抨击“人性自私论”的经典语言:“从理论到实践都是错的”。
    第10章讲“算法设计技巧”,大概是些贪心啊,分治啊,动规啊,回溯啊,随机化啊之类的。调度问题、Huffman树、装箱问题近似算法、最近点距分治算法、最优二叉查找树、Floyd-Warshall、跳跃表、Miller-Rabin素性测试、博弈算法等都在这章中有讲,并且讲得相当好。由于这不是本书的重点内容,这里也不说了。
    第11章整章都在讲摊还分析。这是一个相当复杂的问题,是分析时间复杂度的一个有力工具。它的分析告诉我们的不是某一个操作的复杂度,而是重复执行某一个操作的平均复杂度。研究这个是很有必要的,因为我们会遇到一些“越变越慢”的退化情形和“自我保持不变”的自调整性等数据结构,单个操作并不能反映它真正的效率。

    到这里,这本书的所有东西都已经介绍完了。总的来说,这本书很值得一看(虽然有些地方翻译得很差)。它的理论性很强,证明过程完整(再复杂的分析它也证明得很清楚,满足那些刨根问底的人);整本书自成一个体系,前后呼应;习题具有研究性,与课文互相补充。事实上,这些都是国外教材共有的特点。这算是我完整读过的第一本国外教材,今后我还会读一些。这几天在看《组合数学》(仍然是这个出版社出版的),看完后也打算写一下“对《组合数学》一书中部分内容的形象理解”。读一本国外教材,你会发现它与国内书籍的不同并会从中获益更多。

    这篇文章就写到这里了。号称是一个5000字缩写,没想到写着写着已经超过8000字了。而且,这个并不是缩写,而是一些简单的、系统的、清晰的、形象化的思想和理解。这篇文章或许对已经知道一些有关知识的人有用,但不适合一点也没有接触过数据结构与算法分析的人。如果有一个人能从中收获一件东西,我写这个的目的也就达到了。

(完)

Matrix67原创
做人要厚道 转帖请注明出处

May 31

    堆,就是一陀一陀的东西。头重脚轻不算堆,要上面小下面大才算一个堆。堆是一棵二叉树,满足下面的始终比上面的大。它和二叉查找树比较起来既有好的又有不好的:好的就是要想知道数据里的最小值时根本就不用找了,直接就是最顶上的那个了;不好的就是堆除了这个以外基本上不能做别的事了。除了最顶上的那个以外,你几乎没办法控制其余的部分。当然,插入和删除数据这种基本操作还是可以做的。插入就是把数据暂时先放在最下面的某个位置,然后通过与它上面一个进行比较、交换不断往上冒直到已经到了自己的位置不能再向上为止。删除反起来,通过不断交换往下沉一直沉到底。因为是往下走,所以要考虑到一个把左边的放上来还是把右边的放上来的问题。当然,为了保证堆上小下大的性质,应该把小的一边换上来。刚才说过,由于你只能“看”到最顶上的东西,不知道中间部分是什么样,我们通常只删除最小的(最上面的)那个节点。其实堆还有一个最大的好处:容易写代码。因为我们可以有意让数据把树“排得满满的”,满到它是一行一行挨着排下来的。这叫做“完全二叉树”。我们可以给完全二叉树编个号,从上到下从左到右挨着数下来。根是1,找左儿子就乘2,找右儿子就乘2加1,找它爸就 div 2。以后叫谁就是谁,很方便。这样整个树就可以用一个数组实现了。由于堆基本上只用来找最小,因此如果某个问题要求很复杂的话,最好还是用成二叉查找树;当然,如果问题只要求插入、删除和找最小三种操作,你应该毫不犹豫地选择堆,毕竟找最小时堆方便得多,写起又简单。什么时候出现这种问题呢?比如说,我的女友排起队的,我每次要选一个最纯洁的,就是受那些的影响最小的人。每当我遇见了一个新的美女,我就把她放在这个队伍里合适的位置供我以后娱乐。这时,我只关心每次插入、取最小和删最小。这个队伍就可以用一个堆来优化。因此,堆还有一个形象的名字叫优先队列。如果谁问题目要求不找最小找最大怎么办,那人肯定是个傻子,把堆变通一下,上大下小不就完了吗?

    研究堆麻烦的地方就是堆的合并。如何把两个堆合并成一个堆?这个解决了很有用,至少上面的这些操作跟着全部统一了:插入就是与一个单节点的堆合并,删除根就是把根不要了,把根的左右两边(显然还是堆)合并起来。一个简单的办法就是递归地不断把根大的堆往根小的堆的右边合并,把新得到的堆替换原来的右儿子。注意递归过程中哪个根大哪个根小是不停在改变的。这样下来的结果就是典型的“右倾错误”,而且破坏了完全二叉树的完美。为此,我们想要随时保证堆的最右边尽量少。于是,干脆不要完全二叉树了,不过是多写几行代码嘛。这个不存在像二叉查找树那样“某一边越做越多”的退化问题,因为对于一个堆来说,反正我只管最顶上的东西,下面平不平衡无所谓,只要不挡我合并的道就行。于是,我们想到人为下一个能让堆尽量往左边斜的规定。这个规定就是,对于左右两个儿子来说,左边那个离它下面最近的两个儿子不全(有可能一个都没有)的节点的距离比右边那个的远。这规定看着麻烦,其实还真有效,最右边的路径的长比想像中的变得短得多。这就叫左式堆(左偏树)。这下合并倒是方便了,但合并着合并着要不了多少次右边又多了。解决的办法就是想办法随时保持左式堆的性质。办法很简单,你合并不是递归的吗?每次递归一层后再看看左右两边儿子离它下面没有两个儿子的节点哪个远,如果右边变远了就把左边右边调一下。由于我们已经没有用数组实现这玩意了,因此链表搞起很简单。这个对调左右的方法给了我们一个启发:哪里还要管什么到没有两个儿子的节点的距离嘛,既然我每次都在往右合并,我为什么不每次合并之后都把它对调到左边去呢?这种想法是可行的,事实上它还有一个另外的名字,叫斜堆。

    二项堆更强,它也是堆,也能合并,不过它已经超越了堆的境界了:它不是一个堆,而是满屋子的堆。也就是说,找最小值不能再一下子找到了,而是要把二项堆中的每个堆的顶部都看一下。二项堆的合并也很强,直接把根大的堆放在根小的堆的下面。这意味着二项堆的每个堆都可能不是二叉树了。这增加了编程的难度,不过可以用一个叫做“左儿子右兄弟”的技巧来解决问题。这个技巧,说穿了就是仍然用二叉树来表示多叉树:把树画好,然后规定节点的左儿子是下一层的最左边那个,右儿子就是它右边那个。就是说,左儿子才是真正的儿子,右儿子不过是一起生出来的。为了让二项堆好看些,让堆的个数和大小保持在一个能快速操作的数目和比例内,二项堆作出了一个明智的规定:每个堆的大小(总的节点个数)只能是1、2、4、8、16…中的一个,且每种大小的堆只能有一个。若干个互不相同的2的幂足以表示任意一个正整数,因此这个规定可以保证不管多大的二项堆都能表示出来。保持这个性质很简单,遇到两个大小相等的堆就合并起来成为一个大一号的堆。由于总是两个大小相等的堆在合并,因此二项堆中的每一个堆都有一个奇妙的样子,看看本文结束后下面附的一个大小为16的堆的示意图,再看一下,再看一下,你就能体会到了。图下面有一个用“左儿子右兄弟”法表示的同样的树,其中,往下走的线是左儿子,往右走的线是右儿子。

    最后简单说一下Fibonacci堆。保持一个跟着变的数组记录现在某个节点在堆中的位置,我们还是可以对堆里的数据进行一些操作的,至少像删除、改变数值等操作是完全可以的。但这个也需要耗费一些时间。Fibonacci堆相当开放,比二项堆更开放,它可以不花任何时间减少(只能是减少)某个节点的值。它是这样想的:你二项堆都可以养一屋子的堆,我为什么不行呢?于是,它懒得把减小了的节点一点一点地浮上去,而是直接就把它作为根拿出来当成一个新的堆。每次我要查最小值时我就再像二项堆一样(但不要求堆的大小了)一个个合并起来还原成一个堆。当然,这样的做法是有适用范围的,就是前面说的数值只能是减少。在什么时候需要一个数值只减少不增加的堆结构呢?莫过于Dijkstra一类的图论算法了。所以说,这些图论算法用Fibonacci堆优化可以进一步提速。



Matrix67原创
做人要厚道 转帖请注明出处

« 更早的日志