☰
算法一千零一夜 · 第十三夜 幻方,武侠世界里的数学迷宫
2026/10/22 7:34:32 网站建设 项目流程

第十三夜 · 幻方,武侠世界里的数学迷宫

大一春天的那一夜,我迷上了幻方。

现在,我想给那个天真的我写一封回信。这封信得从一个暑假讲起,一个被我整个泡进武侠里的暑假。

初三毕业后的那个暑假,中考完的我在家闲着没事儿,赶上了网上的暑假档,59集的《射雕英雄传》。追完正剧不过瘾,东邪、西毒那些外传又是一部不落地接着追。追得太狠,家里宽带的流量用超了。被人说了,花了230块钱。

那是我第一次知道,一个暑假可以疯成这样。

也是在那阵子,屏幕里的故事走到了黑泽。黄蓉挨了裘千仞一记铁掌,全靠一件软猬甲挡去了一半掌力才保住性命。郭靖背着她就往大理赶,去求一灯大师出手,黑泽里却先撞见了一位隐居的算学高手,瑛姑。

她考黄蓉的,是一道数阵。你的算法自然精我百倍,可是我问你,将一至九这九个数字排成三列,不论纵横斜角,每三字相加都是十五,如何排法。

黄蓉当它不算什么题。九宫之义,法以灵龟,二四为肩,六八为足,左三右七,戴九履一,五居中央。三句话,九个数各归其位,横行、竖列、斜角,每三个相加都是十五。

瑛姑呆住了。这位在黑泽里苦算了十几年的高手,只道这是我独创的秘法,原来早有歌诀传世。

我盯着屏幕,却记住了另一件事。就是从那一集开始,我遇见了一只大乌龟。传说大禹治水的时候,有神龟浮出洛水,背上驮着一幅点阵图,数至于九。后人管它叫洛书,把它当作世间最早的幻方。

这是我第一次见到幻方的样子,但是当时还不知道它叫幻方。

四年之后,大一春天,谭浩强的《C 程序设计》把这道题原封不动摆在了数组一章的课后。我把标准答案照着抄了三天,抄出来 289 行,全部写在一个 main 函数里,连收尾都是一句 system("pause")。有一处数组声明,我抄的是 int t[10000-1],一万减一,明明可以写 9999,当年的我却老老实实写了个算式,仿佛那道题连减法都要留给读者自己算。

敲完了还是没看懂,就觉得很刺激。

我不死心,特地查了一下这道题叫什么。书上叫它幻方,七百年前的杨辉叫它纵横图。那为什么叫幻方呢?我当时有一个很天真的想法,变幻莫测的方块。很搞笑吧,这就是年少的我。

今天写这一夜,我把当年抄的代码翻了出来。这段是里面对换的机关,收进过文本的只有这一段,其余 289 行的原貌,留在我的硬盘里。

k=n*n/2-1;for(i=0;i<n;i++)for(j=0;j<n;j++)if((j%4==i%4)||(i%4+j%4==3)) { a[i][j]=t[k]; k--; }

先说名字,这是给天真的我的第一句解答。

这只乌龟驮着的图,在中国最早叫洛书,画进方格之后叫九宫,南宋杨辉写《续古摘奇算法》,给它起名纵横图。而幻方这两个字,是近代从西文的 magic square 翻译过来的,幻,就是 magic。我当年猜变幻莫测的方块,居然歪打正着,猜中了它一半的灵魂。瑛姑说早有歌诀传世,传世的歌诀比她的苦算老了几百岁,比这只乌龟又小了几千岁。

然后是填法。黄蓉答的那道三阶题,其实有一个万能章法,杨辉把它写得清清楚楚,一共三条。把 1 放在第一行的正中间,对 2 到9 八个数的每一个数,走向右上一格;出了边界,就绕回另一头;格子里已经有数了,就退到原位的正下方。

三条规则,逐格执行,九个数各就各位。我的 Java 版照着章法走了一遍,跑出来的是 8 1 6、3 5 7、4 9 2,和谭浩强书页上的那一副一模一样。顺手续到五阶,二十五个数,每行每列每条对角线的和都是 65。这是奇数阶的通解,五阶、七阶、九阶,一路通用。

