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

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


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

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

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; } } }
这样减少了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;//如果赋的值不满足条件就把这个格子归零,以便于后面的搜索 }
如果大家有什么更好的想法,也麻烦大家赐教呀! 好好学习,天天进步。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
一、数独说明:数独由九行,九列组成,又分为顺序排列的九宫。每行、每列及每宫都包含九个格,九个格中填放1到9的不重复的数字。 二、自动计算原理(三步法): 1、基础法:找出空格中唯一可填的数字。方法是,先假设某空格中可填入九个数字,然后去掉所在行、所在列、所在宫中的已有数字。余下如果是唯一一个数字,那么这个数字就是结果 2、找唯一法:例如果某行、某列或某宫中只剩一个空格,那么九个数字中缺少的那个就是结果。 3、求唯余法:对于存在多个可能值的空格,循环取其中一个作为假设值,然后反复利用方法1和方法2去测试,如果出错冲突或导致别的空格无值可填时,说明假设的值是错误的。并对别剩余未找到唯一值的空格进行同样操作,直至找到可行的一个方案。 三、自动出题,是自动求解的反向过程,先给出答案,再组合题目: 1、数独难易程度跟数独已知的数字个数有一定关系,但不是必然关系。可分为四级,根据网友“数独难度分级”的文章https://wenku.baidu.com/view/af550ed51a37f111f1855ba0.html,结果是分布在0到1之间的一系列值,值越少越容易。 2、出题时,先利用随机数往81个空格中填入不冲突的值。方法是,因为对角线的三宫中的数字互不干扰,用随机数填充这三宫,然后根据数独规则要求随机填入另外六宫。 3、这是最终结果,然后根据难易要求,随机将结果中的一定数量(可以用随机数)的方格清空。数独题已经形成。再根据网友提供的级别计算公式,计算形成的数独题的难易程度是否符合要求。(此时的数独答案不是唯一的) 4、难易程度具体计算公式是:两个空格所有可能值如果有依赖关系值为1,没依赖关系值为0。如此汇总所有空格之间的关系值为A,再除以空格个数B的18倍。即A/(18*B)。0—0.25为0级,0.25—0.5为1级,0.5—0.75为2级,0.75—1为3组。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值