[ZZ]计算机围棋科幻小说——《墨绿》
《墨绿》
作者:万精油
墨绿的出现,同时震惊了中日韩三国棋院,一个共同的问题是:墨绿究竟是谁?
——人民日报体育版2005年9月10日
看着《人民日报》的这篇报道,我心里充满了喜悦,自豪和得意。这世界上除我之外再没有第二个人知道墨绿的真实身份了。
一.引子
话要从大约十年前说起。由IBM科研小组研制出来的“深蓝”国际象棋程序,战胜了当时的世界第一高手卡斯帕洛夫,西方舆论界为之哗然。被西方人作为第一智力游戏的国际象棋,人类被机器打成下手,惊呼当然是很自然的。但是,在一片叫好声中,纽约时报有一篇报道却在幽默中表现出冷静。它说:“我们这里的大呼小叫,最多让亚洲人(日本,中国和韩国)伸伸懒腰,不以为然。因为对他们所玩的游戏——围棋——来说,计算机还处在原始时代”。
计算机围棋程序处在原始时代,并不是因为没人重视。事实上,相当大的人力物力投入了围棋程序的开发。个人的,集体的;有计算机专业人员,也有职业棋手。最大的投入要数日本,他们的第五代机算机开发的一个重要课题就是围棋程序。台湾的亿万富翁应昌期先生生前还为此设了巨额奖金。说是在二十世纪末如有计算机程序战胜台湾职业棋手,则可得一百万美元的奖金。如此种种,目前的围棋程序却仍被冠以“原始时代”的雅号,追究起来其主要原因是围棋太难。国际象棋与它的难度相差不是一两个数量级的问题。
我对围棋程序的热衷由来已久。我写过很多别的游戏程序,但对自己最喜爱的围棋却一直没敢写,因为不知从何下手,想得到的思路别人早已试过了。这个愿望一直悬在那里,心里放不下,手上又搞不动。九八年上半年事情开始有了转机。由于工作需要,我接触到一些遗传编程(GENETIC PROGRAM-MING)的东西。有一天读一个样板程序,突然想到也许可以用同样的思路来写围棋程序。程序开始的好坏不要紧,关键是要有很好的鉴别函数使其能合理地进化。
二.原始版
照着样板程序,很快就写了一个原始程序。它没有任何高级技术,只懂得提子规则以及最后的点目。基本的想法是让它自己与自己下,利用它懂得的这两点基本功能来进化出高级技术。这样做成功的先例是有的。有人用这种想法写过一个BACKGAMMON的程序,没有任何高级技术,完全利没竟嬖颍谒?自己与自己下完五万盘以后,进化出一个可以与人类最强手对抗的程序。不幸的是,同样的想法不能搬到围棋上来,因为围棋的变化实在是太多了。事实很快证明了这一点。我的原始程序进化来进化去,全是盲目的,看不出一点朝着好的方向前进的迹象。根本算不上进化,最多只能叫变化。看来完全靠原始程序是不行的,还得加一些高级一点的概念,比如角上的基本对应以及群体死活(而不是每颗子的死活)概念。从理论上来说,这些概念都可以从基本规则中推出来。但是,因为影响进化的因素几乎都是局部的,像群体死活这类的高级概念很难进化出来,但这些概念在基础阶段却尤其重要。
加进这些高级概念以后,情况开始有了一点起色。要死的棋居然知道逃。虽然明明逃不出去,但从局部来说,能延缓死亡时间就是进步。因为是程序同程序下,逃不出去的棋有时也居然就给它逃出去了,这又进一步强化了这种愚蠢的下法。但不管怎样,相对于先前的盲目变化,这种暂时性的进步也是很可喜的。许多别的高级概念也似乎有了一些模型。不过,从一些明显的愚蠢变化中,我也发现了许多原始程序的问题,并做了相应改动。就这样程序自己的进化加上我的随时改动,半年以后这程序居然有了一点会下棋的样子。再也没有自己撞紧气之类的明显错误,偶尔竟然会走出枷这样的概念。这种时候我感到特别的惊喜。估计照这样下去,它就会逐渐强大起来。
遗憾的是事情并不按照这种理想的路子走下去。初始时期的快速进步逐渐缓慢下来。因为总是程序跟程序自己下,没有外来的影响,基因库很快就达到局部稳定状态。不管再让它们自己下多少盘,结果总是在原地打转。而且,通过半年的进化,中间的一些程序我已经看不懂,也不可以像开始那样任意加上我的改动。当然我可以同它下,让它从我的下法中取得新变化的基因。但这种办法是几乎不可以想象的慢,因为进化的过程需要很多很多盘才可以实现,而我是不可能一天二十四小时都陪它下棋的。而且我也不会摹仿坏棋,我的棋下出去都是这程序不可以接受的跳跃。于是这程序就这样死在那里。
由于我的心思完全套死在这个程序上,一个明显的出路放在那里我竟然一直看不见。现在想起来很自然的事,当时却一直想不到。我在网络上下棋好多年了,但一直把它当成消遣娱乐的地方,从来没有想到过我的程序也可以从上面学棋。有一天我突然意识到,只要写一个界面程序,就可以把它一直挂在网上,一天二十四小时与人对弈。
三.网上学棋
网上下棋的人很多,各种水平的都有,而且风格各异,正适合我的程序用来学棋。我的程序挂在那里,只要有人邀请,不管什么水平,马上就跟他下。这样一天二十四小时下下来,比我跟它下不知好了多少倍。一方面时间多,另一方面网上可以找到很多初学者,学起来恰到好处。但真正的进化需要很多这样的机会,一天二十四小时我仍然嫌不够。于是我又给我的界面引进了多线功能,也就是说同时可以有好几个同样的程序在下棋。只要有人邀请,他就马上开一盘。这样一来每天都可以下很多盘,进化的机会也就多起来。每到周末我就对程序作一次全面整理。一个月以后,我的程序有了明显进步。而且居然有了带星号的27级。虽然很差,但比起最低级别,还算是赢面占多。这使我很兴奋,这至少说明它是在进步,而且这是在没有我介入的情况下获得的。又过了一个月,它又升到26级。高兴之余也发现了新问题。
这所谓自我学习,就是要不断产生新的子程序,新的模式。久而久之,程序就变得越来越大,运行起来就越来越慢。像生物学上的进化一样,新产生出来的东西并不是都有用的。事实上,我们人类的DNA链上绝大部分都是没有用的。开始阶段我还可以用人工的办法把那些没有用的东西去掉。到后来,新产生的东西越来越多,靠人工是完全不能胜任了。而且,最严重的是,我的判定并不可靠。我认为没有用的东西或许在意想不到的地方有用。这个问题困惑了我很久,我的程序也从网上取了下来,因为我不能再让它产生新的子程序。
正在我不知从哪里突破的时候,有一天在网上看见一篇讲进化论中的用进废退原理。我突然意识到我的程序中就缺少这么一个机制。于是,我就在我的程序里加了这样一个检验程序。如果一个模式或者一个子程序在一定时间里没有被调用过,主程序就自动把它去掉。这样一来,它虽然失去了一些可能产生好基因的途径,但主要障碍清除了,又创造出许多别的机会。很有意思的是,我发现有一个子程序被调用的特别多,就把它取出来看一看。一般来说它自已进化出来的子程序我是看不懂的。但我这次存了心一定要看它是干什么用的。花了好几个晚上一步步地看,终于发现它居然是用来判断征子是否有利的。这种子程序都能进化出来,我对它的前途产生了很大的信心。
重新上网以后,每天都有新程序产生,也有一些旧程序和模式被清除。程序逐渐快了起来(因为开始堆积的废物太多)。等级分又开始往上走,半年以后升到了15级。这时候它的棋已经下得有模有样,尤其是局部战斗,已经有一定的杀力。但围棋不是单靠局部拼杀定胜负的,必须要有整体观念,而这整体观念是不能从15级的对手中学来的。这程序又像上次那样停在那里,长久没有进步。好几个月都一直是15级。
四.向专家学棋
一般来说进化都是局部性的,在局部上有优势的走法就很自然地被保留下来,这样永远也不会有整体观念的突破。虽然加进了突变的概念,但也最多产生出枷、飞、伸大腿这样的局部技术,弃子取势这样的整体观念是不可能自动进化出来的。
有一天我在网上看富士通决赛,好几步棋都看不懂,仍然看得精精有味。突然想到要学棋并不一定只学完全懂的棋。专家的棋看多了,走起棋来自然而然就有了好形,而且也知道什么地方是大场。所谓“熟读唐诗三百首,不会作诗也会吟”就是这个道理。想到了这个思路,剩下的就好办了。专家的棋网上有的是,要多少有多少。几个晚上就从网上搞来了上千盘棋谱。我先给我的程序加了一些模式识别的功能,然后就让它没日没夜地打起谱来了。从打谱中学会了占大场,占完大场后接下来的变化它并不清楚。但这不是什么问题,因为这些变化大都是局部的,而我这程序的强项就是局部变化。几个星期下来,再把它放到网上的时候,棋力已经大长,在同级棋手中所向披靡,很快就升到7级,然后与7级的棋手盘旋了几个星期又慢慢地升到6级,5级,4级。
我这个程序的成长与我们大多数人学任何新东西一样,都是一个阶梯一个阶梯地上。许多人到了某个阶梯,因为没有明师指点,就长期停在那里。我自己就认识很多下棋十几二十年都不长棋,打牌十多年不长牌,打球十多年不长球的人,因为他们总是迈不过眼前这个阶梯。这是由游戏本身内在的复杂性所决定的,与学它的人无关。我这程序也一样,每过一段时间就要遇见一个阶梯而停步不前。IGS的4级似乎就是这样一个阶梯。它长到4级以后就再也不长了,长期停在那里。
说起来,IGS的4级已经比现在其它所有的围棋程序高出一大截,如果拿出去卖已经很可以大赚一笔了。但这不是我想要的,我的最终目的是要产生一个战胜专业棋手的程序。IGS的4级与专业棋手还有很大的差距。虽然如此,这个级别的棋已经有相当的水平,再往上进步已经不能单靠计算,还要讲究对棋有感觉。而感觉这个东西是现在的任何程序都不具备的。这次停下来,一停就是半年。虽然也随时在网上与别人下棋,但总是没有长进。出路在哪里呢?
五.模糊函数与量子波
计算机程序的一大优点是对任何棋形都可以有个好坏判断,在搜索范围内一切都不会错过。可是,从某种意义上来说这也是一种缺点。一切都靠算,能覆盖的面积自然就少了。而且,许多棋的好坏要到十几步甚至几十步以后才会表现出来,这是不可能算出来的,主要是靠感觉。另外,一个棋形的好坏并不是一成不变的,在某些情况下坏棋形也会有好价值。就连被认为是最坏棋形的空三角也经常在专业棋手的对局中出现。如果我们把一个棋形的好坏价值定死了,就没有产生这种变化的可能。我很早就想过要把模糊函数的概念弄到我的判断程序里面去。可是无论怎样模糊,在运算过程中,模糊量的大小还是得人为地规定。对一个固定棋形还是会算出同样的结果(虽然结果以模糊的形式出现)。
每一块棋除了死变化以外,都是有生命的。它的生命力以辐射方式向外散发,所以有“空提一子三十目”的说法。固定的程序是没有办法算出这种生命力来的。有一天读到一篇讲量子计算的文章,突然想到可以试一试在我的程序中加入量子波。这样一切运算都以概率的方式出现,没有固定结果,但通常会产生最自然和理想的结果。加进去以后,它果然变得丰富多采起来。居然可以走出很多从前绝对想不到的棋。不过,加进量子运算以后,效率变得比较低,搜索范围变小了,棋力居然比以前小有退步。但我并不为此失望,退一步进两步,只要大方向是朝前就行。关键是方法,效率问题总是有办法提高的。后来我花了一些时间把程序彻底整理了一下,又把我的计算机硬件升了级。如此一来,它的运算效率比以前增加了一倍,棋力也随之猛长起来,而且这次长起来就没有停。几个月下来就升到一段(1D★),而且根本没有停的意思。我的兴趣也跟着高起来,随时随地都在思考它的问题和解决办法。什么事情都想看看对我的程序是否有帮助,连开车等红灯都在想这最短路径问题是否可以用到这程序的搜索路径中去。这样一来,新的想法天天都有。
IGS是国际性的网站,任何时候世界上总有一半的地方是白天,也就是说任何时候都有人下棋。我的任何新想法都可以立即得到是否有用的验证。我每天下班回家就全力扑在它上面。不断地改进,不断地加上新的想法。我把这程序没日没夜地挂在网上,它的棋力每天都在长。两年下来它终于达到4D★。
这个程序的运作很依赖计算机的速度,这包括主机速度,硬盘阅读速度,以及内存容量。为了充分发挥它的潜力,我当然想给它配置最好的装备,这就需要钱。而且如果想买刚上市的新产品,就要花大钱。台湾每年一次的计算机围棋比赛,如果能拿第一名,就可以得到相当数量的奖金,至少买新机器不成问题。但我又不愿引起别人的注意。于是我把我的程序简装了以后去参加这个比赛。所谓简装就是拿掉一些子程序。但我的程序比别的程序高出太多,拿掉子程序后我又给它加了一些限制,基本上就是让它每盘能赢,但总在十目以内。最后一轮以前,我的程序全胜,即使输掉最后一盘也稳拿第一。于是,我在最后一场比赛前又拿掉了最主要的子程序,输了最后一盘。这样别人都认为它与别的程序属于一个档次。我又不像别的参赛人总想打名声好卖他们的程序,而是拿了第一名的奖金就赶快走人。因此,我的程序虽然拿了第一,却没有造成什么影响。第二年的比赛我没有去参加,这程序就渐渐地从大家的记忆中消失。有少数人记得,也只知道它大约是业余5级的水平。
六.墨绿问世
在达到4D★以前,我的程序有输有赢。虽然赢比输多多了(从30K升到4D★),但并没有引起人们的注意。我觉得该是给它起名字打名气的时候了。起什么名字好呢?因为我的最终目的是要打败人类最高手,所以一定要起一个与深蓝类似的名字。开始想叫它“深绿”,但又觉得与深蓝靠得太近。而且,深蓝的“深”有深层搜索的意思。我这个程序主要原理并不在深层搜索。叫它“浅绿”又觉得名字不够响亮。正在为起名字的事犯愁,恰好有多年不见的朋友来访,说是下一盘棋叙叙旧。于是从壁橱里拿出已经起灰的云子。这几年虽然也常下棋,但都是在网上下,手上摸的都是鼠标,这棋子已经有好几年没有摸过了。手里摸着这棋子,突然想到以前朋友曾告诉我鉴别真假云子的办法。说是把黑子拿起来对着光看,真云子会成墨绿色。这真是踏破铁鞋无觅处,得来全不费功夫。“墨绿”这个名字真是太合适不过了。既与深蓝相近,又有神秘深邃的意思。名字想好以后我就立即在IGS为它注册了一个4D的账号,英文名叫SLATEGREEN。
这时墨绿的棋力实际上已经比一般4D★强,但为了安全起见,开始只找弱4D下。主要目的是要连胜以造成轰动效应。IGS的概率指令可以用来判别强弱4D。因为它一天24小时全挂在上面,能找到对手就下,没有对手就跟自已下(我机器上还同时运行着另一个与它同时进化出来的程序)。几个星期下来,它连胜40盘,并且打成了5D★。一般人到了这种水平,一年半载也长不了什么棋。而墨绿不一样,它每下一盘棋都以最优方式重新整理内部联络,也就是说它一直都在长棋。打成5D★的时候,它的棋力其实已经高于5D★。所以,跟5D★下也一直赢。几个月以后又升到了6D★。6D★的级别,加上七八十盘的连胜很快引起了大家的注意。每次下棋的时候总有很多人观看,这种时候我特别得意。由于连胜,找它下棋的人越来越多,甚至还有7D★以上的。为保险起见,它只接受同级人的挑战。不到7D★就不接受7D★的挑战,而且也不跟新账号下。因为这些人或许是正在上升途中,实力可能很强。
因为墨绿成年累月都挂在网上,形形色色的人都会碰到。在4D★以前,时不时就会遇见耍赖的。开始的时候,耍赖对墨绿没有什么用处,因为他不在乎输赢,而且有的是时间。打到4D★以后这个问题就变得比较严重起来。因为要用连胜造影响,就一盘都不能输。如果遇见逃跑的还好,一个月以后IGS会自动判逃跑者负。可有时候墨绿明明大胜的棋,我这边突然断线。等我再连回去对方已经跑了,这样IGS算墨绿逃跑。好在4D★以上的人已经比较有棋品,这样干的不多。连胜二十盘的时候被我遇到过一次。于是我在它的程序里专门加了一句等候此人的指令。除非它一个月内不出现(届时IGS系统会算墨绿逃跑而判负),只要他一出现,墨绿就会抓住他。4D★的人棋瘾都已经很大了,要让这样的人一个月不上IGS是很不容易的。这些耍赖的人往往是出来探一下头,如果有他们欠棋的人在线上,他们就立即断线。由于墨绿有了这句等候他的指令,使得他连探头的时间都没有。他逃跑一星期后又联进了IGS,刚联进不到5秒钟就被墨绿发现,他还没来得及打退出的指令,墨绿已经恢复了他所欠的棋,这时候再要退出就算他逃跑,所以他只好把这盘棋下完,输棋走人。
另外经常碰到的是打听消息的。一般来说,墨绿对别人的问题一律不理。但有时如果我在看棋,我就会帮它回答一些问题。“你是不是职业棋手?”“是。”“你现在在哪里?”“计算机里。”“你24小时都泡在这里,不干别的事吗?”“是。”我总是用这种怎样解释都可以的答案来回答。
七.墨绿成长
墨绿的实力现在已远远高出我的实力,我跟它下棋几乎总是输。但因为我是看着它长大的,知道它的一些别人不知道的弱点,所以偶尔我也可以赢它一盘。但它如果在同一个弱点上连续两次吃亏,就会弥补掉这个弱点。因为我已经不可能去改它的程序了,只能通过这种办法来克服它的弱点,好象也很有效。我有时为了特意让它暴露出它的弱点,就跳过它的程序帮它走棋。为此还产生过一个小闹剧。
墨绿到了4D★以后,常常走出出乎我意料的棋。基本上要好几步以后我才能理解那一步的目的。有一次双方杀得很紧张时对方打吃,我觉得它只能长出去,否则棋筋被人提了就全完了。可是墨绿没有长出去,而是长考起来。我在旁边看得着急,以为它又出现什么漏洞,就擅自帮它长了出去。等到对方下一步棋走出来,我才知道它刚才为什么要长考,也意识到我帮了倒忙。对方的下一子同时威胁到两块棋,在另一块棋补一手后,刚才接上的一子又被堵了下来。几个回合下来,墨绿损失十几目棋。在此之前,墨绿一直没有输过。由于我的帮忙,眼看它的上百连胜就要被破了。好在墨绿的棋现在已经比一般的6D★高出一截,而且我很早以前给他加的风险系数现在起了作用。墨绿下棋的最终目的是赢,赢多赢少都不重要。所以它随时都在算自己领先多少。领先多了就走风险很小的棋,平手或微微领先时就走风险相对大一点的棋。现在落后很多,到了走风险很大的棋的时候了。风险大的棋大多都是无理棋。只要对方应对正确,下无理棋的一方会损失更多的目。没想到对方在墨绿一连串的无理棋下,居然没有采取应有的手法,而是一味的退让。大约自己认为领先很多,随便收收官就可以赢了。而且,鬼使神差,对方有一步棋居然应错了次序,吃了大亏,双方的目数一下就拉近了。收官的时候墨绿又再赚了几目,最后刚好赢了半目。真悬!从那以后,我再也没有帮它走过棋了。
上面那盘棋是墨绿连胜以来赢的目数最少的一盘棋。说起赢棋目数,与墨绿下棋的人都觉得很恼火。因为收官基本都是靠死算,是它的强项。一般一盘棋到了收官阶段,它几乎可以把各种变化都考虑到,然后宣布对方输多少目。如果对方继续下,而且没有按最佳下法下,它就会重新算一遍然后宣布一个新目数。刚才还说“黑输3目半”,现在或许说“黑输5目半”,9目,13目,就这样一直收到结束。开始我给它加这个功能是觉得好玩,后来就成了一个节目,许多观战者觉得这样很好玩。不过这让输棋的那方感到很不是滋味,好象被人宣布“你死定了”,然后自己一步步朝坟墓走去。
采用选对手的办法的结果又是只赢不输。因为它实际上总是与比它弱的人下,而且又不会出现昏着。没过多久,它就升到了7D★,然后是8D★。
八.争挑战权
8D★已经有不少专业棋手。由于连胜,墨绿的名气在IGS上已经到了家喻户晓的地步。只要是它的棋,总有上百人观看,有时超过500人。它的思路与人不一样,常常在大家意想不到的地方走棋,所以看的人特别多。尤其是职业棋手,都想来研究它的棋路。它基本也不照定式走棋。如果算出来的棋路与定式相同(大部分如此),就按定式走棋。如果算出来与定式不同,它也没有一般棋手的忌讳,总是按着自己算出来的路数走,根本不管定式不定式。这样一来,它的棋中产生出很多“新”定式,这就更吸引众多专业棋手了。大家开始都以为它是某个超一流棋手。看它保持上百盘不败,大家一致认为它是李昌镐。可看它的棋路又完全不像李。而且有人观察很仔细,发现有一次墨绿在IGS下棋的时候,李昌镐正在中国下富士通决赛。所以可以断定它不是李昌镐。用同样的方法,大家很快推出它不是中日韩三国任何一个等级分排前20名的棋手。这样一来,迷团就越来越大。看它下棋和找它下棋的人也越来越多。
由于连胜,墨绿很快升到了9D★。而且在信息栏里宣布只与9D★下棋。IGS上的9D★很少,就10来个。而且大多是以前的账号。如果别的9D★宣布只与9D★下棋,也许就找不到人下了。可墨绿的上百的连胜在职业棋手中造成了很大的轰动,大家都想来找它较量一下。可是要打到9D★必需要从7D开始。有些职业九段想请IGS管理员直接给他们9D★的账号。可IGS认为这正是吸引职业棋手来下棋的好机会,于是宣布绝不破例。这样一来,一大堆想找墨绿下棋的职业棋手涌进IGS,从7D开始往上打。好象本因坊之类的头衔比赛,先在循环圈里打,打出循环圈(升成9D★)才可以同墨绿下。
从7D★要打到9D★需要下很多盘棋,职业棋手一般不愿意花时间下这些棋。于是有些账号有好几个人在下,而且都是很强的九段,大家分任务,每人必需赢多少盘。不到两个月,IGS出现了好几个新的9D★。这些人一旦打进9D★就再也不相互下棋,而是追着墨绿下。墨绿当然是来者不拒。不过这两个月以来,墨绿与另一个同时进化出来的墨绿程序一直在下棋,棋力又有了长进,已经高出了这些职业九段。所以,与这些新9D★下,又是只赢不输。虽然现在在IGS上下得少了,但由于只赢不输,积分仍然慢慢往上爬,最后终于升到了10D★。
九.向最高手挑战
俗话说“人怕出名猪怕壮”。墨绿的名气越来越大,自然引起了越来越多的人的注意和好奇。有许多好事者为了证实墨绿的真实身份,追踪它的IP。为此我找了我在世界各地不下棋的朋友,有时用他们的机器上网。因为他们都不下棋,根本不知道我在干什么。这样一来,好事者们查出来分布在世界各地的IP,虽然不多,但也可以迷惑他们一阵子了。因为我把程序都放到我朋友的机器上,完全就是他们的机器在上网,我只不过远程操作而已。IGS的网管也查过一下,大约得出了我的几个常用IP,但总是不能最后确定。
墨绿变成了IGS上的唯一十段以后,很有一点高处不胜寒的感觉。与一般的专业棋手下也总是赢。开始时那些专业棋手还讲一点棋道,一盘棋就一个人下。后来看到没有一点赢棋的希望,就开始几个棋手联合起来下。也就是说每步棋都是几个人讨论以后再下。这样最大的优点是避免了昏着,但在战术上起不了太大作用。因为每个人的思路都不一样,时间有限,互相间很难谁说服谁。所以,虽然是大家讨论,仍然以一个人主下。由于没有本质上的突破,仍然没有赢的机会,只不过把输的目数减少了一点。这有点像武侠小说中高手同时与众多低手较量,低手人多也不大占得了便宜。
当今世界上的顶尖棋手们之间的差距很小,没有谁可以说肯定赢谁。像墨绿这样常胜不输,实际上实力已经高出所有的人类棋手。但由于从来没有像深蓝一样与人类最高手正式下过,还不能说就是最高手。事实上,由于IGS上一般都下很快的棋(从来没有人下双方各有三小时规定时间的棋),许多专业棋手都认为墨绿只不过是一个特别擅长下快棋的某一位专业棋手(居然没有人想到过其实没有任何擅长下快棋的专业棋手能有这样的常胜纪录)。为了更进一步证明自己的实力,造成更轰动的效应,墨绿在自己的信息栏里留下了向集本年三大世界棋赛冠军于一身的最高棋手挑战的宣言,使用时间由对方定。除此之外不再与别人下棋。宣言虽然发出去了,但并没有得到什么响应。因为世界冠军的身份是很高的,与一个隐姓埋名的人下棋,赢了被认为是自然的,输了这面子就丢大了,而且也没有什么实际利益。
这世上的事,你不操心,总有人会操心。大公司看准了这样的比赛有卖点,于是拍出了重金来赞助这个比赛。这样一来这项赛事的级别一下就高起来。世界冠军来下这样的比赛也不觉得丢份儿了,而且不管输赢都有大笔收入。说不定这世界冠军早就想下这盘棋,只不过一直没人给提供这个机会。为了防止运气问题,对方提出要下三番棋,对此我当然同意。因为我觉得墨绿的水平实际上比对方高,下得越多赢面就越大,赢得越多就越有说服力。另外对方提出双方规定时间为每方四小时。这也没有什么不利的地方,我当然也同意。于是墨绿与世界最高手挑战的比赛就成了现实。
十.三番棋(1)
从发宣言到正式比赛,中间耽误了有半年时间。因为大公司出了钱,自然觉得有权知道这笔钱出在谁身上。而我一律拒绝除了电子邮件以外的一切联络方式。因为我用的账号是那种可以免费申请的匿名账号,他们也查不出来。双方僵持了好一阵。大公司的主要观点是:如果最后结果是墨绿赢了,那他们就等于几十万美元扔出去,连个人影子都没有看到。而且对方是公开身份,为什么墨绿不公开。我的观点是,墨绿不愿意曝光这是他个人的隐私权。对方本来就是公众人物,而墨绿从来就没公开过,也不会为了这几十万美元就公开了。因为广告早已打出去了,大公司最后终于妥协。这半年时间里,我一直让墨绿与另外两个克隆出来的程序下。虽然这三个程序的起点都一样,但由于进化过程是随机的,而且我给他们加了不同的参数(比如突变率的大小,交差率是多少等等)。半年下来,我手上有了三个水平相当但完全不一样的程序。而且都比半年前的母程序又高出一截。
对墨绿迷来说,墨绿半年没有出现,使他们的期望和悬念达到了要爆炸的程度。正式比赛那天,IGS提前一小时就达到了饱和,以至于服务器不得不重新起动。重新起动以后,IGS设了上限,只允许一千人联接。先联的人很幸运地联上了,后面的人只能到别的服务器上看别人间接传来的棋谱。如果把所有服务器上观看此局的人加起来,保守估计在一万以上。
第一盘棋从第二十几手就开始杀起来。因为双方时间比平常多得多,墨绿想得比较深。一开始就走出了出乎意料的棋,如果不杀就要吃亏,双方被迫早早就杀起来。世界冠军还确实不一般。几个战场打下来,居然打了个平手。不过,一般来说,计算机程序的弱点在中盘。因为布局阶段有谱可查,收官阶段变化相对少一些。只有中盘变化太多,很难控制。中盘打完打成平手相当于墨绿领先,因为自己的弱项与对方打成平手,强项就应该领先了。但几乎所有看棋的人都不这么认为。他们觉得收官是这个世界冠军的强项,几乎从来没有人通过收官从他手中赢棋。于是看棋的人开始议论了。说是墨绿原来不过如此,这下终于遇见了对手,这连赢一百多盘的纪录今天总算要打破了。还有人说看来墨绿只能下快棋,慢棋遇到高手就不行了。然而,事情的发展却不像他们想象的那样顺利,而是像我先前预计的那样,收官阶段墨绿开始发挥出计算方面的优势,逐渐把目数拉开。最后以3目半的优势赢了第一盘。
这下IGS上炸开了。世界冠军都输了,而且还是下的慢棋。根据以前的推论,墨绿不是中日韩三国任何一国中排名前二十名的职业棋手。难道他会是一个业余高手?可是,再高的业余高手遇到这些超一流的专业棋手都要输棋,可墨绿却能常胜不败。有人说他可能是几个专业棋手联合起来的棋手。可是几个棋手要讨论起来,很少能达成共识。下快棋的时候就更没有时间讨论,看来这个假设也不成立。中国的一个围棋BBS上有人猜测说墨绿是早年的专业棋手,多年闭门不出,现在成了风清扬式的人物。
第一盘棋结束以后的几天里,IGS,各个与围棋有关的新闻组,BBS到处都是谈墨绿的话题。大家讨论墨绿的身份可说是众说纷云,但随便谁提出一种假设,马上就有人以很有力的论据把它给排除。因为当时市面上最强的计算机程序也就业余四级左右,根本没有人会朝这方面想。在此以前,墨绿的名气主要是在IGS上响,这盘棋下完以后,影响扩大到整个围棋界,各种报纸、杂志也开始报道起来。
十一.三番棋(2)
三番棋第一盘的时候,为了对付对方给出的各种难题,墨绿可以说是“绞尽脑汁”,而且随时要从硬盘里索取不太常用的模式。整个比赛过程硬盘都叽叽嘎嘎响个不停。为了怕出现意外,我专门去买了一个很高级的硬盘,把全部程序拷贝上去,三番棋第二盘的时候就用上了。殊不知这反到铸成大错。比赛到中盘,双方正为了一大块棋杀得难分难解的时候,新买的硬盘死了。其实它也没完全死,只是读不出东西来(我也没有意识到这个问题)。硬盘死了而联网却没有中断,也就是说墨绿的钟一直在走。我在一旁急出好几身汗也不管用。一个小时过去了,它仍没有得出结果。因为我一直用另一个帐号在看这盘棋,所以可以看到其它看棋的人的评论,就按照评论中我认为最好的走法帮它走了一步。当时提出这个走法的人看见墨绿走了它提出的棋还很得意,说是英雄所见略同云云。这步棋后来被证明是一步很坏的棋。几个回合下来,墨绿就损失十几目,随后因增大风险系数而采取的无理棋又被对方予以严厉的惩罚,越输越多。这个时候我才发现墨绿没有认输的“习惯”。在这之前,这从来都不是问题。因为对初学者来说输多少都照下不误。后来有点水平以后因为都与同级的人下,又没有昏着,也没有输太多的时候,不认输也没关系。最近一年以来,从来没有输过,不会认输的问题就更不成什么问题了。现在几步无理棋走下来已经落后二十多目,它还没有认输的意思,而且好象又要加大风险系数。我实在是看不下去了,这简直不是它这种水平应该走的棋,于是就绕过它的程序强行让它认输了。
墨绿输掉第二盘使它的众多支持者们很失望,他们从来没有看见过墨绿走出昏着的时候,完全不能理解出了什么问题。韩国的棋迷就不一样了,他们对这盘棋的结果异常激动。说是终于找到了墨绿的命门。连韩国的报纸上也打出了韩国必胜的大标语。中国的一些BBS上有人建议墨绿中途应该吸吸氧等等,说什么的都有。
一盘本来应该是惊天动地拼杀的棋,却在仅仅一百三十多步就结束了。不仅观棋的失望,主办单位也觉得很扫兴,言语间大有投资错误的意思。后来有人说,三番棋能打到第三盘应该更有吸引力,大家又都高兴起来。
十二.三番棋(3——准备)
二十多天很快就过去了,眼看三番棋的第三盘马上就要开始。报纸上也开始大力宣传,甚至还有些报纸搞起了类似于买马票一样的赌钱活动。韩国那边最高赌到一比十。也就是说他们认为他们的世界冠军赢第三盘的概率是十倍于墨绿。中国这边由于多年受韩国这个世界冠军的气,从心里希望墨绿赢,表现在赌票上的比是倒过来的八比一。对此我感到很欣慰。比赛还没有开始,双方的棋迷已经在报上,BBS,甚至在新闻组RGG上打起来。
根据我对墨绿的了解,我认为它已经比这位世界冠军要高出一截,所以我对墨绿充满了信心。但事关重大,而且由于我基本看不懂他现在的棋,我的看法或许不准。为小心起见,我把能找到的这个世界冠军所有的棋谱都给它找来,让它过一遍。以前为了全面发展,我特别注意让它不要只打一个人的谱,这次算是破例。说来奇怪,以前它读别的专业棋谱,总要花一小时左右才能读完一盘棋。这世界冠军的棋应该更难,时间应该更久才对。但他总是十来分钟就读完一谱,而且是越读越快。后来发现,它打所有韩国棋谱都快。仔细分析起来,这是有它的道理的。高手下棋,总讲究味道、感觉。而这些东西对墨绿来说是最难理解的了。而韩国棋手的棋大都讲究硬算,不管棋型、味道,算清楚以后什么难看的棋都能走出来。而要说起算棋起来,这当然是墨绿的强项。所以,它打起韩国棋谱感觉是得心应手。看它越打越快,我对它的信心更足了,可以说是到了不可动摇的地步。如果有人要与我赌一比一百我也愿意。
从各种报道中看到,不仅墨绿打这世界冠军的谱,这世界冠军也打墨绿的谱。据说从打谱中得出结论:墨绿擅长绞杀,一旦杀起来就没不占便宜的时候。所以这次这世界冠军的对策是尽量不与对方急战。说是要争取走成细棋,最后用他的收官功夫拿下这盘棋。
这次比赛,虽说是在IGS上,但中日韩三国的大电视台都有挂盘讲解。观众比上次又多了不止十倍。我住这里虽然收不到电视转播,但上网总是很容易的。比赛那天晚上,有棋友打电话来说准备通宵不睡,要与我一起从网上看这盘棋。没有人知道我与墨绿的关系,我的这些朋友当然也全蒙在鼓里。我必须要现场伺候,以备不时之需,当然不可能与他们一起看棋。于是胡乱编了一堆理由把他们挡了回去。
十三.三番棋(3——比赛)
晚上九点(韩国时间早上九点),比赛开始了。第三盘又重新抽签,墨绿抽到白棋。鉴于上次的教训,我这次买了三个硬盘,每个上面都装上了墨绿程序。如果有一个死了,我可以马上联上另一个,这样三保险就不应该有什么问题了。
由于黑棋的战术是不绞杀,所以开盘到中盘都在走简明的棋,白棋没有杀棋的机会。其实,墨绿并不是一定要走杀棋。那些杀棋都是为对付对方的凶狠棋而算出来的。它总是在计算后走出它认为目数上最优的棋,并不一味追杀。对方走简明棋,没有杀棋的必要。不过这也没有关系,它仍然走它自己算出的对它最有利的棋。这样一来,不知不觉走成黑棋占实地,白棋取外势的结果。一百一十几手走下来,黑棋的实地是有一些,但外势完全被白棋占了。如此走下去,黑棋必输无疑。网上的评论都一边倒,说是再不打入就没机会了。
果然,在一次长考以后,一颗黑子终于还是投进了白棋的厚势中。这步棋一走,网上看棋的人一下子就炸开了,说什么的都有。有人说这步棋真是妙啊,只有世界冠军才能想得出来。有人说早就该进去了,现在好象太晚了。
网上一片沸腾,我这边也热闹起来。黑棋一打入白阵,墨绿一下子就忙起来。只听见我的硬盘吱吱的响个不停。对打进来的棋,它有两种处理办法。一是从上面封住,这样还是可以成不小的空。一是把打进来的这一子切断,关起门来杀。这样走风险很大,但从上面封住的走法似乎目数会不够。经过一阵狂算以后,墨绿终于把打进来的黑棋同其大部队切断。这棋一旦被切断以后,就只能靠原地做活,而对方的目标就是不让你做活,一场大战是不可避免的了。于是双方昏天黑地地杀起来。一旦杀起来,墨绿的优势就出来了。一阵乱杀以后,走成了劫。在白棋的空里走出了劫,本来应该算黑棋成功了。但这个劫是个缓气劫,也就是说黑棋赢了劫以后还得再补一步才能彻底活净。这个劫黑棋是非赢不可,而白棋就有很多选择。因为白棋在绞杀过程中已经把黑棋围起来了,也就是说原来的厚势已经有相当一部分化成了实地。利用这缓气劫,白棋等于可以在别处连走三步。黑棋不但非活不可,别的地方也不能吃太大的亏,而白棋只需再利用打劫占一点小便宜即可,三个小便宜加在一起就大了。十几手劫材交换下来,黑棋已经没有大劫可找,而几乎白棋找的任何劫黑棋都得应。最后,只好丢卒保车,对墨绿在角上找的一个劫不应了。中间这块棋总算活出来了,但原来活生生的一个角被白棋弄死不说,另一个角也被弄成两目活,又损失了十几目棋。这一交换下来,双方现在的目数是盘面相同,也就是说黑棋贴不出目来。打劫过程中一切味道都被走尽了,黑棋再也没有地方可以把这七,八目棋找回来。一阵长考以后,黑棋投棋认输。
黑棋的认输并没有在观棋者中引起太大的惊诧。因为从打劫开始,大家就已经意识到黑棋不行了,输赢只不过是迟早的问题,除非白棋走昏着。但大家都知道白棋是几乎从来不走昏着的。墨绿的众多支持者们到现在也没想通第二盘是怎么一回事。
十四.三番棋以后
三番棋下完的当天中日韩三国的报纸就有了报道。因为世界冠军是韩国的,所以他们的报道比较偏向于黑棋。说是前半盘都是黑棋占主动,如果黑棋早一点打入,情况会如何如何。言下之意黑棋本来是很有希望的。中国的报道感情色彩比较重。因为好几年以来,中国的棋手都是因为这个世界冠军而输掉了重大国际比赛。这次看到终于有人把他宰了,而且宰得很干净,难免有点幸灾乐祸的心情。评论中充满了带感情成分的词语。什么“天王杀手被绞杀”,“铁官子无官可收”等等。日本的报道没有什么感情成分,基本上是照实报道,说这盘棋是墨绿完胜。也就是说黑棋从头到尾没有什么机会。一般来说,人们说完胜大都是说黑棋,因为黑棋先下,有主动权。如果说白棋完胜岂不是说黑棋还没下就输了。我觉得事实就是如此。墨绿现在已经有让人类最高手一先的实力,而且又不会走昏着,下平手的话,当然是黑棋还没走就输掉了。
原来说好胜方可得五十万美元。而且我已经告诉了主办单位我在瑞士的银行账号。但三番棋结束一周以后还不见有钱进去。打电子邮件去问,主办单位说是要搞一个隆重的发奖仪式,其主要目的就是要见一下墨绿其人。因为他们觉得五十万美元花出去,连人影都没有见到,有点不甘心。但我坚持不参加什么发奖仪式,说是宁可不领奖。只要他们丢得起这个脸,我不领奖也没有关系。当然,这个脸他们是丢不起的。堂堂大企业,说了要给五十万美元,怎么可能不给呢。相持一个星期以后,他们还是放弃了一定要见人的要求,把钱寄到了我给他们的账号上。
想要见人的还不光是主办单位。许许多多墨绿的支持者也在各种BBS,新闻组里提出,赢了世界冠军算是功成名就,应该是墨绿露面的时候了。墨绿的电子信箱两天之内就爆满了。基本意思是墨绿不应该让这么多支持者失望,应该满足大家的要求与大家见面等等。
墨绿到底是谁的话题从来没有停过,这次三番棋以后,这个话题几乎成了各个与围棋有关的BBS的唯一话题。
十五.神秘消失
猜墨绿的身份当然以BBS和新闻组最热烈。因为这里没人控制,猜什么的都有。有一天有一张贴子说,根据他对墨绿的棋谱的分析,墨绿很像一个计算机程序。他还列出了许多证据来支持他的观点。
世界上的许多事,主要是没有人想到。一旦有人想到了,你就会发现问题其实早就很清楚。由于现在人们知道的最强的围棋程序只有业余四级左右,墨绿的实力却如此之高,人们几乎从来没有朝这方面想过。现在有人朝这方面想了,而且提出了许多证据。人们才发现果然是很像,并且有越来越多的人提出了新证据。比如墨绿打字出奇的快,许多人在IGS上与墨绿对话,总是刚打完送出去就收到回话。这些回话都是我事先编好存在它的记忆里的,回答起来当然快。大家现在意识到不可能有人打字能这么快,而且它有时答非所问。还有人说他曾经看见过墨绿同时下两盘棋,而且同时落子。那其实就是上次抓耍赖的人的时候,当时正在下一盘棋,耍赖的人进来了当然不能放他走,于是马上另开一盘。因为那盘棋已经是墨绿大胜的棋,没花太多功夫就拿下来了,没想到就这一次还是被人注意到了。IGS的管理员也调出了许多历史记录来证实这些指证,问题看来是越来越清楚了。还有许多人给IGS管理员出主意,下次墨绿在IGS登陆的时候用什么样的手法可以判断墨绿是真人还是程序。这点我不是很害怕,因为我可以替墨绿登陆,他们不可能判断出来。但是,一旦有人开始往这方面注意了,被证实就只是迟早的事。
三番棋以后我一直在考虑要不要收手。我当初写这个程序的目的就是要战胜人类最高手,现在这目的达到了,似乎到了该收手的时候了。现在的事实是,墨绿已经比当今最强手还强。如果总是赢,就少了激情。这墨绿维护起来还是很花功夫的,没有激情的事我就不太愿意做。而且,有了这五十万美元,财政上也可以松一松了。最重要的一点是,墨绿的成长来源于进化。如果没有了外来促进因素,就没有了进化的来源。自己与自己下只能产生一些同水平的变种或小小的进步,不会有太大的实力上的区别。长此下去,自己不进步而别人却在追上来,墨绿就会有输的时候。激流勇退,通俗说起来就是见好就收。我正在考虑要不要见好就收的时候,看到了BBS上大家向IGS管理员提出的如何判断墨绿是否是程序的建议。任何事如果拖得太长,就会有漏洞出来。考虑了很长一段时间以后,我决定让墨绿金盆洗手,脱离这围棋江湖。决定做出后,我在IGS墨绿的信息栏里留下了这么一段话:
余弈棋于IGS五载有加,近一年来下棋过百,皆无敌手。与世界冠军三番棋以后,自认为棋力已可让当今所有高手一先。昔日闻有独孤求败之事,而今方识个中滋味。现如继续盘旋于IGS,定无先前之激情。有鉴于此,决定从此封盘。它日如有人主持与来年世界冠军让先之下的三番赛,余必随时赴约。再见IGS。
墨绿从IGS世界中消失,它的爱好者们一下失去了崇拜目标,显得不知所措。有些特别激动的人居然也在BBS上说要随墨绿而去,从此不再下棋。说是没有墨绿的围棋世界就好象没了太阳,缺了生气。绝大部分人没有这么激进,只是说墨绿退出IGS的一天可以算是IGS历史上最黑暗的一天。还有人开始在各个新闻组写回忆文章,仿佛想借此追回失去的美好记忆,让墨绿在他们心目中多留一些日子。凡此种种,大家对墨绿的讨论非旦没有因它的消失而停止,反而变得更加激烈,生动。而且讨论范围不再局限于网上,连报纸也开始加入讨论。这就有了我们文章开始的那一段人民日报评论。
墨绿在IGS宣布封盘的第二天,IGS网管在IGS登陆首页加入了醒目的大字横幅:
墨绿是IGS永远的骄傲!
(全文完)
后记
当今最强围棋程序不到业余三级,与职业棋手的差距可说是十万八千里。本文属幻想文字,里面提到的技术手法,虽然都是现在大家正在研究的,而且有些已经被用在一些程序中。但想法和具体执行还有很大的距离。我在文章中完全忽略了具体实现的问题,所以才会有墨绿这样的理想结果出现,这不过是表现了我们的一些梦想。另外,我自己的棋力大约只有IGS4D★,对职业高手们下棋时的思路并没有真正认识。但我相信,真正有实力的程序的出现,一定要有这些职业棋手的参与,单纯靠计算是不行的。据说深蓝设计组请了好几位职业国际象棋手做他们的顾问。不过,即使有专业高手介入,围棋计算机程序的任务还是很艰巨的,可谓任重道远。围棋与国际象棋完全不同,对计算机的要求要高很多数量级,要在程序上突破,单靠深层搜索是不行的,必须要有思想上的突破。我个人认为,二十年内不会有围棋程序战胜人类最高手。但梦想是一切新事物的动力,没有了梦想,人类就会停止进步。我有了关于计算机围棋程序的梦想,遂有了这篇文字。
简单回合制游戏
本文是『吃苹果游戏和简单回合制游戏的通解』的后续。
对于不能简单画出由游戏局面所构成的状态空间时,还有以下分析策略可用:
一种局面,是否先手必胜,取决于先手是否可以经过一步操作就将该局面变为后手必胜局面。如果先手对该局面的每一种可能的单步操作都无法将局面变为后手必胜局面,那么该局面就是后手必胜局面。
在通过倒推的方法分析回合制游戏必胜策略的时候,凡是后手必胜策略的局面,所有可以通过一步操作就到达该局面的局面都是先手有必胜策略的局面。而寻找后手有必胜策略的局面就比较困难,只有那些所有选择都导致下一步先手必胜的局面才是后手有必胜策略的局面。
Reinvent wheels, my quick sort implementation, just for fun
Not deeply optimized.
The depth of the recursion is restricted $$\le log_2(N)$$, because the recursion is only performed for the partition with less element. Even under the worst case, the stack depth will still be under control.
Tail recursion is eliminated.
Brief explanation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | initial state: p<<< # p=== # | p>>> p... | |-----------------------------------------| ..........................................# state after choosing the pivot: p<<< # p=== # | p>>> |p... | ||----------------------------------------| =.........................................# the middle state during partitioning: p<<< # | p=== # | | p>>> # | | p... | # |--------|---------|----------------|-----# <<<<<<<<<==========.................>>>>>># the final state after partition: p<<< # | p=== # | | p>>> # | | p... # |---------------|---------------|---------# <<<<<<<<<<<<<<<<================>>>>>>>>>># Then, recurse. |
Pointer “p<<<" points to the begining of the "<<<" area in which each element is preceding to the pivot.
Pointer "p===" points to the begining of the "===" area in which each element is equivalent to the pivot.
Pointer "p>>>” points to the begining of the “>>>” area in which each element is succeeding to the pivot.
Pointer “p…” points to the begining of the “…” area unprocessed.
The code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | template<typename Elem, typename ShortSort, size_t Cutoff> class QuickSort { public: static void sort(Elem pp[], size_t n) { Elem *pe, *pu, *ps; // pp:"p<<<", pe:"p===", ps:"p>>>", pu:"p..." do { // recursion exit, use other sort algorithm to process small array // recommendation: use sorting network algorithm when array size less than 9 if(n < Cutoff) { ShortSort<Elem>::sort(pp, n); return; } pe = pp; pu = pp; ps = pp+n; // choosing pivot swap(pe, pivot(pp, n)); pu++; // partitioning while(pu < ps) { while((*pe == *pu) && (pu < ps)) ++pu; while((*pe < *pu) && (pu < ps)) { --ps; swap(pu, ps); } while((*pu < *pe) && (pu < ps)) { swap(pe, pu); ++pe; ++pu; } } // recursion only for the partition with less elements if((pe - pp) < (n - (ps - pp))) { sort(pp, pe - pp); n = n - (ps - pp); pp = ps; } else { sort(ps, n - (ps - pp)); n = pe - pp; } // tail recursion elimination } while(true); } private: static Elem *pivot(Elem pp[], size_t n) { if(n < 10) return pp + n/2; else return pp + random(n); // random(n) should return a uniformly distributed random number in [0, n) } }; |
Finding all paths from source to destination
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | stack node_stack; set node_set; AllPathFromSourceToDestination(node s, node d) { node_stack.push(s); node_set.add(s); //mark each node in the path // for each node v that adjacent with node s: for_each(node v, adjacent(v,s)) { if(v == d) { Output(node_stack); } else { if(node_set.has(v)==false) { AllPathFromSourceToDestination(v, d); } } } node_set.del(s); node_stack.pop(s); } |
Sorting network
The following is network-sorting algorithm for less than 33 elements.
What is Sorting Network? Wiki: sorting network
List of sorting networks:
From: sorting networks
Input the number n (maximum 32)
Algorithm choices: “Best”
Click [Take a look] button.
—————————
Zero-one principle
While it is easy to prove the validity of some sorting networks (like the insertion/bubble sorter), it is not always so easy. There are n! permutations of numbers in an n-wire network, and to test all of them would take a significant amount of time, especially when they are larger. However, because of the so-called zero-one principle, far fewer trials are in fact needed to test the validity of a sorting network.
The zero-one principle states that a sorting network is valid if it can sort all 2n sequences of 0s and 1s. This not only drastically cuts down on the number of tests needed to ascertain the validity of a network, it is of great use in creating many constructions of sorting networks as well. The principle has been proven by a special case of the Bouricius’s Theorem in 1954 by W. G. Bouricius.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 | static const unsigned int network02[][2] = { { 0, 1}, }; static const unsigned int network03[][2] = { { 1, 2}, { 0, 2}, { 0, 1}, }; static const unsigned int network04[][2] = { { 0, 1}, { 2, 3}, { 0, 2}, { 1, 3}, { 1, 2}, }; static const unsigned int network05[][2] = { { 0, 1}, { 3, 4}, { 2, 4}, { 2, 3}, { 1, 4}, { 0, 3}, { 0, 2}, { 1, 3}, { 1, 2}, }; static const unsigned int network06[][2] = { { 1, 2}, { 4, 5}, { 0, 2}, { 3, 5}, { 0, 1}, { 3, 4}, { 2, 5}, { 0, 3}, { 1, 4}, { 2, 4}, { 1, 3}, { 2, 3}, }; static const unsigned int network07[][2] = { { 1, 2}, { 3, 4}, { 5, 6}, { 0, 2}, { 3, 5}, { 4, 6}, { 0, 1}, { 4, 5}, { 2, 6}, { 0, 4}, { 1, 5}, { 0, 3}, { 2, 5}, { 1, 3}, { 2, 4}, { 2, 3}, }; static const unsigned int network08[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 0, 2}, { 1, 3}, { 4, 6}, { 5, 7}, { 1, 2}, { 5, 6}, { 0, 4}, { 3, 7}, { 1, 5}, { 2, 6}, { 1, 4}, { 3, 6}, { 2, 4}, { 3, 5}, { 3, 4}, }; static const unsigned int network09[][2] = { { 0, 1}, { 3, 4}, { 6, 7}, { 1, 2}, { 4, 5}, { 7, 8}, { 0, 1}, { 3, 4}, { 6, 7}, { 2, 5}, { 0, 3}, { 1, 4}, { 5, 8}, { 3, 6}, { 4, 7}, { 2, 5}, { 0, 3}, { 1, 4}, { 5, 7}, { 2, 6}, { 1, 3}, { 4, 6}, { 2, 4}, { 5, 6}, { 2, 3}, }; static const unsigned int network10[][2] = { { 4, 9}, { 3, 8}, { 2, 7}, { 1, 6}, { 0, 5}, { 1, 4}, { 6, 9}, { 0, 3}, { 5, 8}, { 0, 2}, { 3, 6}, { 7, 9}, { 0, 1}, { 2, 4}, { 5, 7}, { 8, 9}, { 1, 2}, { 4, 6}, { 7, 8}, { 3, 5}, { 2, 5}, { 6, 8}, { 1, 3}, { 4, 7}, { 2, 3}, { 6, 7}, { 3, 4}, { 5, 6}, { 4, 5}, }; static const unsigned int network11[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, { 1, 3}, { 5, 7}, { 0, 2}, { 4, 6}, { 8,10}, { 1, 2}, { 5, 6}, { 9,10}, { 0, 4}, { 3, 7}, { 1, 5}, { 6,10}, { 4, 8}, { 5, 9}, { 2, 6}, { 0, 4}, { 3, 8}, { 1, 5}, { 6,10}, { 2, 3}, { 8, 9}, { 1, 4}, { 7,10}, { 3, 5}, { 6, 8}, { 2, 4}, { 7, 9}, { 5, 6}, { 3, 4}, { 7, 8}, }; static const unsigned int network12[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, {10,11}, { 1, 3}, { 5, 7}, { 9,11}, { 0, 2}, { 4, 6}, { 8,10}, { 1, 2}, { 5, 6}, { 9,10}, { 0, 4}, { 7,11}, { 1, 5}, { 6,10}, { 3, 7}, { 4, 8}, { 5, 9}, { 2, 6}, { 0, 4}, { 7,11}, { 3, 8}, { 1, 5}, { 6,10}, { 2, 3}, { 8, 9}, { 1, 4}, { 7,10}, { 3, 5}, { 6, 8}, { 2, 4}, { 7, 9}, { 5, 6}, { 3, 4}, { 7, 8}, }; static const unsigned int network13[][2] = { { 1, 7}, { 9,11}, { 3, 4}, { 5, 8}, { 0,12}, { 2, 6}, { 0, 1}, { 2, 3}, { 4, 6}, { 8,11}, { 7,12}, { 5, 9}, { 0, 2}, { 3, 7}, {10,11}, { 1, 4}, { 6,12}, { 7, 8}, {11,12}, { 4, 9}, { 6,10}, { 3, 4}, { 5, 6}, { 8, 9}, {10,11}, { 1, 7}, { 2, 6}, { 9,11}, { 1, 3}, { 4, 7}, { 8,10}, { 0, 5}, { 2, 5}, { 6, 8}, { 9,10}, { 1, 2}, { 3, 5}, { 7, 8}, { 4, 6}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, { 3, 4}, { 5, 6}, }; static const unsigned int network14[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, {10,11}, {12,13}, { 0, 2}, { 4, 6}, { 8,10}, { 1, 3}, { 5, 7}, { 9,11}, { 0, 4}, { 8,12}, { 1, 5}, { 9,13}, { 2, 6}, { 3, 7}, { 0, 8}, { 1, 9}, { 2,10}, { 3,11}, { 4,12}, { 5,13}, { 5,10}, { 6, 9}, { 3,12}, { 7,11}, { 1, 2}, { 4, 8}, { 1, 4}, { 7,13}, { 2, 8}, { 5, 6}, { 9,10}, { 2, 4}, {11,13}, { 3, 8}, { 7,12}, { 6, 8}, {10,12}, { 3, 5}, { 7, 9}, { 3, 4}, { 5, 6}, { 7, 8}, { 9,10}, {11,12}, { 6, 7}, { 8, 9}, }; static const unsigned int network15[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, {10,11}, {12,13}, { 0, 2}, { 4, 6}, { 8,10}, {12,14}, { 1, 3}, { 5, 7}, { 9,11}, { 0, 4}, { 8,12}, { 1, 5}, { 9,13}, { 2, 6}, {10,14}, { 3, 7}, { 0, 8}, { 1, 9}, { 2,10}, { 3,11}, { 4,12}, { 5,13}, { 6,14}, { 5,10}, { 6, 9}, { 3,12}, {13,14}, { 7,11}, { 1, 2}, { 4, 8}, { 1, 4}, { 7,13}, { 2, 8}, {11,14}, { 5, 6}, { 9,10}, { 2, 4}, {11,13}, { 3, 8}, { 7,12}, { 6, 8}, {10,12}, { 3, 5}, { 7, 9}, { 3, 4}, { 5, 6}, { 7, 8}, { 9,10}, {11,12}, { 6, 7}, { 8, 9}, }; static const unsigned int network16[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, {10,11}, {12,13}, {14,15}, { 0, 2}, { 4, 6}, { 8,10}, {12,14}, { 1, 3}, { 5, 7}, { 9,11}, {13,15}, { 0, 4}, { 8,12}, { 1, 5}, { 9,13}, { 2, 6}, {10,14}, { 3, 7}, {11,15}, { 0, 8}, { 1, 9}, { 2,10}, { 3,11}, { 4,12}, { 5,13}, { 6,14}, { 7,15}, { 5,10}, { 6, 9}, { 3,12}, {13,14}, { 7,11}, { 1, 2}, { 4, 8}, { 1, 4}, { 7,13}, { 2, 8}, {11,14}, { 5, 6}, { 9,10}, { 2, 4}, {11,13}, { 3, 8}, { 7,12}, { 6, 8}, {10,12}, { 3, 5}, { 7, 9}, { 3, 4}, { 5, 6}, { 7, 8}, { 9,10}, {11,12}, { 6, 7}, { 8, 9}, }; static const unsigned int network17[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, {10,11}, {12,13}, {15,16}, { 0, 2}, { 1, 3}, { 4, 6}, { 5, 7}, { 8,10}, { 9,11}, {14,16}, { 1, 2}, { 5, 6}, { 0, 4}, { 3, 7}, { 9,10}, {14,15}, {13,16}, { 1, 5}, { 2, 6}, {12,15}, {11,16}, { 1, 4}, { 3, 6}, {12,14}, {13,15}, { 7,16}, { 2, 4}, { 3, 5}, {13,14}, {10,15}, { 3, 4}, { 8,13}, { 9,14}, {11,15}, { 8,12}, { 9,13}, {11,14}, { 6,15}, { 9,12}, {10,13}, { 5,14}, { 7,15}, {10,12}, {11,13}, { 0, 9}, { 7,14}, {11,12}, { 0, 8}, { 1,10}, { 4,13}, { 1, 9}, { 2,11}, { 3,12}, { 5,13}, { 1, 8}, { 3,11}, { 2, 9}, { 6,13}, { 2, 8}, { 3,10}, { 7,13}, { 6,11}, { 3, 9}, { 5,10}, { 7,12}, { 3, 8}, { 4, 9}, { 7,11}, { 4, 8}, { 5, 9}, { 7,10}, { 5, 8}, { 6, 9}, { 6, 8}, { 7, 9}, { 7, 8}, }; static const unsigned int network18[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 7, 8}, { 9,10}, {11,12}, {13,14}, {16,17}, { 0, 2}, { 1, 3}, { 6, 8}, { 9,11}, {10,12}, {15,17}, { 1, 2}, { 6, 7}, { 5, 8}, {10,11}, {15,16}, {14,17}, { 4, 7}, { 3, 8}, {13,16}, {12,17}, { 4, 6}, { 5, 7}, {13,15}, {14,16}, { 8,17}, { 5, 6}, { 2, 7}, {14,15}, {11,16}, { 0, 5}, { 1, 6}, { 3, 7}, { 9,14}, {10,15}, {12,16}, { 0, 4}, { 1, 5}, { 3, 6}, { 9,13}, {10,14}, {12,15}, { 7,16}, { 1, 4}, { 2, 5}, {10,13}, {11,14}, { 0, 9}, { 6,15}, { 8,16}, { 2, 4}, { 3, 5}, {11,13}, {12,14}, { 1,10}, { 7,15}, { 3, 4}, {12,13}, { 1, 9}, { 2,11}, { 5,14}, { 8,15}, { 3,12}, { 2, 9}, { 4,13}, { 7,14}, { 3,11}, { 5,13}, { 8,14}, { 3,10}, { 6,13}, { 3, 9}, { 7,13}, { 5,10}, { 6,11}, { 8,13}, { 4, 9}, { 7,12}, { 5, 9}, { 8,12}, { 7,11}, { 8,11}, { 6, 9}, { 7,10}, { 8,10}, { 7, 9}, { 8, 9}, }; static const unsigned int network19[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 7, 8}, { 9,10}, {12,13}, {14,15}, {17,18}, { 0, 2}, { 1, 3}, { 6, 8}, {11,13}, {16,18}, { 1, 2}, { 6, 7}, { 5, 8}, {11,12}, {10,13}, {16,17}, {15,18}, { 4, 7}, { 3, 8}, { 9,12}, {14,17}, {13,18}, { 4, 6}, { 5, 7}, { 9,11}, {10,12}, {14,16}, {15,17}, { 8,18}, { 5, 6}, { 2, 7}, {10,11}, {15,16}, { 9,14}, {12,17}, { 0, 5}, { 1, 6}, { 3, 7}, {10,15}, {11,16}, {13,17}, { 0, 4}, { 1, 5}, { 3, 6}, {10,14}, {12,16}, { 7,17}, { 1, 4}, { 2, 5}, {13,16}, {11,14}, {12,15}, { 0,10}, { 8,17}, { 2, 4}, { 3, 5}, {13,15}, {12,14}, { 0, 9}, { 1,11}, { 6,16}, { 3, 4}, {13,14}, { 1,10}, { 2,12}, { 5,15}, { 7,16}, { 1, 9}, { 3,13}, { 2,10}, { 4,14}, { 8,16}, { 7,15}, { 3,12}, { 2, 9}, { 5,14}, { 8,15}, { 3,11}, { 6,14}, { 3,10}, { 7,14}, { 6,11}, { 3, 9}, { 8,14}, { 5,10}, { 7,12}, { 4, 9}, { 8,13}, { 7,11}, { 5, 9}, { 8,12}, { 7,10}, { 8,11}, { 6, 9}, { 8,10}, { 7, 9}, { 8, 9}, }; static const unsigned int network20[][2] = { { 0, 1}, { 3, 4}, { 5, 6}, { 8, 9}, {10,11}, {13,14}, {15,16}, {18,19}, { 2, 4}, { 7, 9}, {12,14}, {17,19}, { 2, 3}, { 1, 4}, { 7, 8}, { 6, 9}, {12,13}, {11,14}, {17,18}, {16,19}, { 0, 3}, { 5, 8}, { 4, 9}, {10,13}, {15,18}, {14,19}, { 0, 2}, { 1, 3}, { 5, 7}, { 6, 8}, {10,12}, {11,13}, {15,17}, {16,18}, { 9,19}, { 1, 2}, { 6, 7}, { 0, 5}, { 3, 8}, {11,12}, {16,17}, {10,15}, {13,18}, { 1, 6}, { 2, 7}, { 4, 8}, {11,16}, {12,17}, {14,18}, { 0,10}, { 1, 5}, { 3, 7}, {11,15}, {13,17}, { 8,18}, { 4, 7}, { 2, 5}, { 3, 6}, {14,17}, {12,15}, {13,16}, { 1,11}, { 9,18}, { 4, 6}, { 3, 5}, {14,16}, {13,15}, { 1,10}, { 2,12}, { 7,17}, { 4, 5}, {14,15}, { 3,13}, { 2,10}, { 6,16}, { 8,17}, { 4,14}, { 3,12}, { 5,15}, { 9,17}, { 8,16}, { 4,13}, { 3,11}, { 6,15}, { 9,16}, { 4,12}, { 3,10}, { 7,15}, { 4,11}, { 8,15}, { 7,12}, { 4,10}, { 9,15}, { 6,11}, { 8,13}, { 5,10}, { 9,14}, { 8,12}, { 6,10}, { 9,13}, { 8,11}, { 9,12}, { 7,10}, { 9,11}, { 8,10}, { 9,10}, }; static const unsigned int network21[][2] = { { 0, 1}, { 3, 4}, { 5, 6}, { 8, 9}, {10,11}, {13,14}, {16,17}, {19,20}, { 2, 4}, { 7, 9}, {12,14}, {15,17}, {18,20}, { 2, 3}, { 1, 4}, { 7, 8}, { 6, 9}, {12,13}, {11,14}, {15,16}, {18,19}, {17,20}, { 0, 3}, { 5, 8}, { 4, 9}, {10,13}, {15,18}, {16,19}, {14,20}, { 0, 2}, { 1, 3}, { 5, 7}, { 6, 8}, {10,12}, {11,13}, {17,19}, {16,18}, { 9,20}, { 1, 2}, { 6, 7}, { 0, 5}, { 3, 8}, {11,12}, {17,18}, {10,16}, {13,19}, { 1, 6}, { 2, 7}, { 4, 8}, {10,15}, {11,17}, {12,18}, {14,19}, { 1, 5}, { 3, 7}, {11,16}, {13,18}, { 8,19}, { 4, 7}, { 2, 5}, { 3, 6}, {11,15}, {14,18}, {13,16}, { 9,19}, { 4, 6}, { 3, 5}, {12,15}, {14,17}, { 0,11}, { 7,18}, { 4, 5}, {14,16}, {13,15}, { 0,10}, { 1,12}, { 6,17}, { 8,18}, {14,15}, { 1,11}, { 2,13}, { 5,16}, { 9,18}, { 8,17}, { 1,10}, { 3,14}, { 4,15}, { 6,16}, { 9,17}, { 4,14}, { 3,13}, { 2,10}, { 7,16}, { 4,13}, { 3,11}, { 8,16}, { 4,12}, { 3,10}, { 9,16}, { 7,13}, { 8,14}, { 4,11}, { 6,12}, { 9,15}, { 8,13}, { 4,10}, { 5,11}, { 9,14}, { 5,10}, { 6,11}, { 9,13}, { 6,10}, { 8,11}, { 9,12}, { 7,10}, { 9,11}, { 8,10}, { 9,10}, }; static const unsigned int network22[][2] = { { 0, 1}, { 3, 4}, { 6, 7}, { 9,10}, {11,12}, {14,15}, {17,18}, {20,21}, { 2, 4}, { 5, 7}, { 8,10}, {13,15}, {16,18}, {19,21}, { 2, 3}, { 1, 4}, { 5, 6}, { 8, 9}, { 7,10}, {13,14}, {12,15}, {16,17}, {19,20}, {18,21}, { 0, 3}, { 5, 8}, { 6, 9}, { 4,10}, {11,14}, {16,19}, {17,20}, {15,21}, { 0, 2}, { 1, 3}, { 7, 9}, { 6, 8}, {11,13}, {12,14}, {18,20}, {17,19}, {10,21}, { 1, 2}, { 7, 8}, { 0, 6}, { 3, 9}, {12,13}, {18,19}, {11,17}, {14,20}, { 0, 5}, { 1, 7}, { 2, 8}, { 4, 9}, {11,16}, {12,18}, {13,19}, {15,20}, { 1, 6}, { 3, 8}, {12,17}, {14,19}, { 0,11}, { 9,20}, { 1, 5}, { 4, 8}, { 3, 6}, {12,16}, {15,19}, {14,17}, {10,20}, { 2, 5}, { 4, 7}, {13,16}, {15,18}, { 1,12}, { 8,19}, { 4, 6}, { 3, 5}, {15,17}, {14,16}, { 1,11}, { 2,13}, { 7,18}, { 9,19}, { 4, 5}, {15,16}, { 3,14}, { 2,11}, { 6,17}, {10,19}, { 4,15}, { 3,13}, { 5,16}, { 7,17}, {10,18}, { 4,14}, { 3,12}, { 6,16}, { 9,17}, { 4,13}, { 3,11}, { 7,16}, {10,17}, { 4,12}, { 8,16}, { 7,13}, { 4,11}, { 9,16}, { 6,12}, { 8,14}, {10,16}, { 5,11}, { 7,12}, { 9,15}, { 6,11}, {10,15}, { 9,14}, { 7,11}, {10,14}, { 9,12}, { 8,11}, {10,13}, {10,12}, { 9,11}, {10,11}, }; static const unsigned int network23[][2] = { { 0, 1}, { 3, 4}, { 6, 7}, { 9,10}, {12,13}, {15,16}, {18,19}, {21,22}, { 2, 4}, { 5, 7}, { 8,10}, {11,13}, {14,16}, {17,19}, {20,22}, { 2, 3}, { 1, 4}, { 5, 6}, { 8, 9}, { 7,10}, {11,12}, {14,15}, {13,16}, {17,18}, {20,21}, {19,22}, { 0, 3}, { 5, 8}, { 6, 9}, { 4,10}, {11,14}, {12,15}, {17,20}, {18,21}, {16,22}, { 0, 2}, { 1, 3}, { 7, 9}, { 6, 8}, {13,15}, {12,14}, {19,21}, {18,20}, {11,17}, {10,22}, { 1, 2}, { 7, 8}, { 0, 6}, { 3, 9}, {13,14}, {19,20}, {12,18}, {15,21}, { 0, 5}, { 1, 7}, { 2, 8}, { 4, 9}, {13,19}, {12,17}, {14,20}, {16,21}, { 1, 6}, { 3, 8}, {13,18}, {15,20}, { 0,12}, { 9,21}, { 1, 5}, { 4, 8}, { 3, 6}, {13,17}, {16,20}, {15,18}, { 0,11}, {10,21}, { 2, 5}, { 4, 7}, {14,17}, {16,19}, { 1,13}, { 8,20}, { 4, 6}, { 3, 5}, {16,18}, {15,17}, { 1,12}, { 2,14}, { 7,19}, { 9,20}, { 4, 5}, {16,17}, { 1,11}, { 3,15}, { 6,18}, {10,20}, { 4,16}, { 3,14}, { 2,11}, { 5,17}, { 7,18}, {10,19}, { 4,15}, { 3,12}, { 6,17}, { 9,18}, { 4,14}, { 3,11}, { 7,17}, {10,18}, { 4,13}, { 8,17}, { 4,12}, { 9,17}, { 7,13}, { 8,14}, { 4,11}, {10,17}, { 6,12}, { 9,15}, { 5,11}, { 7,12}, {10,16}, { 9,14}, { 6,11}, {10,15}, { 9,12}, { 7,11}, {10,14}, { 8,11}, {10,13}, {10,12}, { 9,11}, {10,11}, }; static const unsigned int network24[][2] = { { 1, 2}, { 4, 5}, { 7, 8}, {10,11}, {13,14}, {16,17}, {19,20}, {22,23}, { 0, 2}, { 3, 5}, { 6, 8}, { 9,11}, {12,14}, {15,17}, {18,20}, {21,23}, { 0, 1}, { 3, 4}, { 2, 5}, { 6, 7}, { 9,10}, { 8,11}, {12,13}, {15,16}, {14,17}, {18,19}, {21,22}, {20,23}, { 0, 3}, { 1, 4}, { 6, 9}, { 7,10}, { 5,11}, {12,15}, {13,16}, {18,21}, {19,22}, {17,23}, { 2, 4}, { 1, 3}, { 8,10}, { 7, 9}, { 0, 6}, {14,16}, {13,15}, {20,22}, {19,21}, {12,18}, {11,23}, { 2, 3}, { 8, 9}, { 1, 7}, { 4,10}, {14,15}, {20,21}, {13,19}, {16,22}, { 0,12}, { 2, 8}, { 1, 6}, { 3, 9}, { 5,10}, {14,20}, {13,18}, {15,21}, {17,22}, { 2, 7}, { 4, 9}, {14,19}, {16,21}, { 1,13}, {10,22}, { 2, 6}, { 5, 9}, { 4, 7}, {14,18}, {17,21}, {16,19}, { 1,12}, {11,22}, { 3, 6}, { 5, 8}, {15,18}, {17,20}, { 2,14}, { 9,21}, { 5, 7}, { 4, 6}, {17,19}, {16,18}, { 2,13}, { 3,15}, { 8,20}, {10,21}, { 5, 6}, {17,18}, { 2,12}, { 4,16}, { 7,19}, {11,21}, { 5,17}, { 4,15}, { 3,12}, { 6,18}, { 8,19}, {11,20}, { 5,16}, { 4,13}, { 7,18}, {10,19}, { 5,15}, { 4,12}, { 8,18}, {11,19}, { 5,14}, { 9,18}, { 5,13}, {10,18}, { 8,14}, { 9,15}, { 5,12}, {11,18}, { 7,13}, {10,16}, { 6,12}, { 8,13}, {11,17}, {10,15}, { 7,12}, {11,16}, {10,13}, { 8,12}, {11,15}, { 9,12}, {11,14}, {11,13}, {10,12}, {11,12}, }; static const unsigned int network25[][2] = { { 1, 2}, { 4, 5}, { 7, 8}, {10,11}, {13,14}, {16,17}, {19,20}, {21,22}, {23,24}, { 0, 2}, { 3, 5}, { 6, 8}, { 9,11}, {12,14}, {15,17}, {18,20}, {21,23}, {22,24}, { 0, 1}, { 3, 4}, { 2, 5}, { 6, 7}, { 9,10}, { 8,11}, {12,13}, {15,16}, {14,17}, {18,19}, {22,23}, {20,24}, { 0, 3}, { 1, 4}, { 6, 9}, { 7,10}, { 5,11}, {12,15}, {13,16}, {18,22}, {19,23}, {17,24}, { 2, 4}, { 1, 3}, { 8,10}, { 7, 9}, { 0, 6}, {14,16}, {13,15}, {18,21}, {20,23}, {11,24}, { 2, 3}, { 8, 9}, { 1, 7}, { 4,10}, {14,15}, {19,21}, {20,22}, {16,23}, { 2, 8}, { 1, 6}, { 3, 9}, { 5,10}, {20,21}, {12,19}, {15,22}, {17,23}, { 2, 7}, { 4, 9}, {12,18}, {13,20}, {14,21}, {16,22}, {10,23}, { 2, 6}, { 5, 9}, { 4, 7}, {14,20}, {13,18}, {17,22}, {11,23}, { 3, 6}, { 5, 8}, {14,19}, {16,20}, {17,21}, { 0,13}, { 9,22}, { 5, 7}, { 4, 6}, {14,18}, {15,19}, {17,20}, { 0,12}, { 8,21}, {10,22}, { 5, 6}, {15,18}, {17,19}, { 1,14}, { 7,20}, {11,22}, {16,18}, { 2,15}, { 1,12}, { 6,19}, { 8,20}, {11,21}, {17,18}, { 2,14}, { 3,16}, { 7,19}, {10,20}, { 2,13}, { 4,17}, { 5,18}, { 8,19}, {11,20}, { 2,12}, { 5,17}, { 4,16}, { 3,13}, { 9,19}, { 5,16}, { 3,12}, { 4,14}, {10,19}, { 5,15}, { 4,12}, {11,19}, { 9,16}, {10,17}, { 5,14}, { 8,15}, {11,18}, {10,16}, { 5,13}, { 7,14}, {11,17}, { 5,12}, { 6,13}, { 8,14}, {11,16}, { 6,12}, { 8,13}, {10,14}, {11,15}, { 7,12}, { 9,13}, {11,14}, { 8,12}, {11,13}, { 9,12}, {10,12}, {11,12}, }; static const unsigned int network26[][2] = { { 1, 2}, { 4, 5}, { 7, 8}, { 9,10}, {11,12}, {14,15}, {17,18}, {20,21}, {22,23}, {24,25}, { 0, 2}, { 3, 5}, { 6, 8}, { 9,11}, {10,12}, {13,15}, {16,18}, {19,21}, {22,24}, {23,25}, { 0, 1}, { 3, 4}, { 2, 5}, { 6, 7}, {10,11}, { 8,12}, {13,14}, {16,17}, {15,18}, {19,20}, {23,24}, {21,25}, { 0, 3}, { 1, 4}, { 6,10}, { 7,11}, { 5,12}, {13,16}, {14,17}, {19,23}, {20,24}, {18,25}, { 2, 4}, { 1, 3}, { 6, 9}, { 8,11}, {15,17}, {14,16}, {19,22}, {21,24}, {12,25}, { 2, 3}, { 7, 9}, { 8,10}, { 4,11}, {15,16}, {20,22}, {21,23}, {17,24}, { 8, 9}, { 0, 7}, { 3,10}, { 5,11}, {21,22}, {13,20}, {16,23}, {18,24}, { 0, 6}, { 1, 8}, { 2, 9}, { 4,10}, {13,19}, {14,21}, {15,22}, {17,23}, {11,24}, { 2, 8}, { 1, 6}, { 5,10}, {15,21}, {14,19}, {18,23}, { 0,13}, {12,24}, { 2, 7}, { 4, 8}, { 5, 9}, {15,20}, {17,21}, {18,22}, { 1,14}, {10,23}, { 2, 6}, { 3, 7}, { 5, 8}, {15,19}, {16,20}, {18,21}, { 1,13}, { 9,22}, {12,23}, { 3, 6}, { 5, 7}, {16,19}, {18,20}, { 2,15}, { 8,21}, {10,22}, { 4, 6}, {17,19}, { 2,14}, { 3,16}, { 7,20}, {11,22}, { 5, 6}, {18,19}, { 2,13}, { 4,17}, { 8,20}, {12,22}, {11,21}, { 5,18}, { 4,16}, { 3,13}, { 6,19}, {10,20}, {12,21}, { 5,17}, { 4,14}, { 7,19}, {12,20}, { 5,16}, { 4,13}, { 8,19}, { 5,15}, { 9,19}, { 5,14}, {10,19}, { 8,15}, { 9,16}, { 5,13}, {11,19}, { 7,14}, {10,17}, {12,19}, { 6,13}, { 8,14}, {10,16}, {11,18}, { 7,13}, {12,18}, {11,16}, {10,14}, { 8,13}, {12,17}, {11,15}, {12,16}, { 9,13}, {10,13}, {12,15}, {11,13}, {12,14}, {12,13}, }; static const unsigned int network27[][2] = { { 1, 2}, { 4, 5}, { 7, 8}, { 9,10}, {11,12}, {14,15}, {16,17}, {18,19}, {21,22}, {23,24}, {25,26}, { 0, 2}, { 3, 5}, { 6, 8}, { 9,11}, {10,12}, {13,15}, {16,18}, {17,19}, {20,22}, {23,25}, {24,26}, { 0, 1}, { 3, 4}, { 2, 5}, { 6, 7}, {10,11}, { 8,12}, {13,14}, {17,18}, {15,19}, {20,21}, {24,25}, {22,26}, { 0, 3}, { 1, 4}, { 6,10}, { 7,11}, { 5,12}, {13,17}, {14,18}, {20,24}, {21,25}, {19,26}, { 2, 4}, { 1, 3}, { 6, 9}, { 8,11}, {13,16}, {15,18}, {20,23}, {22,25}, {12,26}, { 2, 3}, { 7, 9}, { 8,10}, { 4,11}, {14,16}, {15,17}, {21,23}, {22,24}, {13,20}, {18,25}, { 8, 9}, { 0, 7}, { 3,10}, { 5,11}, {15,16}, {22,23}, {14,21}, {17,24}, {19,25}, { 0, 6}, { 1, 8}, { 2, 9}, { 4,10}, {15,22}, {14,20}, {16,23}, {19,24}, {11,25}, { 2, 8}, { 1, 6}, { 5,10}, {15,21}, {17,23}, { 0,14}, {12,25}, { 2, 7}, { 4, 8}, { 5, 9}, {15,20}, {18,23}, {17,21}, { 0,13}, {10,24}, { 2, 6}, { 3, 7}, { 5, 8}, {19,23}, {16,20}, {18,22}, { 1,15}, {12,24}, { 3, 6}, { 5, 7}, {17,20}, {19,22}, { 2,16}, { 1,13}, { 9,23}, { 4, 6}, {18,20}, {19,21}, { 2,15}, { 3,17}, { 8,22}, {10,23}, { 5, 6}, {19,20}, { 2,14}, { 4,18}, { 7,21}, {11,23}, { 2,13}, { 5,19}, { 4,17}, { 3,14}, { 6,20}, { 8,21}, {12,23}, {11,22}, { 5,18}, { 3,13}, { 4,15}, { 7,20}, {10,21}, {12,22}, { 5,17}, { 4,13}, { 8,20}, {12,21}, { 5,16}, { 9,20}, { 5,15}, {10,20}, { 9,16}, { 5,14}, {11,20}, { 8,15}, {10,17}, { 5,13}, {12,20}, { 7,14}, {10,16}, {11,18}, { 6,13}, { 8,14}, {12,19}, {11,16}, { 7,13}, {12,18}, {10,14}, {11,15}, { 8,13}, {12,17}, {12,16}, { 9,13}, {10,13}, {12,15}, {11,13}, {12,14}, {12,13}, }; static const unsigned int network28[][2] = { { 1, 2}, { 3, 4}, { 5, 6}, { 8, 9}, {10,11}, {12,13}, {15,16}, {17,18}, {19,20}, {22,23}, {24,25}, {26,27}, { 0, 2}, { 3, 5}, { 4, 6}, { 7, 9}, {10,12}, {11,13}, {14,16}, {17,19}, {18,20}, {21,23}, {24,26}, {25,27}, { 0, 1}, { 4, 5}, { 2, 6}, { 7, 8}, {11,12}, { 9,13}, {14,15}, {18,19}, {16,20}, {21,22}, {25,26}, {23,27}, { 0, 4}, { 1, 5}, { 7,11}, { 8,12}, { 6,13}, {14,18}, {15,19}, {21,25}, {22,26}, {20,27}, { 0, 3}, { 2, 5}, { 7,10}, { 9,12}, {14,17}, {16,19}, {21,24}, {23,26}, {13,27}, { 1, 3}, { 2, 4}, { 8,10}, { 9,11}, { 0, 7}, { 5,12}, {15,17}, {16,18}, {22,24}, {23,25}, {14,21}, {19,26}, { 2, 3}, { 9,10}, { 1, 8}, { 4,11}, { 6,12}, {16,17}, {23,24}, {15,22}, {18,25}, {20,26}, { 0,14}, { 2, 9}, { 1, 7}, { 3,10}, { 6,11}, {16,23}, {15,21}, {17,24}, {20,25}, {12,26}, { 2, 8}, { 4,10}, {16,22}, {18,24}, { 1,15}, {11,25}, {13,26}, { 2, 7}, { 5,10}, { 4, 8}, {16,21}, {19,24}, {18,22}, { 1,14}, {13,25}, { 6,10}, { 3, 7}, { 5, 9}, {20,24}, {17,21}, {19,23}, { 2,16}, { 4, 7}, { 6, 9}, {18,21}, {20,23}, { 2,15}, { 3,17}, {10,24}, { 5, 7}, { 6, 8}, {19,21}, {20,22}, { 2,14}, { 4,18}, { 9,23}, {11,24}, { 6, 7}, {20,21}, { 4,17}, { 5,19}, { 3,14}, { 8,22}, {12,24}, { 6,20}, { 5,17}, { 4,15}, { 7,21}, { 9,22}, {13,24}, {12,23}, { 6,19}, { 4,14}, { 5,16}, { 8,21}, {11,22}, {13,23}, { 6,18}, { 5,14}, { 9,21}, {13,22}, { 6,17}, {10,21}, { 6,16}, {11,21}, {10,17}, { 6,15}, {12,21}, { 9,16}, {11,18}, { 6,14}, {13,21}, { 8,15}, {11,17}, {12,19}, { 7,14}, { 9,15}, {13,20}, {12,17}, { 8,14}, {13,19}, {11,15}, {12,16}, { 9,14}, {13,18}, {13,17}, {10,14}, {11,14}, {13,16}, {12,14}, {13,15}, {13,14}, }; static const unsigned int network29[][2] = { { 1, 2}, { 3, 4}, { 5, 6}, { 8, 9}, {10,11}, {12,13}, {15,16}, {17,18}, {19,20}, {21,22}, {23,24}, {25,26}, {27,28}, { 0, 2}, { 3, 5}, { 4, 6}, { 7, 9}, {10,12}, {11,13}, {14,16}, {17,19}, {18,20}, {21,23}, {22,24}, {25,27}, {26,28}, { 0, 1}, { 4, 5}, { 2, 6}, { 7, 8}, {11,12}, { 9,13}, {14,15}, {18,19}, {16,20}, {22,23}, {26,27}, {21,25}, {24,28}, { 0, 4}, { 1, 5}, { 7,11}, { 8,12}, { 6,13}, {14,18}, {15,19}, {22,26}, {23,27}, {20,28}, { 0, 3}, { 2, 5}, { 7,10}, { 9,12}, {14,17}, {16,19}, {22,25}, {24,27}, {13,28}, { 1, 3}, { 2, 4}, { 8,10}, { 9,11}, { 0, 7}, { 5,12}, {15,17}, {16,18}, {23,25}, {24,26}, {14,22}, {19,27}, { 2, 3}, { 9,10}, { 1, 8}, { 4,11}, { 6,12}, {16,17}, {24,25}, {14,21}, {15,23}, {18,26}, {20,27}, { 2, 9}, { 1, 7}, { 3,10}, { 6,11}, {16,24}, {15,21}, {17,25}, {20,26}, {12,27}, { 2, 8}, { 4,10}, {16,23}, {18,25}, { 0,15}, {11,26}, {13,27}, { 2, 7}, { 5,10}, { 4, 8}, {16,22}, {19,25}, { 0,14}, {13,26}, { 6,10}, { 3, 7}, { 5, 9}, {16,21}, {20,25}, {18,22}, {19,23}, { 4, 7}, { 6, 9}, {17,21}, {20,24}, { 1,16}, {10,25}, { 5, 7}, { 6, 8}, {18,21}, {20,23}, { 2,17}, { 1,14}, { 9,24}, {11,25}, { 6, 7}, {19,21}, {20,22}, { 2,16}, { 3,18}, { 8,23}, {12,25}, {20,21}, { 2,15}, { 4,19}, { 7,22}, { 9,23}, {13,25}, {12,24}, { 2,14}, { 4,18}, { 5,20}, { 6,21}, { 8,22}, {11,23}, {13,24}, { 6,20}, { 5,18}, { 3,14}, { 4,15}, { 9,22}, {13,23}, { 6,19}, { 4,14}, { 5,16}, {10,22}, { 6,18}, { 5,14}, {11,22}, { 6,17}, {12,22}, {10,18}, {11,19}, { 6,16}, {13,22}, { 9,17}, {11,18}, {12,20}, { 6,15}, { 8,16}, {13,21}, {12,18}, { 6,14}, { 7,15}, { 9,16}, {13,20}, { 7,14}, { 9,15}, {13,19}, {12,16}, { 8,14}, {13,18}, {11,15}, { 9,14}, {13,17}, {10,14}, {13,16}, {11,14}, {13,15}, {12,14}, {13,14}, }; static const unsigned int network30[][2] = { { 1, 2}, { 3, 4}, { 5, 6}, { 7, 8}, { 9,10}, {11,12}, {13,14}, {16,17}, {18,19}, {20,21}, {22,23}, {24,25}, {26,27}, {28,29}, { 0, 2}, { 3, 5}, { 4, 6}, { 7, 9}, { 8,10}, {11,13}, {12,14}, {15,17}, {18,20}, {19,21}, {22,24}, {23,25}, {26,28}, {27,29}, { 0, 1}, { 4, 5}, { 2, 6}, { 8, 9}, {12,13}, { 7,11}, {10,14}, {15,16}, {19,20}, {17,21}, {23,24}, {27,28}, {22,26}, {25,29}, { 0, 4}, { 1, 5}, { 8,12}, { 9,13}, { 6,14}, {15,19}, {16,20}, {23,27}, {24,28}, {21,29}, { 0, 3}, { 2, 5}, { 8,11}, {10,13}, {15,18}, {17,20}, {23,26}, {25,28}, {14,29}, { 1, 3}, { 2, 4}, { 9,11}, {10,12}, { 0, 8}, { 5,13}, {16,18}, {17,19}, {24,26}, {25,27}, {15,23}, {20,28}, { 2, 3}, {10,11}, { 0, 7}, { 1, 9}, { 4,12}, { 6,13}, {17,18}, {25,26}, {15,22}, {16,24}, {19,27}, {21,28}, { 2,10}, { 1, 7}, { 3,11}, { 6,12}, {17,25}, {16,22}, {18,26}, {21,27}, { 0,15}, {13,28}, { 2, 9}, { 4,11}, {17,24}, {19,26}, { 1,16}, {12,27}, {14,28}, { 2, 8}, { 5,11}, {17,23}, {20,26}, { 1,15}, {14,27}, { 2, 7}, { 6,11}, { 4, 8}, { 5, 9}, {17,22}, {21,26}, {19,23}, {20,24}, { 3, 7}, { 6,10}, {18,22}, {21,25}, { 2,17}, {11,26}, { 4, 7}, { 6, 9}, {19,22}, {21,24}, { 2,16}, { 3,18}, {10,25}, {12,26}, { 5, 7}, { 6, 8}, {20,22}, {21,23}, { 2,15}, { 4,19}, { 9,24}, {13,26}, { 6, 7}, {21,22}, { 4,18}, { 5,20}, { 3,15}, { 8,23}, {10,24}, {14,26}, { 6,21}, { 5,18}, { 4,16}, { 7,22}, {10,23}, {13,24}, {14,25}, { 6,20}, { 4,15}, { 5,17}, { 8,22}, {12,23}, {14,24}, { 6,19}, { 5,15}, { 9,22}, {14,23}, { 6,18}, {10,22}, { 6,17}, {11,22}, {10,18}, { 6,16}, {12,22}, { 9,17}, {11,19}, { 6,15}, {13,22}, { 8,16}, {10,17}, {12,20}, {14,22}, { 7,15}, {10,16}, {12,19}, {13,21}, { 8,15}, {14,21}, {13,19}, {12,16}, { 9,15}, {14,20}, {13,17}, {10,15}, {14,19}, {11,15}, {14,18}, {12,15}, {14,17}, {13,15}, {14,16}, {14,15}, }; static const unsigned int network31[][2] = { { 1, 2}, { 3, 4}, { 5, 6}, { 7, 8}, { 9,10}, {11,12}, {13,14}, {15,16}, {17,18}, {19,20}, {21,22}, {23,24}, {25,26}, {27,28}, {29,30}, { 0, 2}, { 3, 5}, { 4, 6}, { 7, 9}, { 8,10}, {11,13}, {12,14}, {15,17}, {16,18}, {19,21}, {20,22}, {23,25}, {24,26}, {27,29}, {28,30}, { 0, 1}, { 4, 5}, { 2, 6}, { 8, 9}, {12,13}, { 7,11}, {10,14}, {16,17}, {20,21}, {15,19}, {18,22}, {24,25}, {28,29}, {23,27}, {26,30}, { 0, 4}, { 1, 5}, { 8,12}, { 9,13}, { 6,14}, {16,20}, {17,21}, {24,28}, {25,29}, {15,23}, {22,30}, { 0, 3}, { 2, 5}, { 8,11}, {10,13}, {16,19}, {18,21}, {24,27}, {26,29}, {14,30}, { 1, 3}, { 2, 4}, { 9,11}, {10,12}, { 0, 8}, { 5,13}, {17,19}, {18,20}, {25,27}, {26,28}, {16,24}, {21,29}, { 2, 3}, {10,11}, { 0, 7}, { 1, 9}, { 4,12}, { 6,13}, {18,19}, {26,27}, {16,23}, {17,25}, {20,28}, {22,29}, { 2,10}, { 1, 7}, { 3,11}, { 6,12}, {18,26}, {17,23}, {19,27}, {22,28}, { 0,16}, {13,29}, { 2, 9}, { 4,11}, {18,25}, {20,27}, { 0,15}, { 1,17}, {12,28}, {14,29}, { 2, 8}, { 5,11}, {18,24}, {21,27}, { 1,15}, {14,28}, { 2, 7}, { 6,11}, { 4, 8}, { 5, 9}, {18,23}, {22,27}, {20,24}, {21,25}, { 3, 7}, { 6,10}, {19,23}, {22,26}, { 2,18}, {11,27}, { 4, 7}, { 6, 9}, {20,23}, {22,25}, { 2,17}, { 3,19}, {10,26}, {12,27}, { 5, 7}, { 6, 8}, {21,23}, {22,24}, { 2,16}, { 4,20}, { 9,25}, {13,27}, { 6, 7}, {22,23}, { 2,15}, { 4,19}, { 5,21}, { 8,24}, {10,25}, {14,27}, { 6,22}, { 5,19}, { 3,15}, { 4,16}, { 7,23}, {10,24}, {13,25}, {14,26}, { 6,21}, { 4,15}, { 5,17}, { 8,23}, {12,24}, {14,25}, { 6,20}, { 5,15}, { 9,23}, {14,24}, { 6,19}, {10,23}, { 6,18}, {11,23}, { 6,17}, {12,23}, {10,18}, {11,19}, { 6,16}, {13,23}, { 9,17}, {12,20}, { 6,15}, {14,23}, { 8,16}, {10,17}, {12,19}, {13,21}, { 7,15}, {10,16}, {14,22}, {13,19}, { 8,15}, {14,21}, {12,16}, {13,17}, { 9,15}, {14,20}, {10,15}, {14,19}, {11,15}, {14,18}, {12,15}, {14,17}, {13,15}, {14,16}, {14,15}, }; static const unsigned int network32[][2] = { { 0, 1}, { 2, 3}, { 4, 5}, { 6, 7}, { 8, 9}, {10,11}, {12,13}, {14,15}, {16,17}, {18,19}, {20,21}, {22,23}, {24,25}, {26,27}, {28,29}, {30,31}, { 0, 2}, { 1, 3}, { 4, 6}, { 5, 7}, { 8,10}, { 9,11}, {12,14}, {13,15}, {16,18}, {17,19}, {20,22}, {21,23}, {24,26}, {25,27}, {28,30}, {29,31}, { 1, 2}, { 5, 6}, { 0, 4}, { 3, 7}, { 9,10}, {13,14}, { 8,12}, {11,15}, {17,18}, {21,22}, {16,20}, {19,23}, {25,26}, {29,30}, {24,28}, {27,31}, { 1, 5}, { 2, 6}, { 9,13}, {10,14}, { 0, 8}, { 7,15}, {17,21}, {18,22}, {25,29}, {26,30}, {16,24}, {23,31}, { 1, 4}, { 3, 6}, { 9,12}, {11,14}, {17,20}, {19,22}, {25,28}, {27,30}, { 0,16}, {15,31}, { 2, 4}, { 3, 5}, {10,12}, {11,13}, { 1, 9}, { 6,14}, {18,20}, {19,21}, {26,28}, {27,29}, {17,25}, {22,30}, { 3, 4}, {11,12}, { 1, 8}, { 2,10}, { 5,13}, { 7,14}, {19,20}, {27,28}, {17,24}, {18,26}, {21,29}, {23,30}, { 3,11}, { 2, 8}, { 4,12}, { 7,13}, {19,27}, {18,24}, {20,28}, {23,29}, { 1,17}, {14,30}, { 3,10}, { 5,12}, {19,26}, {21,28}, { 1,16}, { 2,18}, {13,29}, {15,30}, { 3, 9}, { 6,12}, {19,25}, {22,28}, { 2,16}, {15,29}, { 3, 8}, { 7,12}, { 5, 9}, { 6,10}, {19,24}, {23,28}, {21,25}, {22,26}, { 4, 8}, { 7,11}, {20,24}, {23,27}, { 3,19}, {12,28}, { 5, 8}, { 7,10}, {21,24}, {23,26}, { 3,18}, { 4,20}, {11,27}, {13,28}, { 6, 8}, { 7, 9}, {22,24}, {23,25}, { 3,17}, { 5,21}, {10,26}, {14,28}, { 7, 8}, {23,24}, { 3,16}, { 5,20}, { 6,22}, { 9,25}, {11,26}, {15,28}, { 7,23}, { 6,20}, { 4,16}, { 5,17}, { 8,24}, {11,25}, {14,26}, {15,27}, { 7,22}, { 5,16}, { 6,18}, { 9,24}, {13,25}, {15,26}, { 7,21}, { 6,16}, {10,24}, {15,25}, { 7,20}, {11,24}, { 7,19}, {12,24}, { 7,18}, {13,24}, {11,19}, {12,20}, { 7,17}, {14,24}, {10,18}, {13,21}, { 7,16}, {15,24}, { 9,17}, {11,18}, {13,20}, {14,22}, { 8,16}, {11,17}, {15,23}, {14,20}, { 9,16}, {15,22}, {13,17}, {14,18}, {10,16}, {15,21}, {11,16}, {15,20}, {12,16}, {15,19}, {13,16}, {15,18}, {14,16}, {15,17}, {15,16}, }; typedef struct { const unsigned int comparators; const unsigned int (*pNetwork)[2]; } Network; #define elemof(ary) (sizeof(ary)/sizeof(ary[0])) #define NETWORK(n) {elemof(network##n), network##n} static const Network networks[] = { {0, NULL}, {0, NULL}, NETWORK(02), NETWORK(03), NETWORK(04), NETWORK(05), NETWORK(06), NETWORK(07), NETWORK(08), NETWORK(09), NETWORK(10), NETWORK(11), NETWORK(12), NETWORK(13), NETWORK(14), NETWORK(15), NETWORK(16), NETWORK(17), NETWORK(18), NETWORK(19), NETWORK(20), NETWORK(21), NETWORK(22), NETWORK(23), NETWORK(24), NETWORK(25), NETWORK(26), NETWORK(27), NETWORK(28), NETWORK(29), NETWORK(30), NETWORK(31), NETWORK(32), }; #undef NETWORK static const size_t cutoff = elemof(networks); template<typename Elem> inline void networkSort(Elem *p, size_t n) { const unsigned int (* pNetwork)[2], (* pEnd)[2]; Elem *p0, *p1; assert(n < elemof(networks)); pNetwork = networks[n].pNetwork; pEnd = pNetwork + networks[n].comparators; for(; pNetwork < pEnd; pNetwork++) { p0 = p + (*pNetwork)[0]; p1 = p + (*pNetwork)[1]; if(*p1 < *p0) swap(p0, p1); } } |
图灵机vs数学家——图灵机计算能力的分析
(这是我2009年5月在繁星客栈上发表的,现稍加修改放在这里)
这里我尝试回答三个问题
0.是否存在数学家可以做出判定的问题,不存在判定该问题的图灵机?
1.是否存在这样的通用判定图灵机,输入任何数学家可以判定的问题,该图灵机都能在有限步骤之内输出其判定结果,而不输出任何错误的判定结果?
2.是否存在这样的自动判定图灵机,自动尝试判定所有可能的判定问题,而对于其中任何一个数学家可以判定的问题,该图灵机都可以在有限步骤之内输出其判定结果,而不输出任何错误的判定结果?
现在我们对数学家做一个假定:只有能够完全形式化的有限判定过程,才会被数学家承认。
这个假定是合理的,虽然天才的数学家可以用直觉、灵感、做梦、神启等等各种惊人的手段获得一个问题判定结果,但只有能够被彻底形式化的有限判定过程才会被认为是可靠的判定过程。换言之,任何一个能够被数学家认可的判定过程,绝对不能包含任何直觉成分。
上述假定蕴含这样一个结果:
能被数学家承认的任何判定,其步骤数目必然有限。
能被数学家判定的任何问题,其符号刻画必然有限。
有限符号可刻画的问题的全集显然是递归可枚举集,因为它是所有有限长字符串集合的子集,因此可以用自然数i进行编号,记为Pi。
同理,刻画任何有效判定过程的符号串长度必然有限,因此显然是递归可枚举集,也可以用自然数j进行编号,记为Dj。
任何形式系统的推理规则,至多属于乔姆斯基无限制文法的子集,因此推理规则所生成的语言必然是递归可枚举语言的子集,而递归可枚举语言是图灵机可接受(但未必可判定)语言。所有形式系统的推理规则,都可以用自然数k进行编号,记为Lk。
判定【Dj是否为Lk的问题Pi的有效判定过程】,可以用一个通用验证图灵机V(Lk,Pi,Dj)进行判定,而且这是个可判定过程。因为Lk中任何一个判定过程中的任何一个步骤否有效,可以直接套用Lk的推理规则进行判定,而由于整个判定过程的步骤必然有限,因此整个判定过程Dj的有效性必然是可以被图灵机V判定的。
现在我们来构造一个图灵机H(Lk,Pi),对于给定的形式系统Lk以及Lk的问题Pi,如果Lk中存在对Pi的有效判定过程Dj,那么图灵机H就输出Pi在Lk内的判定结果。但如果Lk中不存在对Pi的有效判定,H永不停机。
为了构造H,我们先考虑一个不可行的构造,然后将其改造为可行的构造:
构造无穷多图灵机Hj(Lk,Pi),其定义为V(Lk,Pi,Dj)。显然,如果存在某个j,Dj是Pi在Lk下的有效判定,那么Hj必然在有限步骤内停机。
如果将所有的Hj同时启动,那么只要问题Pi在Lk下可判定,那么至少有一个Hj会在有限时间内输出判定,除非问题Pj在Lk下不可判定。
但是上述机构造需要同时运行无数个图灵机,是不可接受的。不过很容易将上述改造为可接受的构造:
构造H(Lk,Pi),H包含一个循环,在第j次循环中,H先枚举出图灵机Hj的仿真程序,然后将已经枚举出的图灵机H0-Hj的仿真程序分别单步执行一步。显然,H的任何一次循环都必然在有限步骤内结束,只不过越往后循环周期越长。这样,只要Pi在Lk内可判定,那么必然存在某个Hj在运行m个步骤后停机给出判定结果,那么H(Lk,Pi)就必然在第j+m个循环之内停机。反之,如果Pi在Lk内不可判定,那么H永远也不停机。
于是,H就是符合前述要求的图灵机。
现在,我们要利用H(Lk,Pi,Dk)来构造一个永不停机的图灵机X,X不接受任何输入,但X对于任何一个形式系统Lk中任何一个可判定问题Pi,都可以在有限时间内输出其判定结果(连同Lk和Pi的编号一起输出),而对于任何一个不可判定的问题,X都永远不给出对该问题的任何输出。
类似于构造H的过程,我们先考虑一个不可行的构造,然后改造为可行的构造:
二元组(Lk,Pi)显然是一个递归可枚举集,因此可以用自然数n进行编号:n=f(Lk,Pi),(Lk,Pi)=g(n),f和g互为反函数。
构造无穷多图灵机Xn,其定义为:如果H(g(n))停机输出判定结果d,Xn就输出(n,d)。显然,只要Pi在Lk中可判定,Xn就必然在有限步骤内停机并输出(n,d),反之Xn永不停机。
如果将所有的Xn同时启动,那么任何Lk中的任何可判定问题Pi,都必然在有限步骤内被某个Xn输出,输出内容为(n,d),其中n=f(Lk,Pi)。
上述构造当然也是不可接受的,我们仿照改造H的方法改造X
构造图灵机X,X包含一个循环,在第n次循环中,X先枚举出图灵机Xn的仿真程序,然后将已经枚举出的图灵机X0-Xn的仿真程序分别单步执行一步。显然,X的任何一次循环都必然在有限步骤内结束,只不过越往后循环周期越长。这样,对于任何Lk中的任何Pi,只要Pi在Lk中是可判定的且判定结果为d,那么Xn(n=f(Lk,Pi))必然在某个有限步骤s处停机并输(n,d),而Xn的第s步必然在X的第n+s个循环中被执行到,X必然在n+s个循环之内,输出(n,d)。对于任何形式系统中的任何不可判定的问题,X永远都不会输出判定结果。
于是,X就是符合前述要求的图灵机。
现在澄清几件事情:
1.H和X都不会对任何形式系统中的任何不可判定问题给出判定。
2.对于任何Lk中的任何【Pi】,无论【Pi】在Lk中是否可判定,都存在整数i’,使得【Pi’】==【Pi在Lk中可判定】,因此【Pi在Lk中可判定】这个问题迟早会被X枚举出来并尝试对之进行判定。只要【Pi在Lk中可判定】在某个形式系统Lk’中可判定,那么【Pi在Lk中可判定】的判定结果必然会在有限步骤内被X输出。
3.X在枚举Xn的过程中,许多Xn是根本不会停机的,而每次循环中X都必须单步执行所有尚未停机的Xn,因此X的效率会越来越低。但正如我们前面所讨论的,只要一个问题可以判定,X必然会在有限步骤内输出这个问题的判定,X的能力不会随着效率越来越低而变弱。如果实在觉得这种垃圾越来越多的系统很不舒服,那么还存在这样一个优化:每当某个Xn停机输出了某个问题A的判定结果,就检查A的内容是不是在断言另一个问题B不可判定,如果A有效断言出B不可判定,那么就将正在尝试判定B的图灵机从循环中删除。甚至还可以对B做个标记,在枚举新问题的时候,凡是将问题B作为子问题的问题,都将被直接跳过。不过这些优化其实没什么意义,我们的目的仅仅是讨论图灵机的计算能力,并不关心性能。
——————————————————————————————
现在说一点题外话,数学上明明允许图灵机不可判定的问题存在,也允许超越图灵机的计算模型存在,而我们仅仅对数学家做了一个简单的限制,数学家的计算能力就被限制成不超越图灵机了呢?
这个问题很简单。我们所说的形式系统当然都是有限刻画的。数学家可以谈论某些必须由无限符号才能刻画的数学系统的存在性,甚至谈论这些系统的某些能力和限制,但完全无法使用这些系统进行推理。我们之所以可以谈论这些系统的某些能力和限制,是因为精确分析这些东西仅仅需要有限符号。于是,图灵机也一样可以给出关于这些系统的某些有限符号就可以刻画的属性的判定结果。
事实上,即便是我们熟悉的实数集,其中属性能够被有限符号所精确刻画的实数(也包括π、e、γ等)也只是实数集的一个可数子集。
除非数学家可以分辨出连续统那么多不同的符号甚至更多,以至于整个实数轴的每一个实数对于数学家来说一目了然,那么数学家也就具备了Hyper Computing的能力,我在文章开头对数学家所做的假设也就不再成立了。但这种情况下,数学家所能够提出的机械计算模型肯定要比图灵机更强。这种数学家的本事简直赶上上帝了,我们有能力回答的一切数学问题,对于这种超级数学家来说都可以一眼看出答案。
从计算观点开始……(草稿)
- 孤立物理体系
所谓孤立物理体系,是指这个体系完全不受其他体系影响,体系的可能状态由体系自身的物理规律所决定。假定为体系选择的一组状态参数的所有取值构成的集和为$$S$$,那么物理规律就限定了这个集和中那些状态是物理规律所允许的,相当于是对集和$$S$$的一个约束。可以记为:
$$K[s], s\in S$$
其中,$$K$$是一个判断,$$K[s]$$为真当且仅当状态$$s\in S$$是物理规律所允许的。
将所有这样的状态构成的集合记为$$K[S]$$,显然有$$K[S]\subseteq S$$。
如果我们的状态参数选取得非常好,参数空间中每一个参数都对应一个符合物理规律的状态,我们就全知了。可惜我们做不到,我们所选择的状态参数所构成的参数空间总是远大于物理规律所允许的状态集。退而求其次,我们希望找到一种等价关系,这种等价关系将状态空间划分为若干个等价类,所有物理规律允许可以共存的现象事件都仅仅属于其中一个等价类,而这个等价类也仅仅包含物理规律允许的现象事件。这样,我们只要找到了这个等价类中的一个代表状态,就可以确定整个等价类。这就是通过初值条件或者边界条件来求解物理体系的方法。
一个简单的例子:一个沿直线匀速运动的粒子,其状态由$$(x,t)$$标记,运动方程为$$x=v t+x_0$$,那么仅当$$(x,t)$$满足这个运动方程时,我们才认为由$$(x,t)$$所标记的状态才是物理规律所允许的状态。这个例子中所有物理规律所允许的状态的集和刚好构成了粒子的世界线。再举个一维经典标量场的例子:场$$A$$的状态由函数$$A(x,t)$$决定。仅当$$(A,x,t)$$满足场方程$$\frac{\partial^2 A}{{\partial t}^2}-\frac{\partial^2 A}{{\partial x}^2}=0$$和某个边界条件(例如初始条件$$A(x,0)=A_0(x)$$)时,我们才认为$$A(x,t)$$是物理规律所允许的状态。
但上述模型对于物理学家来说只是运动学(Kinematic)模型,只有当你了解了宇宙状态的所有细节才能给出这个运动学方程,而这些细节恰恰是我们希望能够通过方程预言的。换言之这种抽象的运动学模型没什么用。物理学家希望的是:当我了解了宇宙局部的某些信息之后,我可以通过这些信息推算出另一些局部甚至全局的信息。这就要求我们建立所谓的动力学(Dynamical)模型。如果我们知道了宇宙的全部运动学信息,那么动力学模型总是可以导出的,但我们了解到的只是一些局部的信息。
以一维匀速直线运动为例,当我们知道了某个$$(x_0,t_0)$$是合法物理状态,那么从动力学方程$$\frac{dx}{dt}=v$$就可以推算出其他的合法物理状态,这个方程告诉我们,如果$$(x_0,t_0)$$是合法物理状态,那么$$(x+dx,t+dt)$$要满足什么条件才是合法的物理状态。也就是说,该动力学方程给出了$$(x_0,t_0)$$和$$(x+dx,t+dt)$$必须满足的关系,积分后就可以得出$$(x_0,t_0)$$和任意$$(x_1,t_1)$$的关系。物理学的动力学视角写成数学方程就是:
$$D[S_0,S]=K[S],~where~S_0\subset K[S]$$
限定了$$S_0$$和K[S]必须满足的关系。显然,最平凡的情况是$$S_0=K[S]$$,但物理学家希望从一组尽可能小的$$S_0$$出发,通过$$D$$能够得出整个$$K[S]$$。比方说匀速直线运动的例子中,从任何一个单独的$$(x_0,t_0)$$就可以得到所有合法的物理状态。而在上述的一维经典场论中,单独的一个$$(A,x,t)$$是得不出所有合法物理状态的,必须了解一组状态,比方说了解$$t$$为某个定值的时候所有$$x$$对应的$$A$$(初值问题),或者了解曲线$$f(x,t)=0$$上所有的$$A$$(边值问题),或者了解曲线$$f(x,t)=0$$上所有的$$\partial A/\partial t, \partial A/\partial x$$(边值问题)。
在经典绝对时空观之中,时间是一个非常特殊的状态参数,所有其他的状态参数都可以写成时间的函数,对于实数、复数、向量、张量等形式的物理量,都可以表达为时间的函数,对于许多物理问题而言,我们都可以根据初值来决定今后的演化:
$$D[K[S]|_{t=t_0}, S]=K[S]$$
这样一来,这就是所谓经典的机械决定论的视角。进一步,我们可以把这种方程写成关于时间的演化方程:
$$K[S]|_{t^+}=F[K[S]|_t]$$
如果模型中的时间是连续的,那么可以直接表达为微分方程,令$$s(t)=K[S]|_t$$:
$$\partial s/\partial t=f(s,t)$$
在相对论之中,时间不再具有特殊地位,不同参考系也没有一个标准时间,但大家仍然希望能够在时间上做出预言,一个办法是给出物理量在某个完整的类空超曲面上的值,以至于所有的类时世界线都会与这个类空超曲面相交,就可以决定整个时空流形的物理量了。
另一方面,最初用来标记系统状态的状态参量可能具有若干对称性……(待续)
虽然真正的孤立体系要求完全不受外界影响,以至于这样的体系不能从外部通过相互作用来观测,但最初人们认为我们总是可以设法在技术上降低观测对体系的影响,使被观测的体系几乎跟孤立体系没有区别。但近代物理学让人们改变了这种想法,当我们对被观测体系的了解越来越精细,就不得不越来越多地与之相互作用。这种情况下我们需要的是一个相互作用的理论模型,而不是一个孤立体系的理论模型。……(待续)
量子力学实验中往往把宇宙划分为3个部分:被测系统S,观察者和仪器O,环境E。通常要求S只跟O相互作用,但不允许受到E干扰。量子力学的原理体系中扣掉测量原理,描述的就是一个孤立体系的演化。本来应该能够通过孤立体系的演化方程得出一个相互作用的方程,然后计算出观察者观察被测体系将会得到什么结果,但最初大家不知道怎样算,没人会求解整个宇宙的Schrödinger方程。于是物理学家从实验中得到了一条经验规律:测量原理。遗憾的是测量原理被上升到了量子力学基本原理的地位,引发了Einstein和Bohr旷日持久的争论。到了近代人们逐渐发现测量原理在许多条件下是可以导出的,而且导出的结果比测量原理所给出的细节更多。
————(可逆性、对称性、熵增、可观察量、规范……)
————(引入动机的数学模型,然后是偏好的评价函数,心理,行为,经济,博弈,生态系统,社会……)
- 开放的物理体系,相互作用
一个孤立体系可以被人为地通过边界划分为两个子系统,这两个子系统通过边界发生相互作用。每一个子系统的所有合法状态自然仍然由整个系统的运动学方程所谓一决定,但我们需要的是得到其中一个子系统的方程,这个方程只跟这个子系统的状态以及另一个子系统在边界上的状态有关……(待续)
- 孤立机器
所谓孤立机器,就是指这样一种东西:其任意时刻的状态演化方式完全取决于该时刻的状态。
将孤立机器的状态记为$$S$$,时间记为$$t$$,后继(Successor)时间记为$$t^+$$(比方说在t连续的情况下,$$t^+:=t+dt$$,离散情况下$$t^+:=t+1$$),演化规律记为$$U$$,那么上述定义相应的数学模型为:
$$S(t^+) =U[S(t)]$$
经典力学中的Hamilton方程就是描述这种孤立机器的方程。在Hamilton力学中,体系的状态用所有粒子的位置和动量来标记,而Hamilton方程则决定了体系状态的演化机制。
$$(p,q)(t+dt) = (p,q)(t) + \left(-\frac{\partial H}{\partial q}, +\frac{\partial H}{\partial p}\right)dt$$
还有量子力学方程$$\left| \psi (t) \right\rangle = e^{-\mathrm{i}H t}\left| \psi (0) \right\rangle$$,此时$$U = e^{-\mathrm{i}H t}$$
- 机器
所谓机器,就是指这样一种东西:
- 跟环境之间有一个分界线
- 环境在边界上的状态作为机器输入可以改变机器状态
- 机器在边界上的状态作为机器输出可以改变环境状态
- 任何时刻其行为和内部状态的变迁完全由该时刻的内部状态和环境输入唯一决定
如果环境也是一台机器,那么可以将环境的状态记为E,那么相应的数学模型为:
$$S(t^+) = U[S(t), \partial E(t)]$$
$$E(t^+) = U[E(t), \partial S(t)]$$
孤立机器是机器的特例,如果将环境和机器当作一个孤立整体,那么它又会变为一台孤立机器:
$$(S,E)(t^+) = (S(t^+),E(t^+)) = (U[S(t),\partial E(t)],U[E(t),\partial S(t)]) = U[(S,E)(t)]$$
如果把系统作为一个整体考虑,那么无论在机器和环境之间如何划界,对结果都是无影响的。
- 离散的孤立机器
所谓离散的机器,就是上述孤立机器定义中,状态变迁是按步骤跳变的,具有可数内部状态。
对于这样的机器,我们可以用步骤编号$$i$$代替时间$$t$$,用$$i+1$$代替$$t^+$$。于是其数学模型为:
$$S_{i+1} =U[S_i]$$
- 离散的机器
所谓离散的机器,就是上述机器定义中,状态变迁是按步骤跳变的,具有可数内部状态,对环境输入的分类能力也是可数的。
这样一台机器,其时间由步骤标记,记为$$i$$,于是:
环境从边界给机器的输入记为$$\partial E_i$$,机器从边界对环境的输出记为$$\partial S_i$$:
$$S_{i+1} = U[S_i, \partial E_i]$$
$$E_{i+1} = U[E_i, \partial S_i]$$
- 有限状态机器
如果离散的机器的状态数量是有限的,而且只能对环境输入做有限种分类,就叫做有限状态机器。但环境的状态可以是无限的,我们利用有限的时间和资源能够制造出这种机器。图灵机就是一种这样的机器。
- 图灵机
图灵机是一台有限状态的机器,对于图灵机来说,其环境就是一条无限长的格子纸带(因此环境拥有无限种状态),这个纸带的每一个格子都存贮一个符号,符号的种类是有限的。每一个步骤中,图灵机停泊在纸带上一个特定的位置,可以从这个位置读取符号,然后根据自身状态和读取到的符号唯一地作出一个动作,动作包括向当前位置写入一个符号,然后向左或向右移动一个格子,将自身状态迁移到新的状态。
除此之外,图灵机有一个特殊状态:起始状态,作为每一次执行计算任务的过程的初始状态。还有一组状态作为结束状态,其中一部分被标志为接受,另一部分被标记为拒绝,表示计算任务执行的后果。
可以证明,凡是能够用有限状态机器完成的计算任务,用图灵机都可以完成。还有人进一步证明,通常的神经网络计算模型的计算能力完全等价于图灵机,所谓通常的神经网络计算模型是指这样的神经网络:每一个神经元仅仅有有限种(典型是两种)兴奋状态。
但物理系统经常不是有限状态机器,因此不能简单地说所有的物理系统都等价于图灵机。但是,如果一个由连续参数描述的物理系统,如果对于人类来说可以通过物理量来区分的状态是有限的(比如由于热噪声的存在导致的测量误差),那么这个物理系统对于人类来说计算能力就并不比一个有限状态机器更强。
Christopher Nolan(诺兰)的电影Memento(记忆碎片)的基本创意来自于通用图灵机?
如果今后某天你的记忆突然因为某种意外而受损,受损之前的事情你都记得,但受损之后你只能记得很短时间内发生的事情,这种情况下你如何去完成一些复杂的任务呢?把自己要做的事情记录在纸片上就可以么?如果事情很复杂,而你压根就无法同时记住许多纸片的内容和逻辑关系呢?没错,你必须设计一个方案才可能完成复杂的任务,而且必须趁自己记忆没有受损的时候把方案设计出来,一旦你记忆受损了,你可能就根本没有能力设计出这样的方案,或者即便你设计出来,过一会也忘了。
如何设计这样一个方案呢?如果你是一个Geek,我想你可以从图灵机理论中寻找答案。图灵机除了自己当前的状态,什么都记不住,但是它有一个不限数量的数据纸带。图灵机根据自己的当前状态和从纸带上读取到的数据,按照事先设计好的规则执行动作(动作包括移动到纸带的不同位置,用新的数据覆盖纸带上原有的数据)并且跳转到新的状态。它每时每刻都只能看到纸带上的一个数据,完全不记得纸带的其他部分有什么,不记得它之前曾经读到过什么,却照样能够完成复杂的任务。
而『通用图灵机』是一种编制了特定程序的图灵机(不妨称之为图灵机U),U的功能是:如果你把任何一台图灵机(不妨称之为T)的程序和纸带上的输入用一定规则编码之后写在U的纸带上,U就可以产生跟T精确相同的输出(虽然效率可能会低得多)。而这个T甚至也可以是一台跟U完全相同的图灵机。注意,T的内部状态可以远远比U多得多,而U的最简单实现只需要一段很小的程序。
如果你记忆受损,那么你就像那程序很简单的通用图灵机U,而你的复杂任务就像那图灵机T,你需要在完全不记得之前都了解到了些什么信息的情况下根据你刚刚在纸片上看到的指令去执行任务,执行的过程中你可能还需要继续按照一定规则在纸片上做记录以备将来之用。
关于通用图灵机的东西,今后有时间可能会详细谈谈(不做承诺)。
吃苹果游戏和简单回合制游戏的通解
问题:
有20个苹果。
两个人轮流吃,可以吃一个,也可以吃半个。
吃半个的时候,可以吃原来就是半个的,也可以把一个完整的吃掉一半,也就是说可以有很多半个的苹果。
吃一个的时候,只能吃完整的一个。
没有其他选项。
最后吃苹果的人输。
请问,先吃还是后吃的有必胜的策略?这个必胜策略是什么?
答案:
后手有必胜策略。开始的时候先手怎么吃,后手就怎么吃,换言之后手总是设法保持剩下的整苹果和半苹果都是偶数即可。但当剩下的整苹果刚好只有两个的时候,肯定轮到先手吃,而后手此时不能跟先手学。如果先手吃掉一个整苹果,后手就把最后一个整苹果吃掉一半,让半苹果数量变成奇数。如果先手把一个整苹果吃掉一半,那么后手就吃掉最后一个整苹果,保持半苹果是奇数个。由于此时剩下奇数个半苹果,只能轮流吃,后手必胜。
这个问题的答案并不很难找到,通过倒推的方法很容易解决。但对于这类问题,有没有通用的解法呢?
现在就给出一个通用的解法:
通解:
- 确定游戏的完整状态(或者说局面)参数。比方说上面题目的完整状态参数就是(m,n,p),m是整苹果的个数,n是半苹果的个数,p是此时由谁行动。
- 游戏的行动规则确定了状态的迁移规则,或者说从某个状态出发可以到达哪些状态。比方说上面题目从(m,n,p)出发只能到达(m-1,n,~p)——吃一个整苹果、(m,n-1,~p)——吃一个半苹果、(m-1,n+1,~p)——吃半个整苹果,~p表示轮到对方行动。
- 游戏的初始规则确定了状态空间的出发点。
- 游戏的获胜规则确定了哪些状态是直接被判定为某一方获胜的。比方说上面问题中(0,0,p)意味着p获胜。在这些状态上,哪个玩家获胜,那么这个状态本身对于这个玩家来说就是有必胜策略的状态(废话,这个状态玩家p都已经获胜了那么p在该状态上当然是有必胜策略的)。
- 最后一步就是从这些已知的必胜状态出发,递归地确定其他的状态是哪个玩家有必胜策略的状态。如果对于一个玩家来说,根据行动规则某个轮到自己行动的状态存在到达某个自己必胜的状态的行动选择,那么该点对于自己来说也是必胜状态,于是可以递归地将所有的状态都标记上是哪一方必胜,初始状态也会被标记,因此就可以确定初始状态哪一方有必胜策略。
上述解法原则上可以寻找任何完全信息的确定性的回合制的游戏的必胜方和必胜策略。完全信息意思是任何一个回合中,每一个玩家对游戏的局面都有完全的信息,因此不包括桥牌这种不知道对手手中有什么牌的游戏。确定性意思是轮到任何一个玩家行动的时候他可以做出完全确定的行动,因此不包括飞行棋之类需要掷骰子决定如何行动的游戏。回合制意思是参与游戏的若干玩家是按照确定的规则轮流行动的,行为决策是离散的,因此不包括需要连续不断地做行为决策的游戏。类似围棋象棋五子棋跳棋(可以是多方跳棋)的游戏原则上都可以用上述解法寻找必胜方和必胜策略。只不过对于大部分流行的棋类游戏,上述方案会过于庞大以至于无法实际应用。但对于前面这种可以用于做智力题的完全信息确定回合制游戏,用上述通用解法大都可以迅速找到必胜方和必胜策略。
现在以前面的问题为例来说明这个通用解法。由于规则是两个人交替行动,所以(m,n,p)里面的p可以省略,用(m,n)代表状态就可以了,于是状态空间就变成了二维平面上的格点空间。而游戏的行动规则决定了从一个格点出发可以到达哪些格点。注意,由于取消了参数p,所以(0,0)状态无法确定谁胜谁负,但只有一个半苹果的状态(0,1)显然是后手必胜,因为先手只能吃掉这半个苹果而输掉游戏。另外,只有一个整苹果的状态(1,0)显然是先手必胜,但这一点可以通过(0,1)是后手必胜来导出,因此是可有可无的。有了这一个确定的格点,其他所有的格点先手还是后手有必胜策略都可以确定了:对于任何一个格点S,如果从S出发能够到达的所有格点全都是先手有必胜策略的格点,那么S就是后手有必胜策略的格点(因为后手在下一步变为先手)。否则,如果从S出发能够到达的格点之中至少有一个是后手有必胜策略的格点,那么S就是先手有必胜策略的格点(因为先手可以选择进入这个后手必胜的格点,自己同时变成后手)。
上图中,直接确定了(0,1)、(1,0)两个画圈的点的必胜方和必胜策略。图中选了几个典型的点示范了其他点的颜色是如何确定的:凡是从该点出发只能到达绿色点的点都涂成红色,凡是从该点出发至少有一个目标点是红色的点都涂成绿色。从两个画圈的点出发(其实只从(0,1)出发就够了),可以递归确定所有点的颜色。只需要做几步就可以发现规律得到答案。


插件比较