首先,我采用的策略是随机生成终盘,而不是由一个终盘排列组合出100w个;
所以我又得满足随机性与时间的要求了。
在动手前,我去网上搜了一些数独终盘的生成函数,发现大体上有两种算法:
1.每格都在可能的数中随机生成,如果碰见没数可填的地方,就把该行或者是更多的指定的格子删除,再随机的跑一遍。每格地方都设置次数上限,如果达到了这个上限,就全部推倒重来;
2.直接采用递归,每格格子都用DFS求解;
简单分析一下两种的利弊:第一种方法确实保证了随机性,但是生成成功一个终盘还是很大程度上靠概率的。可能是他们在写随机生成数独的时候已经透彻的分析了这样做的成功率才这样做的,不然这种方法太不稳定;第二种方法很稳定,每次一定都能成功生成终盘,但是这个时间和随机性都无法保证,尤其是如果每次DFS的搜索都按123456789来的话。
所以我最终采用了两者折中的方法,在保证能生成成功的基础下,多随机生成一些数,然后用DFS去搜索剩下的点,找到一个可行解。当然,最终这个找到可行解的概率也要保证尽可能的大。
这个函数的设计其实是全凭个人的喜好的,所以我大概说一下我的设计过程:
首先我选择了每次生成棋盘中的所有的一个数的做法(比如第一次,生成9个1,当然这些1都是放在满足数独规则的位置上的)。在经过许多测试后,我发现这个方法不太稳定,在生成第四个数的时候就有失败的概率,大于五的概率就已经很低了,后面用DFS搜索终盘的成功率也不高;
接着我就选择了逐行生成。首先,第一行是可以完全随机的,但在第二行,如果简单的用取一个能填的随机数的方法失败率有接近50%。在分析过后发现,只有把第一行的789三个数全部放入第二行的前6个格子中才会成功,而如果全部随机的化,成功率只有52.5%(通过概率算出来的)。所以我手动的把这789三个数放入了第二行的前六个格中(为了保证成功率)。在第三行时,可以发现,取完全随机的成功率为100%,这个也不难分析,在第三行时,123,456,789列都相当于只受九宫格的影响,所以随机的取,必定成功。
本来我觉得随机生成前三行已经够了,但转念一想,我已经发现了第一行到第二行得手动放789三个数的规律,为何不按相同的规则生成123列呢?道理相同,我按照上面的方法又写了生成4,7号九宫格的代码。
接下来的4个九宫格,我尝试了生成其中一个九宫格(它们四个是等价的),但是在生成后搜索成功率只有80%,生成过程不搜索也很麻烦,所以我就放弃了。
最终我的生成策略为:随机生成前三行和前三列(也就是12347号九宫格),剩下的4个九宫格用DFS搜索得到可行解。
这样做我用10w组做过测试,生成的成功率为100%。