publicclassNight13Siamese{publicstaticvoidmain(String[] args){ fill(3); System.out.println(); fill(5); }staticvoidfill(intn){int[][]a=newint[n +1][n +1];// 下标 1..n,0 行 0 列弃用inti=1, j = n /2+1;// 1 放在第一行的正中间a[i][j] =1;for(intk=2; k <= n * n; k++) { i--; j++;// 尝试右上if(i <1&& j > n) { i +=2; j--; }// 右上同时出界:原位正下方else{if(i <1) i = n;// 出上边界:绕到最后一行if(j > n) j =1;// 出右边界:绕回第一列}if(a[i][j] !=0) { i +=2; j--; }// 该格已被占:退回原位正下方a[i][j] = k; }intm=n * (n * n +1) /2;// 幻方常数for(intr=1; r <= n; r++) {StringBuildersb=newStringBuilder();for(intc=1; c <= n; c++) sb.append(String.format("%4d", a[r][c])); System.out.println(sb.toString()); } System.out.println("n="+ n +" constant="+ m +" rowsOk="+ check(a, n, m)); }staticbooleancheck(int[][] a,intn,intm){for(intr=1; r <= n; r++) {ints=0;for(intc=1; c <= n; c++) s += a[r][c];if(s != m)returnfalse; }for(intc=1; c <= n; c++) {ints=0;for(intr=1; r <= n; r++) s += a[r][c];if(s != m)returnfalse; }intd1=0, d2 =0;for(intr=1; r <= n; r++) { d1 += a[r][r]; d2 += a[r][n +1- r]; }returnd1 == m && d2 == m; } }

瑛姑不服,把数字从九个加到了十六个,四四图。一至十六排成四行四列,纵横斜角之和全要相等。黄蓉还是那副轻描淡写的口气,就说四四图罢,以十六字依次作四行排列,先以四角对换,一换十六,四换十三,后以内四角对换,六换十一,七换十。这般横直上下斜角相加,皆是三十四。

看清楚了,先按顺序把 1 到 16 填满,然后只动八个格子,四个角互换,里面的四个角再互换,一道十六个数的题就解决了。我大一抄的那段 t 数组代码,if 条件收出来倒着放回去的,恰好就是这八个格子。黄蓉在黑泽的沙地上画的四步,和标准答案的算法,是同一个动作。

publicclassNight13Corners{publicstaticvoidmain(String[] args){intn=4;int[][]a=newint[n][n];for(inti=0; i < n; i++)for(intj=0; j < n; j++) a[i][j] = i * n + j +1;// 依次作四行排列swap(a,0,0,3,3);// 一换十六swap(a,0,3,3,0);// 四换十三swap(a,1,1,2,2);// 六换十一swap(a,1,2,2,1);// 七换十for(inti=0; i < n; i++) {StringBuildersb=newStringBuilder();for(intj=0; j < n; j++) sb.append(String.format("%4d", a[i][j])); System.out.println(sb.toString()); }intm=n * (n * n +1) /2;// 幻方常数 34booleanok=true;for(inti=0; i < n; i++) {intr=0, c =0;for(intj=0; j < n; j++) { r += a[i][j]; c += a[j][i]; }if(r != m || c != m) ok =false; }intd1=0, d2 =0;for(inti=0; i < n; i++) { d1 += a[i][i]; d2 += a[i][n -1- i]; } System.out.println("constant="+ m +" rowsColsOk="+ ok +" diag="+ d1 +"/"+ d2); }staticvoidswap(int[][] a,intr1,intc1,intr2,intc2){intt=a[r1][c1]; a[r1][c1] = a[r2][c2]; a[r2][c2] = t; } }

其实瑛姑没有考到第三题。黄蓉说,四四图,五五图,以至百子图,亦不足为奇。百子图是十阶幻方的古名,十除以四余二,是三类里最难缠的一类。瑛姑考了奇数阶和双偶阶,剩下的单偶阶,教材的标准答案替她考了。我的手稿三案全解,倒不是当年的我比黄蓉强,是标准答案比瑛姑勤快。

这一夜,我还把笨办法请了出来当裁判。三阶幻方,九个数随便排,一共 9 的阶乘种排法,362880 种。机器一种一种全部试完,用了 8 毫秒。成立的只有 8 个,而且这 8 个全是同一个解的旋转和镜像,本质唯一。

publicclassNight13Brute{staticintcount=0;publicstaticvoidmain(String[] args){int[]p=newint[9];for(inti=0; i <9; i++) p[i] = i +1;longt0=System.currentTimeMillis(); perm(p,0);longt1=System.currentTimeMillis(); System.out.println("tried=362880 magic="+ count +" essential=1 orientations=8 time="+ (t1 - t0) +"ms");// 8 个成立解全是同一个解的旋转与镜像,所以本质唯一,只有 1 个。}staticvoidperm(int[] p,intd){if(d ==9) {if(magic(p)) count++;return; }for(inti=d; i <9; i++) {intt=p[d]; p[d] = p[i]; p[i] = t; perm(p, d +1); t = p[d]; p[d] = p[i]; p[i] = t; } }staticbooleanmagic(int[] p){// p 排进 3x3:p[0] p[1] p[2] / p[3] p[4] p[5] / p[6] p[7] p[8]for(intr=0; r <3; r++) {ints=p[r *3] + p[r *3+1] + p[r *3+2];if(s !=15)returnfalse; }for(intc=0; c <3; c++) {ints=p[c] + p[c +3] + p[c +6];if(s !=15)returnfalse; }returnp[0] + p[4] + p[8] ==15&& p[2] + p[4] + p[6] ==15; } }

瑛姑苦算了十几年没有算出的谜底,是这八个解本来就是同一个解。黄蓉背得出歌诀,也未必数过那 362880 种排法。机器用 8 毫秒替她们两人都数完了。

那么四阶呢?十六的阶乘,超过 20 万亿种。机器还敢试吗?这一次笨办法走到了自己的边界,全枚举这条路,在三阶是 8 毫秒的坦途,在四阶就是一座翻不过去的山。所以黄蓉没有数,她用了章法。三阶可以交给笨办法,四阶必须交给聪明办法,而我的手稿里,两样都有。

笨办法有笨办法的天花板,聪明办法有聪明办法的来历。瑛姑输给黄蓉的那一天,输的从来不是智力,是她的十几年的苦算,没能遇上一句传世的歌诀。而歌诀能传世,是因为千年前有人也这样一格一格地摆过,摆出了那只龟背上的图案。

所以,天真的我,这是给你的回信。

你在黑泽里遇见的那只乌龟,驮的不是一段剧情,是几千年的传承。你抄的 289 行一行都没有白抄,它们只是在等,等到一台肯陪这道题把 362880 种摆法全部数完的机器。你看懂看不懂都要紧,刺激是真的,就够了。

这就是一个在试卷里面得不到数学趣味的孩子,用计算机再一次见证了那个变幻莫测的魔方。


① 真伪核对逐条照实登记。

Ⅰ 射雕与暑假 1983 版《射雕英雄传》由香港无线电视制作,1983 年首播,分《铁血丹心》《东邪西毒》《华山论剑》三辑;黄蓉一角由全港招募、金庸亲自参与评判选出,翁美玲当选(真)。初三暑假追剧与流量超额 230 元为本人亲历自述,追剧集数为当年记忆口径,含同系外传多部(真,亲历自述)。翁美玲 1985 年离世(真)。

Ⅱ 剧情链 黄蓉伤于裘千仞铁掌,软猬甲挡去部分掌力;郭靖携黄蓉往大理求一灯大师,途经黑泽遇瑛姑,瑛姑以算学相试后指点去向(真,据原著第二十九回《黑沼隐女》;俗称铁砂掌,原著功夫名为铁掌)。

Ⅲ 瑛姑 本名刘瑛,原为大理南帝贵妃,隐居黑泽苦研算学与武功十余年(真)。

Ⅳ 九宫歌诀 「九宫之义,法以灵龟,二四为肩,六八为足,左三右七,戴九履一,五居中央」承自《数术记遗》九宫旧注,金庸原著第二十九回黄蓉原话引此歌诀;三阶幻方即洛书,纵横斜角三数之和皆为十五(真)。

Ⅴ 神龟负书 《汉书·五行志》引刘歆说,天兴禹洛出书,神龟负文而出,列于背,有数至于九,禹遂因而第之;更早源头为《周易·系辞上》河出图、洛出书,未言龟(真)。民间有归于伏羲者,系河图洛书混称,本夜不取。

Ⅵ 杨辉与四四图 杨辉《续古摘奇算法》(1275 年)称幻方为纵横图并载构造法(真)。原著四四图一题,黄蓉以四角对换(一换十六、四换十三)加内四角对换(六换十一、七换十)解之,与本夜代码二为同一算法,对换后各行、列、对角线之和皆为 34(真,实跑验证)。百子图为十阶幻方古名(真)。

Ⅶ 词史 九宫图形化后入术数,杨辉创称纵横图;幻方系近代西文 magic square 之汉译(真)。

Ⅷ 谭浩强习题 《C 程序设计(第五版)》第六章课后习题收幻方,三阶示例 8 1 6、3 5 7、4 9 2(真)。

Ⅸ 手稿照登 本人大一文物,289 行,全部写在一个 main 函数里;本夜仅节选对换机关一段,gcc 实跑 n=3、4、5、6、8、10 全部成立(真,实跑验证,已披露)。

Ⅹ 实跑记录 JDK 21。连续摆法 n=3 输出 8 1 6、3 5 7、4 9 2,n=5 常数 65;四角对换法 n=4 常数 34,对角线 34;全排列枚举 362880 种,成立解 8 个,本质唯一,耗时 8 毫秒(真,实跑验证)。

② 今夜习题三道,评论区见。

Ⅰ 五五图。用连续摆法把 1 到 25 填成五阶幻方,算一算每行、每列、每条对角线的和,看看是不是都是 65。

Ⅱ 四四图。按黄蓉的四步亲手排一遍,先顺排一至十六,再四角对换、内四角对换。数一数你一共动了几格,再想想,为什么只动八格就够。

Ⅲ 笨办法的天花板。三阶幻方全枚举要试 362880 种,你的机器不到一秒。四阶是十六的阶乘,先估算它有多少种,再估算你的机器全试一遍要跑多久。想完之后你就知道,黄蓉为什么不数数,直接报了口诀。


本书目录导航上一篇:第十二夜 一个数学学渣,用计算机解奥数题 下一篇:第十四夜 幻方与奇妙的二进制 起点:卷首语·算法童话


版权声明:本文为作者原创,受著作权法保护。未经授权禁止转载、搬运、摘编、改编及任何形式的二次创作;个人学习引用请注明作者与原文出处。转载授权请联系作者(CSDN 私信)。侵权必究。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询