憋屈地看了一个星期的论文,实在是没一点意思。为了娱乐一下自己,兼受同学启发,我决定用python写一个数独游戏。从大二开始装了个ubuntu系统后,就发现了这个有趣的游戏。之后每次进入这个系统,非得先来几局数独;再到后来,为了玩数独,特意进了这个系统。喜欢这个自带的游戏的特点,界面简单,只做必要的错误提示,可以回溯,是我用过的最理想的数独游戏了,呵呵。至于我自己打算做一个数独游戏,纯粹是为了现学现用。自从开始学python以来,从来没有真正用它写过一段带有自己设计思想的代码,实是不该。何不就为自己做一个windows下也能玩的称心如意的数独呢?好了,取名就叫Eva's Sudoku~!不过话说回来,这篇文章的主题是:[b]总共有多少个数独[/b]?
这绝对不是一个简单的排列组合问题。关于这个问题,《编程之美》有过一个简单的推断介绍。根据里面找提供的一个网址,我找到了关于这个问题的详细解决方案[1]。现在让我们先做一些简单的规定:
1. 我们要求的是合法的数独总数,定为N
2. 对于数独中的9个块(block),分别命名如下:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0010/f26c1969-c57a-355a-b366-b327904a6408.jpg[/img]
[/align]
3. 对于一个块(block),如果其内9个数字按如下方式排序,则称其为“标准型”:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0046/fb9a7d37-a316-3b6b-9862-4ab3ecd2ef9d.jpg[/img]
[/align]
总是可以通过替换使得一个块成为标准型的,这个过程叫做relabeling。这样的话,我们可以总是通过relabeling使B1成为标准型,对于一个B1是标准型的合法数独,它将可以通过relabeling得到9!个不一样的数独,因此,现在我们要求解的问题变成:
[b]B1是标准型的合法数独有多少个?[/b]设该数为N1,我们有N=N1*9!。
好了,现在B1确定下来了,为了使数独合法,B2-B3总共有多少种可能呢?首先考虑B2的第一行,填写它的数字要么全部来自B1的第二行或第三行,要么是B1第二、三行的混合。B2-B3第一行的所有可能如下:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0014/99053005-87d8-3f57-96bf-49c886efcc79.jpg[/img]
[/align]
如果填写B2第一行的数字全部来自B1的第二行或第三行(我们称其为[b]pure的情况[/b]),合法的B2-B3的填法如下(图为填写B2第一行的数字全部来字B1的第二行):
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0016/72a010b4-2463-3d39-a8f0-40c59ef8e834.jpg[/img]
[/align]
每行内部都可以全排,因此总共有(3!)^6种可能,再加上填写B2第一行的数字全部来自B1第三行的情况,对于pure的情况,总共有2*(3!)^6种可能。
如果填写B2第一行的数字是B1第二、第三行的混合(总共有18种组合方式),列取其中一种来看(其中a,b,c各代表1,2,3中的某个数),在这种情况下,包括全排列,总共会有3*(3!)^6种排列情况:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0018/32c5f36a-7dc2-3f18-8cb4-cb107debd13a.jpg[/img]
[/align]
因此,B2-B3的合法可能共有:
M1=2*(3!)^6+18*3*(3!)^6=56*(3!)^6=2612736
[b]为了让问题快速得到一个接近的解,我们使用探索法来进一步分析:[/b]
我们从九宫格的每一个块出发,如果每个块都由1~9填充,不再有其他限制,则合法的解共有N0=(9!)^9;进一步加上限制,块行中每一行都由1~9填充,其合法的解共有M2=9!*M1,九宫格里共有3个块行,因此使三个块行都合法的解共有M=M2^3,满足块行限制的解的个数在满足块限制解的个数中所占的比例为k=M/N0,同理满足块列限制的解的个数在满足块限制解的个数中所占的比例也为k。假设以上两个比例相互独立(事实上它们并不完全独立),则同时满足块行解和块列解在满足块限制解中的比较约为k^2,因此同时满足块行列限制的解(即九宫格的解)的总数约为N0*k^2~6.657*10^21。该估计结果与精确结果6.671*10^21相差大约0.2%。
[b]现在让我们进入精确结果的求解过程:[/b]
总的来说,求解的思路是暴力求解。设置两个loop循环,外循环穷举所有在B1为标准型的情况下B2-B3所有可能的解,内循环则在B2-B3的解的情况下穷举所有合法的数独的解。
[b]1. 外循环[/b]
现在我们知道了B2-B3所有可能的解的个数了(M1=2612736),外循环将执行200多万次!这一想就让人觉得遥遥无期,有什么办法能够减少穷举的次数呢?选代表~!我们总是能够通过行列变换、交换元素等方法来使一个数独转变成另外一个数独,这样的话,当我们知道了第一个数独的解法,我们自然而然就知道了第二个数独的解法,第二个数独将与第一个数独有同样的解法个数(不知道你明白我的意思了没?)由此我们可能在所有B2-B3可能的解(M1=2612736种)的集合中定义等价关系,从而使等价集中的待完成的数独具有相等个数的解。
怎么从一个数独转换成另一个数独呢?前面我们使用了relabeling技术,但事实上除了这个以外,还有其他技术,如块交换,例如我们交换B2跟B3,相应的解只需要交换B5跟B6,B8跟B9,完成B2-B3的解的个数将与完成B3-B2的解的个数相等。我们还可以对B1、B2、B3进行全排,下面的B4~B9只需做相应的顺序调整。虽然这将使B1不再是标准型的,但是别忘了我们还可以通过relabeling技术使它成为标准型的。甚至我们还可以对块的列、行进行全排。这些技术将帮助我们选举出一些特定的代表来进入内循环,内循环算出的该代表的个数乘以该代表所在集合的个数,将是该集合的合法的数独解的个数。我决定用数学公式来表达这些绕口的事情:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0022/a7b16342-e8fb-3e42-810f-b3ec13381c65.png[/img]
[/align]
现在我们的目标就是减少这个外循环的次数k。上面的分析我们知道,对B2、B3内的列进行全排列得到的解属于一个集合,对B2、B3进行交换得到的解也属于一个集合。因此我们选取的代表具有如下特点:
1. B2,B3的第一行是增序的;
2. B2的第一行的第一个数字小于B3第一行的第一个数字。
每个代表都存在2*(3!)^2种原始序列通过转换(lexicographical reduction)变成该代表,也就是说,解决了一个代表的解的个数,我们实际上解决了72种情况下的解的个数,现在我们把外循环k由2612736降到了36288(=2612736/72)。
上面的做法事实上并没有完全地利用全排列与relabeling技术。对于这36288种可能,我们可以对B1-B2-B3进行全排列,也可以对每个块中的三个列进行全排列,从而可以得到6^4=1296种不同的解,然后对B1进行relabeling,再对B2-B3进行lexicographical reduction从而使其成为一个代表,这将使原先的36288个代表进一步降到只剩下2051个代表(具体得出的结果是通过程序完成的);更进一步,可以对B1-B2-B3的行进行全排列,然后再将B1进行relabeling变换得到一个新的可能,最终,可以得到416个代表。
有时候交换特定元素并不改变数独其他元素的解,例如下面块行,完成他们的解的个数的相同的(因此它们也是等价的):
[img]http://dl.iteye.com/upload/attachment/0068/0028/647f4959-8b23-3c3a-8025-efb1cb2b81f7.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/0068/0030/c684883d-3be6-36a8-85d7-c5f06edce98b.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/0068/0032/c1c409c5-5e00-3545-ac62-5042bae127ed.jpg[/img]
除了列6跟列9的元素对(8,9)之外,列4、7的(1,2)、列1、4的(1,4)、列2、9的(5,8)以及列3、6的(6,9)之间的交换也能达到同样的效果。
更近一步,除了一对一的交换,还有2对2的交换等等,如下图:
[img]http://dl.iteye.com/upload/attachment/0068/0034/039aa95f-84a4-33d4-acc6-8e17949b47af.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/0068/0036/f249ce03-e0ec-3cfa-95f6-cbe85b4d80ec.jpg[/img]
通过消除这些等价关系,最终我们只剩下了71个代表。通过解答这71个代表,最终发现其实只有44种完全不同的代表。也就是说,最终我们可以将外循环降到k=44。
2. 内循环
内循环的工作就是针对B1-B2-B3的44种代表中的每一个,穷举所有合法的完整数独,然后根据我们上面提供的公式,即可求出N1进而求出N。但是我们精益求精,对B2-B3的进行选举代表的办法也同样可以运用到B4、B7上,当然,由于它受到更多的限制,我们只限制B4、B7的第一列是递增的,这样我们可以通过行的全排列来求出其他的数独,这样的做法使内循环的速度提升了72倍。
通过对这44种代表的穷举,我们最终将得到N1=18383222420692992,进一步得到N=N1*9!=6670903752021072936960 ~ 6.671*10^21。
------------------------
[1]http://www.afjarvis.staff.shef.ac.uk/sudoku/
这绝对不是一个简单的排列组合问题。关于这个问题,《编程之美》有过一个简单的推断介绍。根据里面找提供的一个网址,我找到了关于这个问题的详细解决方案[1]。现在让我们先做一些简单的规定:
1. 我们要求的是合法的数独总数,定为N
2. 对于数独中的9个块(block),分别命名如下:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0010/f26c1969-c57a-355a-b366-b327904a6408.jpg[/img]
[/align]
3. 对于一个块(block),如果其内9个数字按如下方式排序,则称其为“标准型”:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0046/fb9a7d37-a316-3b6b-9862-4ab3ecd2ef9d.jpg[/img]
[/align]
总是可以通过替换使得一个块成为标准型的,这个过程叫做relabeling。这样的话,我们可以总是通过relabeling使B1成为标准型,对于一个B1是标准型的合法数独,它将可以通过relabeling得到9!个不一样的数独,因此,现在我们要求解的问题变成:
[b]B1是标准型的合法数独有多少个?[/b]设该数为N1,我们有N=N1*9!。
好了,现在B1确定下来了,为了使数独合法,B2-B3总共有多少种可能呢?首先考虑B2的第一行,填写它的数字要么全部来自B1的第二行或第三行,要么是B1第二、三行的混合。B2-B3第一行的所有可能如下:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0014/99053005-87d8-3f57-96bf-49c886efcc79.jpg[/img]
[/align]
如果填写B2第一行的数字全部来自B1的第二行或第三行(我们称其为[b]pure的情况[/b]),合法的B2-B3的填法如下(图为填写B2第一行的数字全部来字B1的第二行):
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0016/72a010b4-2463-3d39-a8f0-40c59ef8e834.jpg[/img]
[/align]
每行内部都可以全排,因此总共有(3!)^6种可能,再加上填写B2第一行的数字全部来自B1第三行的情况,对于pure的情况,总共有2*(3!)^6种可能。
如果填写B2第一行的数字是B1第二、第三行的混合(总共有18种组合方式),列取其中一种来看(其中a,b,c各代表1,2,3中的某个数),在这种情况下,包括全排列,总共会有3*(3!)^6种排列情况:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0018/32c5f36a-7dc2-3f18-8cb4-cb107debd13a.jpg[/img]
[/align]
因此,B2-B3的合法可能共有:
M1=2*(3!)^6+18*3*(3!)^6=56*(3!)^6=2612736
[b]为了让问题快速得到一个接近的解,我们使用探索法来进一步分析:[/b]
我们从九宫格的每一个块出发,如果每个块都由1~9填充,不再有其他限制,则合法的解共有N0=(9!)^9;进一步加上限制,块行中每一行都由1~9填充,其合法的解共有M2=9!*M1,九宫格里共有3个块行,因此使三个块行都合法的解共有M=M2^3,满足块行限制的解的个数在满足块限制解的个数中所占的比例为k=M/N0,同理满足块列限制的解的个数在满足块限制解的个数中所占的比例也为k。假设以上两个比例相互独立(事实上它们并不完全独立),则同时满足块行解和块列解在满足块限制解中的比较约为k^2,因此同时满足块行列限制的解(即九宫格的解)的总数约为N0*k^2~6.657*10^21。该估计结果与精确结果6.671*10^21相差大约0.2%。
[b]现在让我们进入精确结果的求解过程:[/b]
总的来说,求解的思路是暴力求解。设置两个loop循环,外循环穷举所有在B1为标准型的情况下B2-B3所有可能的解,内循环则在B2-B3的解的情况下穷举所有合法的数独的解。
[b]1. 外循环[/b]
现在我们知道了B2-B3所有可能的解的个数了(M1=2612736),外循环将执行200多万次!这一想就让人觉得遥遥无期,有什么办法能够减少穷举的次数呢?选代表~!我们总是能够通过行列变换、交换元素等方法来使一个数独转变成另外一个数独,这样的话,当我们知道了第一个数独的解法,我们自然而然就知道了第二个数独的解法,第二个数独将与第一个数独有同样的解法个数(不知道你明白我的意思了没?)由此我们可能在所有B2-B3可能的解(M1=2612736种)的集合中定义等价关系,从而使等价集中的待完成的数独具有相等个数的解。
怎么从一个数独转换成另一个数独呢?前面我们使用了relabeling技术,但事实上除了这个以外,还有其他技术,如块交换,例如我们交换B2跟B3,相应的解只需要交换B5跟B6,B8跟B9,完成B2-B3的解的个数将与完成B3-B2的解的个数相等。我们还可以对B1、B2、B3进行全排,下面的B4~B9只需做相应的顺序调整。虽然这将使B1不再是标准型的,但是别忘了我们还可以通过relabeling技术使它成为标准型的。甚至我们还可以对块的列、行进行全排。这些技术将帮助我们选举出一些特定的代表来进入内循环,内循环算出的该代表的个数乘以该代表所在集合的个数,将是该集合的合法的数独解的个数。我决定用数学公式来表达这些绕口的事情:
[align=center]
[img]http://dl.iteye.com/upload/attachment/0068/0022/a7b16342-e8fb-3e42-810f-b3ec13381c65.png[/img]
[/align]
现在我们的目标就是减少这个外循环的次数k。上面的分析我们知道,对B2、B3内的列进行全排列得到的解属于一个集合,对B2、B3进行交换得到的解也属于一个集合。因此我们选取的代表具有如下特点:
1. B2,B3的第一行是增序的;
2. B2的第一行的第一个数字小于B3第一行的第一个数字。
每个代表都存在2*(3!)^2种原始序列通过转换(lexicographical reduction)变成该代表,也就是说,解决了一个代表的解的个数,我们实际上解决了72种情况下的解的个数,现在我们把外循环k由2612736降到了36288(=2612736/72)。
上面的做法事实上并没有完全地利用全排列与relabeling技术。对于这36288种可能,我们可以对B1-B2-B3进行全排列,也可以对每个块中的三个列进行全排列,从而可以得到6^4=1296种不同的解,然后对B1进行relabeling,再对B2-B3进行lexicographical reduction从而使其成为一个代表,这将使原先的36288个代表进一步降到只剩下2051个代表(具体得出的结果是通过程序完成的);更进一步,可以对B1-B2-B3的行进行全排列,然后再将B1进行relabeling变换得到一个新的可能,最终,可以得到416个代表。
有时候交换特定元素并不改变数独其他元素的解,例如下面块行,完成他们的解的个数的相同的(因此它们也是等价的):
[img]http://dl.iteye.com/upload/attachment/0068/0028/647f4959-8b23-3c3a-8025-efb1cb2b81f7.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/0068/0030/c684883d-3be6-36a8-85d7-c5f06edce98b.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/0068/0032/c1c409c5-5e00-3545-ac62-5042bae127ed.jpg[/img]
除了列6跟列9的元素对(8,9)之外,列4、7的(1,2)、列1、4的(1,4)、列2、9的(5,8)以及列3、6的(6,9)之间的交换也能达到同样的效果。
更近一步,除了一对一的交换,还有2对2的交换等等,如下图:
[img]http://dl.iteye.com/upload/attachment/0068/0034/039aa95f-84a4-33d4-acc6-8e17949b47af.jpg[/img]
[img]http://dl.iteye.com/upload/attachment/0068/0036/f249ce03-e0ec-3cfa-95f6-cbe85b4d80ec.jpg[/img]
通过消除这些等价关系,最终我们只剩下了71个代表。通过解答这71个代表,最终发现其实只有44种完全不同的代表。也就是说,最终我们可以将外循环降到k=44。
2. 内循环
内循环的工作就是针对B1-B2-B3的44种代表中的每一个,穷举所有合法的完整数独,然后根据我们上面提供的公式,即可求出N1进而求出N。但是我们精益求精,对B2-B3的进行选举代表的办法也同样可以运用到B4、B7上,当然,由于它受到更多的限制,我们只限制B4、B7的第一列是递增的,这样我们可以通过行的全排列来求出其他的数独,这样的做法使内循环的速度提升了72倍。
通过对这44种代表的穷举,我们最终将得到N1=18383222420692992,进一步得到N=N1*9!=6670903752021072936960 ~ 6.671*10^21。
------------------------
[1]http://www.afjarvis.staff.shef.ac.uk/sudoku/