我算半个数独爱好者,但是市面上的数独软件很多都有广告,web版则需要联网,就想着自己用JAVA写一个。简简单单用swing,搭建好GUI后,发现最大的问题居然是数独题目的生成。从网上找题库实在是不甘心,索性动动脑子查查资料自己编一个。
数独的规则就不多介绍了,唯一让我没想到的是,小小的一个数独题目,居然还有这么多的坑和讲究。最难的部分是,如何保证这个数独题目是有唯一解的。数独题目中的数字越少,它有唯一解的可能性就越低。所以生成一个数字很少且有唯一解的数独要求还是很多的。
知乎查到的算法是,生成一个满数的数独矩阵,写一个解数独的方法,然后随机去掉一个数字,查看这个数独是否有多种解,然后再去掉一个,再次查看是否有多个解,使用回溯法,持续下去,直到找到可以去掉最多数字的时候。然而这种算法虽然可以生成很难的数独,但速度太慢,被我放弃了。(还有一个先随便生成题目然后验证是否有解,再验证是否有唯一解的方法,很适合生成困难数独,但我目前没有尝试。此方法可能表现更好,但还没写hhh,没有调查就没有发言权)
所以我用了以下思路:
第一步,生成一个满数的数独矩阵,用int[ ][ ] matrix弄个二位数组。
第二步,约定一个随意的值作为数独题目剩余的数字n,比如n=40
第三步,将数慢数独矩阵随意挖空81-40=41个数字
第四步,使用回溯法解数独,记录最后一次回溯产生在哪里。
第五步,把第四步的最后一次回溯返回false,也就是强行失败,看看是否还有解,若有则为第二个解。则此时此数独解不唯一,则返回第一步,重新生成数独。若无解,则此时此数独解唯一。
虽然看起来好像挺复杂,但是当n大于30时,计算机的运算量是完全可以接受的。虽然n为30时数独剩下的数字实在是太多了,也就是说太简单了。。更难的方法我还在探索中,准备尝试先随便生成题目的算法。
下面粘贴一些代码吧:
第一步:1,写个方法判断一下,当前位置如果填了某数,那会不会冲突,取个名叫isDup
2,把数组的第一行随便填满,这个很容易
3,用递归生成拥有全部数字的矩阵,还不一定成功,毕竟填着填着数独可能就无解了
//回溯算法生成全数字矩阵,t从9开始,因为第一行已经满了
public boolean addANumb(int t){
int x = t/9;
int y = t%9;
Random random = new Random();
int index = Math.abs(random.nextInt());
int atry = 0;
for (int i=0;i<9;i++){
atry = seed[(i+index)%9];
if (isDup(x,y,atry,matrix)){
matrix[x][y]=atry;
if (t==80)
return true;
return addANumb(t+1);
}
}
return false;
}
//返回一个全矩阵,用循环,失败了就重新构造重新生成一个
public void generateMatrix(){
boolean flag = false;
while (true){
GenerateNumb g = new GenerateNumb();
flag=g.addANumb(9);
if (flag){
this.matrix = g.matrix;
break;
}
}
}
第四步:写个求解数独的程序,还是递归回溯。别忘了记录最后一次递归是在哪里产生的。
//求解数独,memory用来记录最后一次回溯在哪里
public boolean solve(int count){
int x=count/9;
int y=count%9;
while (gqMatrix[x][y]!=0){
count++;
if (count>=81){
return true;
}
x=count/9;y=count%9;
}
if (memory<count){
memory=count;
}
for (int i=1;i<=9;i++){
if (isDup(x,y,i,gqMatrix)){
gqMatrix[x][y]=i;
if (count==80||solve(count+1)){
return true;
}else {
gqMatrix[x][y]=0;
}
}
}
return false;
}
第五步:看看最后一步递归失败的时候,有没有其他解,有解返回的是true。如果这时候返回false,那这个数独是有唯一解且一定有解的(毕竟是完整的数独矩阵去掉一些数字变成的嘛)
//,基本和上面的一样,只是用memroy判断是不是到了最后一次递归,是的话直接返回false
public boolean solveIfOnly(int count){
int x=count/9;
int y=count%9;
while (gqMatrix[x][y]!=0){
count++;
if (count>=81){
return true;
}
x=count/9;y=count%9;
}
if (count==memory){
memory=0;
return false;
}
for (int i=1;i<=9;i++){
if (isDup(x,y,i,gqMatrix)){
gqMatrix[x][y]=i;
if (count==80||solveIfOnly(count+1)){
return true;
}else {
gqMatrix[x][y]=0;
}
}
}
return false;
}
就这样结束了,写到头秃。
其实现在已经知道之前提到的方法估计表现更好,但还是耐着性子把这个方法写完了。毕竟这是正道(笨道)啊,人间正道是沧桑啊。。。。