排序算法的哲学思考

1 从世界杯谈起

世界杯分小组赛和淘汰赛.一个小组四支队伍,两两交手,分出1234.

小组赛abcd四队,赛程如下:

ab/ac/bc/ad/bd/cd

一共6场比赛.

淘汰赛呢?假设abcd已经进入四强,要角逐出冠军,亚军,季军,殿军,赛程是这样的:

ab->a(胜者)

cd->c(胜者)

ac->a(冠军),c(亚军)

bd->b(季军),d(殿军)

只需要比4场.但这里有个问题,亚军c和季军b,有争议,有待商榷.因为:

b与c没有直接交手;

而且从比赛结果来看:

a>b;c>d;a>c;b>d

即:

a>b>d;

a>c>d;

无法确认b和c的关系.

c获得亚军,b获得季军,不过是因为c比b更早获得一场胜利而已.更公平的办法是cb再赛一场.


2 世界杯的简化

假设:每个球队的战斗力是恒定的,且各不相等

问题:如何以最少的赛事安排分出名次?

假如现在有4支球队:

最直观的方式是像小组赛那样巡回:ab/ac/bc/ad/bd/cd;需要6场比赛.

但真的需要6场吗?

从上一节的分析可以看出来,按照淘汰赛的办法,在4场比赛的基础上,再加赛一场(bc),就能完全确定名次.因此最少只需要5场比赛即可.那么小组巡回赛的方式有什么问题呢?或者说它是否进行了无意义的比赛?

为了分析这个问题,调整一下小组赛的顺序.

先安排两场:ab,cd;分出胜负,假设a>b;c>d;

然后安排ac:

如果a>c,那么再安排bc;

----如果b>c,那么有 a>b>c>d;结果已经出来了,共需要4场比赛,无需bd,ad.

----如果bc>b;a>c>d;那么再安排bd就行了,一共5场比赛,无需ad.

如果a

----如果b>d,那么有c>a>b>d;共需4场比赛,无需ad,bc.

----如果ba>b;c>d>b;那么再安排一场ad就行了,共需5场比赛,无需bc.

这是扩充淘汰赛的思路(扩充的意思是在经典淘汰赛的规则的基础上,解决亚军季军争议).如果已经排好了一部分,那么应该将这部分结果利用起来,而不是无视它.利用已打完比赛的结果,利用递进关系,利用传递性,可以省去一些无谓的比赛.

如果扩大到8支球队呢?

按照小组巡回的思路,C(8,2)需要28场比赛.

按照扩充淘汰赛的思路呢?

ab/cd//ef/gh

分为两个小组.abcd最多需要5场比赛决出胜负;efgh也是5场.假设这10场赛完,结果是:

a>b>c>d;e>f>g>h;

接下来还需要多少场比赛呢?

设想一下,先让de打一场,如果d>e,两条不等式刚好就接上了,一场比赛就能解决问题

同理,如果h一举战胜了a,也可以一锤定音.

以上是最理想的情况,最差的情况如何呢?最差也只用比7次就行了.

为什么是7次呢?无非是想要排出12345678的名次,假设现在有8个笼子,分别代表第一名到第八名.

() () () () () () () ()

a>b>c>d;e>f>g>h;4对4;

比较两个阵营里领头的队伍:ae,可以确定第一名,进笼.

不管a>e还是e>a,都变成了3对4.再比两阵领头,确定第二名.

以此类推,如果哪个阵营率先全部进了笼子,就不用再比了,另一个阵营就可以按顺序全部入笼.

这样,最多的比较次数就是44,34,33,23,22,12,11,完了.

所以按照扩充淘汰赛的思路,只需要17场比赛就行了.

假如有16支球队呢?

小组巡回赛需要C(16,2)=120场比赛.

扩充淘汰赛需要17+17+15=49场比赛:

16队,八八分组.每八队需要17场角逐,上面分析过.然后:

a>b>c>d>e>f>g>h;    i>j>k>l>m>n>o>p; 需要15场.

小组巡回赛的计算方法很简单:C(n,2);那么扩充淘汰赛有什么规律呢?

 2队需要1场;

 4队:两组两队 2*1场,加上: a>b; c>d; 4-1=3场,一共5场;

8队:5*2+8-1=17场;

16队:17*2+16-1=49场;

公式:2^n个队,需要(n-1) * 2^n  + 1 场比赛.

譬如32队,按照递推:需要49*2+31=129场;按公式计算:n=5, 4*32 +1 = 129.

比较一下小组巡回赛和扩充淘汰赛的公式:

C(N,2)=N*(N-1)/2=1/2*(N^2 - N);

N*(logN - 1) + 1;(log底数为2)

按照算法复杂度的写法:

小组巡回赛为O(N^2);

扩充淘汰赛为O(N*logN);

说穿了,小组巡回赛就是冒泡排序或插入排序;扩充淘汰赛就是归并排序;

