数独的生成与破解算法分析

<span style=”font-size:18px;”> </span>
     首先在此向大家道歉,我在上一篇博文中转载了一篇关于数独的生成与破解算法的文章,其中作者的破解算法确实不错,也没有问题,但是其生产算法是有问题的。虽然初看起来每行每列都符合要求,但是是无解的。例如,我用其破解算法解由它生成算法生成的数独,结果没有解法出来。
     最近在网上看到不少人发帖,生成数独的算法如下:
          1 随机生成一个1-9的整数;
          2:随机生成一个坐标位置
         3:判断这个整数放在这个坐标位置处是否符合条件,也就是行,列,九宫格不重复
         4:满足条件就赋值,否则转到步骤1

       这种算法初看没有错,但是通常都是无解的。所以实际上生成一个有解的数独并不容易,许多人的构想是先用破解算法生成一个完整的数独,然后根据难易程度挖去n个数字,于是就得到了符合条件的数独。
     这种做法是可行的,我也是这样做的,但是先用破解算法生成一个完整的破解数独的前提是必须有一个有解的数独。 我曾经看到有人先在第一行随机填满1-9,然后在用破解算法破解,但是这样的话递归深度太大,有72个不确定度为8的位置,计算机容易失去响应甚至陷入死循环,我开始时也是这样做的,发现不行。

       为了解决这个问题,有两个优化方法,第一:另开一个线程,防止主线程失去响应;其次就是减少递归的深度,也就是在9×9的格子里面多天几个数字。我做的时候是在第一行和第九行分别填满1-9,且保证上下不重复, 这样就减少了9个深度,不确定度也减少到7了。

<span style=”font-size:18px;”>srand(time(NULL)); int i=0,j=0; for(i=0;i<9;i++) for(j=0;j<9;j++) date[i*9+j]=0;//先初始化 int a[9]={1,2,3,4,5,6,7,8,9}; int b[7]; int temp=0,s=0; for(i=0;i<9;i++) { s=rand()%8; temp=a[i]; a[i]=a[s]; a[s]=temp;//洗乱一个数组 } for(i=0;i<9;i++) date[i]=a[i];//给第一行赋值 for(i=0;i<9;i++) { s=rand()%8; temp=a[i]; a[i]=a[s]; a[s]=temp;//洗乱一个数组 } for(i=0;i<9;i++) date[72+i]=a[i]; while(1) { s=0; for(i=0;i<9;i++) if(date[i]!=date[72+i]) s++; if(s>=9) break; for(i=0;i<9;i++) { if(date[i]==date[72+i]) { temp=date[i]; date[i]=date[(i+1)%9]; date[(i+1)%9]=temp; } } } </span>

 这样减少了9个深度在破解,发现成功的概率还是只有50%左右,仍然还是会有近一半的可能陷入死循环的递归中,所以还是需要进一步减少深度。

      在此期间,我发现如果计算机能够解出数独,时间是几百毫秒的,如果解不出,估计电脑烧起来了也动不了,所以这时候可以选择折中的办法,减少深度,增大重复计算的次数。 也就是说在已经填好的9*9格子中随机填入少量的随机数,虽然不一定有解,但是只要填入的随机数不是太多,有解的概率还是挺大的。例如随机数数目n=6是,平均重复24次,但是计算机破解成功的概率是100%; 时间在2秒左右。

   具体代码是:

int total=6; while(total>0) { i=1+rand()%7; j=1+rand()%8; if(date[i*9+j]>0) continue;//如果该点存在,重新开始 temp=1+rand()%9;//随机生成一个数 while(1) { if(judge(i,j,temp,date)) //如果可以的话 { date[i*9+j]=temp; break; } else temp=((temp+1)%10==0)?1:(temp+1); } total–; }

     其次,还可以把第一列的数字填满,这样不需要填入随机数就可以了,而且效率更高,我也是这样做的。

for(i=0;i<9;i++) { s=rand()%8; temp=a[i]; a[i]=a[s]; a[s]=temp;//洗乱一个数组 } s=0; for(i=0;i<9;i++) if(date[0]!=a[i] && date[72]!=a[i]) b[s++]=a[i]; for(i=1;i<=7;i++) date[i*9]=b[i-1]; temp=3; while(date[9]==date[1]||date[9]==date[2]) { s=date[9]; date[9]=date[temp*9]; date[temp*9]=s; temp++; } temp=3; while(date[18]==date[1]||date[18]==date[2]) { s=date[18]; date[18]=date[temp*9]; date[temp*9]=s; temp++; } temp=3; while(date[54]==date[73]||date[54]==date[74]) { s=date[54]; date[54]=date[temp*9]; date[temp*9]=s; temp++; } temp=3; while(date[63]==date[73]||date[63]==date[74]) { s=date[63]; date[63]=date[temp*9]; date[temp*9]=s; temp++; }    

     这样计算机就可以得到一个完整的数独了,虽然时间长了点,但是不会使递归失去响应,还是挺不错的。

       接下来我还是把破解的算法写在下面的,具体的可以看我的上一篇博文,是转载的,确实不错的算法。

/**********计算不确定度函数********************/ int i,j,is,js,count=0; int check(int y,int x,int *mark,int *map) { i=j=is=js=count=0; for(i=1;i<=9;++i) mark[i]=0;//初始化 for(i=0;i<9;++i) mark[map[y*9+i]]=1;//计算该行中已经确定的值对应的mark[i]的变量值为1 for(i=0;i<9;++i) mark[map[i*9+x]]=1;//计算该列中确定的值 is=y/3*3; js=x/3*3; for(i=0;i<3;++i)//计算九格里面的确定的值 { for(j=0;j<3;++j) mark[map[(is+i)*9+js+j]]=1; } for(i=1;i<=9;++i) if(mark[i]==0) count++; return count; } /**********破解主函数*******************/ int mark[10],c=0; int im=-1,jm,min=10; void dfs(int solves,int *map)//执行的主要函数 { if(solves>=1)//这两句是我自己加的,主要是控制结果的数量。因为一个数独可能有几百种结果,而我们不需要这么多,而且递归次数越多耗时越多 return; int i,j; im=-1,jm=0,min=10; for(i=0;i<9;++i) { for(j=0;j<9;++j) { if(map[i*9+j])//如果不为零时,不确定度为0,不需要计算不确定度了 continue; c=check(i,j,mark,map);//用于计算不确定度 if(c==0)//表示不确定度为0,前面的数字填错了,或者是说这个数组本身就存在问题 return; if(c<min)//用于计算最小的不确定度,从最小的不确定度开始搜索 { im=i; jm=j; min=c; } } } if(im==-1)//这是当所有格子里面的数值都确定时显示结果 { solves++; //printf(“No. %d:\n”,++solves); //display(map); return; } check(im,jm,mark,map); for(i=1;i<=9;++i) { if(mark[i]==0) { map[im*9+jm]=i;//将所有可能的数值都赋值一次 dfs(solves,map); } } map[im*9+jm]=0;//如果赋的值不满足条件就把这个格子归零,以便于后面的搜索 }
   如果大家有什么更好的想法,也麻烦大家赐教呀! 好好学习,天天进步。

    原文作者:计算机技术
    原文地址: https://www.cnblogs.com/it20120227/archive/2011/11/14/2370907.html
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