冒泡排序和插入排序很直观,但是二分更接近事物的本质.


3 算法的结构

算法是有结构的.

冒泡排序和插入排序,是线性结构,一条数组,轮一遍,轮一遍,再轮一遍.

归并算法,明显是树结构.分治--拆开,拆开,一层层拆开,各行其是,然后收拢.

程序=数据结构+算法.这是最基本的描述,侧重于空间.

而算法本身,其实也是一种结构,它把基础数据结构当作积木,穿针引线,编织出巧妙复杂的迷宫,阡陌交通,假以时日,参数在其中变幻流动.算法是思维的,流动的,时间的结构.

算法以代码符号的形式呈现,但并非所见即所得--循环,尤其是递归让它变得缱绻,深邃,百转千回.

[图:归并排序代码,不太直观]

必须随着时间展开,才能窥见算法的奇妙.在效果上,它像一串风铃,一阵数据飘过,银铃作响;在结构上,像一个花骨朵,随时间绽放.

和艺术对比,算法不是雕塑,建筑,它像一幅画卷,连环展开,更像文学,乐谱,要阅读,演奏,要流动起来.算法不是一目了然的--迷宫,要走一走,才知道奇妙.

[图:归并排序示例,可作为逻辑电路的设计框架]

程序是偏重于过程的描述语言,从一轮轮的过程中抽象出共同的逻辑,然后使用状态(变量)把每一轮衔接,串联起来.这样处理获得了抽象的简洁描述,同时也牺牲了一路直下,开门见山的直观.


4 算法的哲学思考

其一 时间与空间

人类最根本的限制在于寿命太短,短短几十年.所以人类的许多活动都是在拼命制造空间,利用空间以节约时间.

人生苦短,大家都极力寻求时间复杂度更低的算法.一个问题提出来,内在的难度就在那里,不可化约,想解决它,不可避免要付出代价,如果这个代价可以在时空之间转换,那么方法论就有了灵活性,人在各种条件的制约下,可以有适当的选择.人受制于时间更多,所以青睐更快的算法.但整体的时空代价,有一个极限,对应问题本身的难度,因此我觉得时空代价是守恒的.定性地看,在归并算法里,多层递归在栈上翻滚铺张,直觉上,这是个指数行为,也许正是这种指数性的空间挥霍(或者说充分利用),换来了时间上的对数型便利.冯诺依曼一眼看穿问题的本质,归并排序,是时空的协变,协奏.

其二 分治与二进制

分治是很古老的方法,公元前五百年,孙武兵法曰:凡治众如治寡,分数是也.与排序场景更接近的:

利用已排序的对象序列去加快搜索的构想早已在公元前200年的巴比伦尼亚出现.

直到二进制计算机发明出来,人们才重新意识到二分法的威力.

易曰:太极生两仪,两仪生四象,四象生八卦.后来发展成六十四卦.古早的中国人用这套学说演算天文,地理和人事.一分为二,非常朴素简练的思想,在计算机上焕发出强劲的生命力.计算机基于二进制的世界观--1,2,4,8,16,...--像下棋一样,占据数的空间,稀疏扼要.计算机对2的指数幂十分友好,在处理其他数量的时候应该就近靠拢,形成共振的效果.

想到道德经,"道生一,一生二,二生三,三生万物".初为混沌,分一分,分出天地,时空.不管哪种哲学,都无法回避"一生二"--二元论,二律背反,二分法--很自然,没有办法避免.二生三,我觉得是宇宙间出现了生命,从无机之中产生了有机能动性.简化一点,天地人三才,又形成三组"二",不断生产下去.

欧拉公式:e^(i*pi) + 1 = 0;让人感觉e和pi好像是从同一个母体分离出去的. e = lim(n->∞)(1 + 1/n)^n,触碰到时间的神秘;pi,主宰着空间的和谐.

其三 熵与对数

O(N*logN)是基于比较的排序算法的时间复杂度的极限.可以信息熵解释:

H=-sum(P*logP);

信息熵由香农从热力学引入,来源是玻尔兹曼熵公式:

S=k*lnW;

对数核算了事物的成本(或者说本质),反推回去,整个事物的形成构建,可能是一个指数变化的过程.

譬如分形:

[图:谢尔宾斯基三角形,指数变幻]

[图:谢尔宾斯基三角形可由分形树产生]

[图:自然界的分形]

[图:沃尔夫勒姆自动元胞机生成的图案]

分形,指数变化,自相似,树结构,自动元胞机.

莱布尼茨曾经思考过递归的自相似性,启发了分形数学.

沃尔夫勒姆则相信,宇宙的本质是计算,一套简单的规则经过无数次的重复,扩展,最终形成了无比复杂的景观.

也许,那"几样简单的规则"是宇宙游戏的底数,而它扩张的方式,就是指数增长.谁知道呢?

你可能感兴趣的:(排序算法的哲学思考)