了解一下数独

  • 数独规则
  • 最常规的回溯法
  • 再看这
  • 第一种,唯一候选数法
  • 第二种,隐式唯一候选数法
  • plan B
  • 此处附上我的测试
  • 写在最后
  • 附上代码

数独规则

数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次。此处出现的候选数我们稍后再提。

※不要求斜线也满足条件

最常规的回溯法

对于常规,也可以说是最常使用的一种算法,回溯法。它可以用来解决很多问题,比如这里的数独问题。
先说说它的优点,对于程序员来说,代码量很少,往往几层嵌套循环就可以解决问题,逻辑也很简单, 编写也很快捷。但这里我们为什么不推荐使用呢,因为它对于这个问题,时间复杂度有些高。
首先这个算法类似与我们平时说的试。先对于一个空格,依次读取该格所在行,所在列,所在宫,保证所填数与三者中任一数都不重复。比如上面的图,第一行第二列那个格,检查该行列宫后,出现数为123567,所以它可以填489,根据回溯法,就填4,然后看第二空格,以此类推,直到有一空格没有数字可填,则将上一空格所填数更改,如果该格也没有可以更改的数,那么再退上一格。。。。。所以到后来,如果运气不好,这个方法可能会试非常非常多次,所以我认为对于单纯的计算机效率来说,这个方法不算优。

再看这

此时,再将我要写的算法之前,先讲一下人工数独解法。此处要引入一个概念,叫做候选数。
候选数顾名思义,就是候选的数字。对于每一个要填的空格,都有一个候选数组。比如第一行第二列的那个空格,489就是它的候选数,如果还是不懂,百度一下你就知道!

第一种,唯一候选数法

其实这些解法只是数独解法的冰山一角,往往一个复杂的数独题目要使用很多很多的方法。具体可参考这里 但是对于实现起来的逻辑,我挑选了一共两种解法交替使用。第一种,也是最好用的,唯一候选数法。即按照上面提到的,将每一个空白格按照行列宫依次查找,最后得出其候选数组,然后找到那个候选数组中候选数个数为1的,说明这个格只能填这个数字,然后填入。之后更新所填格所在的行列宫的其他空白格的候选数组,也就是减去新填入的这个数字。

当某行已填数字的宫格达到8个,那么该行剩余宫格能填的数字就只剩下那个还没出现过的数字了。成为行唯一解。
唯一候选数法的思路是:选定一个空白格,从其初始候选数中删除与它在同一行、同一列以及同一宫中已经确定的数字,当这一格的候选数为1时,这一格正确的数字就是剩下的唯一一个候选数。如此重复,直至找到全部解或者无法再用此方法进行下去。
比如这个题,我们可以发现中间那个格子,填5,然后将该行列宫中所有的候选数都删去5,发现该行第二列那个各自只能填2,依次类推,到后来会出现越来越多的唯一候选数格子。

第二种,隐式唯一候选数法

与第一种方法类似,只是有时我们发现所有空白格中,没有一个候选数个数为1的,此时第一种方法已经行不通了,我们就要用到隐式法了。虽然表面上没有候选数
当某个数字在某一行(列、宫)各单元格的候选数中只出现一次时,那么这个数字就是这一列的唯一候选数了.这个单元格的值就可以确定为该数字. 这时因为,按照数独游戏的规则要求每一行(列、宫)都应该包含数字1~9,而其它单元格的候选数都不含有该数,则该数不可能出现在其它的宫格,那么就只能出现在这个宫格了。

比如这个题,会发现没有唯一候选数格子,此时第一种方法失败,然后用第二种方法,可以发现第三行第七列中候选为5的那个格子,该行的候选数中再没有出现5,所以这里必填5,然后更新该行列宫的候选数,以此方法,一旦出现候选数为1的格子,立刻停止此方法,调用第一个方法,直到第一个方法再次失败,再调用此方法。

plan B

如果两种方法都失败了呢?那就无可奈何了,我的算法逻辑仅写到这里。有兴趣的朋友可以写更多的算法,根据上面提到的链接,或者百度一下数独的解法。
但对于我的方法就结束了,这时数独如果还没解完怎么办?那就回到经典的回溯法去解了。但有人也许会问,这不还是一样吗?还是需要回溯法。
其实不然,回溯法之所以对于复杂数度题目解起来很慢,是因为空白格太多,所以可能的情况太多了。而此时我们经过上面两种方法交替使用后,可以发现填出来很多格子,再不济只填出一个格子,那样对于回溯法来说,少的复杂度也是几何倍数的。所以总体来说,经过上面两种解法解过的数度题,要不解完,要不难度也不会很大了。此时调用回溯法完善就很快捷了。
PS:如果上面两种方法一个空白格也填不出来,我也没办法了,因为数独解法太多了,有兴趣的朋友可以继续写写Plan c,Plan d。。。。

此处附上我的测试

就下图这个较为复杂数独来说,比如此时右侧数独,因为所给数不多,而且分布较为集中,所以此时若按常规的回溯方法来解,势必回有较大的复杂度。


我在同一台设备上进行了多次重复实验。单一采用回溯法,我们解题大概需要0.28s左右

但用了我的两种方法后,数独变成了这个样子
此时我的两种方法都用不了了,调用Plan B回溯法,但就这么几个空白格,相信人都可以手动推算出来,所以此时时间复杂度大大降低。

而当我们使用综合方法时,此时的时间大致在0.16s左右

※也许看起来快了其实并没有多少,但是你要知道,这么复杂的题目,对于计算能力恐怖的计算机来说,对于计算机来说,0.01s的优势都是多大的优化。

但是对于一些简单的数独题目,这个方法的优势就不是很大了
下图是回溯法的

这是我的方法的

但对于复杂题目,再来个例子
回溯法的

我的

还是快了很多哦!

写在最后

第一次写这个博客,可能说的不是很清楚,我也只是一个大二学生,程序小白,这个代码的优化程度根本不够,而且逻辑很复杂,很庞大。总之,写的太傻了,但是我认为的比较好的解数独方法,大家不要杠我~~最后欢迎各位提个意见!

附上代码

觉得不错的话,记得顶一下哦!
有问题可以评论区留言哦

#include<stdio.h>
#include<string.h>
#include<stdlib.h>//原题
/*int a[9][9]={{5,3,0,0,7,0,0,0,0},{6,0,0,1,9,5,0,0,0},{0,9,8,0,0,0,0,6,0},{8,0,0,0,6,0,0,0,3},{4,0,0,8,0,3,0,0,1},{7,0,0,0,2,0,0,0,6},{0,6,0,0,0,0,2,8,0},{0,0,0,4,1,9,0,0,5},{0,0,0,0,8,0,0,7,9}};*/
//难度3
/*int a[9][9]={{0,0,0,1,0,0,2,6,0},{7,0,0,0,3,0,0,0,0},{3,0,2,0,8,0,4,0,0},{0,0,0,4,0,8,0,0,1},{0,3,5,0,0,0,9,4,0},{2,0,0,3,0,5,0,0,0},{0,0,6,0,5,0,7,0,9},{0,0,0,0,4,0,0,0,8},{0,5,7,0,0,9,0,0,0}};*/
//难度4/*int a[9][9]={{0,0,1,0,5,0,0,0,0},{9,0,7,0,0,3,0,0,0},{0,4,3,9,2,8,0,0,0},{0,5,0,0,0,0,0,4,2},{0,7,0,0,0,0,0,8,0},{1,9,0,0,0,0,0,6,3},{0,0,0,5,1,6,4,2,0},{0,0,0,3,0,0,9,0,6},{0,0,0,0,7,0,8,0,0}};*/
/*int a[9][9]={{0,1,0,0,0,8,4,0,7},{9,5,0,0,0,0,0,0,0},{0,0,8,0,1,0,0,0,0},{0,8,2,0,0,0,0,0,0},{7,0,0,4,0,6,0,0,8},{0,0,0,0,0,0,6,2,0},{0,0,0,0,5,0,7,0,0},{0,0,0,0,0,0,0,8,2},{5,0,3,2,0,0,0,1,0}};*///难度5
int a[9][9]={{9,0,0,0,1,7,0,0,2},{5,6,3,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0},{0,0,0,5,0,0,0,8,0},{0,7,0,0,0,0,4,0,0},{0,2,0,0,0,0,0,3,0},{8,0,0,3,0,0,0,0,0},{4,0,0,0,0,0,0,0,7},{0,0,0,0,0,0,0,0,0}};/*int a[9][9]={{9,0,0,0,1,7,0,0,2},{5,6,3,0,0,0,0,0,0},{0,0,0,0,0,0,0,0,0},{0,0,0,5,0,0,0,8,0},{0,7,0,0,0,0,4,0,0},{0,2,0,0,0,0,0,3,0},{8,0,0,3,0,0,0,0,0},{4,0,0,4,8,9,0,0,7},{0,0,0,0,0,0,0,0,0}};*/struct King
{int num;int candidata[9] = {1,2,3,4,5,6,7,8,9};  //候选字数组int row;int line;int Gnum;int cannum = 9;
};struct King shudu[81];
int G[9];
int check = 0;      //检查标志,若数独完成,则check = 1
int measure1 = 0,measure2 = 0,measure3 = 0;     //判断使用哪些方法void del(King *shudu,int num);
int read(int candidata[]);
int update(int row,int line,int Gnum,int num);
void seek();
void sub(int backup[],int candidata[]);
void implicit();
void search(int n);
int canplace(int n,int i);
void output();
void renew();
void out();int main()
{int i,j;int m,n,b;int e,d;int answer = 1;     //数独解情况printf("解数独程序开始!\n原数独为:\n");output();printf("\n数独解为:\n");for(i=0;i<9;i++)     //初始化数独结构体数组{int n = 0;int m = 0;for(j=0;j<9;j++){int x = i*9+j;shudu[x].num = a[i][j];shudu[x].row = i;shudu[x].line = j;if(i<3)if(j<3){G[0] = 0;shudu[x].Gnum = 0;}else if(j<6){G[1] = 3;shudu[x].Gnum = 1;}else{G[2] = 6;shudu[x].Gnum = 2;}else if(i<6)if(j<3){G[3] = 27;shudu[x].Gnum = 3;}else if(j<6){G[4] = 30;shudu[x].Gnum = 4;}else{G[5] = 33;shudu[x].Gnum = 5;}elseif(j<3){G[6] = 54;shudu[x].Gnum = 6;}else if(j<6){G[7] = 57;shudu[x].Gnum = 7;}else{G[8] = 60;shudu[x].Gnum = 8;}}}for(i=0;i<81;i++)     //初始化候选字{if(shudu[i].num == 0){for(j=shudu[i].row*9;j<shudu[i].row*9+9;j++){if(shudu[j].num!=0){del(&shudu[i],shudu[j].num);}}for(m=shudu[i].line;m<81;m=m+9){if(shudu[m].num!=0){del(&shudu[i],shudu[m].num);}}d=G[shudu[i].Gnum];for(e=0;e<3;e++){for(j=0;j<3;j++){if(shudu[d].num!=0){del(&shudu[i],shudu[d].num);}d++;}d=d+6;}}}seek();     //调用唯一候选数解法renew();for(i=0;i<9;i++){for(j=0;j<9;j++){if(a[i][j] == 0){answer = 0;break;}}if(answer == 0){break;}}if(answer == 1){printf("\n解此数独使用方法:\n");if(measure1 == 1)printf("唯一候选法 ");if(measure2 == 1)printf("隐式候选法 ");if(measure3 == 1)printf("回溯法 ");printf("\n");}else{printf("此数独无解!\n");}
}int change;                 //调用隐式法开关
void seek()                 //唯一候选数解法
{int shut = 0;        //遍历开关change = 1;              //隐式法开关打开check = 1;int v,i;for(i=0;i<81;i++)//寻找唯一解{if(shudu[i].num == 0&&shudu[i].cannum!=1){shut = 1;                   //发现空单元,需再次遍历,打开开关check = 0;}if(shudu[i].cannum == 1){shudu[i].num = read(shudu[i].candidata);//读取唯一候选字并填入shudu[i].cannum--;update(shudu[i].row,shudu[i].line,shudu[i].Gnum,shudu[i].num);    //更新所在行列的元素候选字change = 0;         //关闭隐式法开关measure1 = 1;}if(i == 80){if(change == 0)         //若隐式法法开关为关闭{if(shut == 1){seek();shut =0;     //遍历开关关闭}else{out();}}else{if(check == 0){implicit();}else{out();//输出}}}}
}void del(King *shudu,int num)
{int i;for(i=0;i<9;i++){if(shudu->candidata[i] == num){shudu->candidata[i] = NULL;if(shudu->cannum>0)shudu->cannum--;}}
}
int read(int candidata[])
{int i;for(i=0;i<9;i++){if(candidata[i] != NULL)return(candidata[i]);}//需要添加读取失败提示!!!!!!
}
int notice;     //出现唯一候选数开关(默认为关)
int update(int row,int line,int Gnum,int num)
{int i,j;int x = G[Gnum];for(i=row*9;i<row*9+9;i++){if(shudu[i].num==0){del(&shudu[i],num);if(shudu[i].cannum == 1){notice = 1;         //打开唯一候选数开关}}}for(i=line;i<81;i=i+9){if(shudu[i].num==0){del(&shudu[i],num);if(shudu[i].cannum == 1){notice = 1;         //打开唯一候选数开关}}}for(i=0;i<3;i++){for(j=0;j<3;j++){del(&shudu[x],num);if(shudu[i].cannum == 1){notice = 1;         //打开唯一候选数开关}x++;}x=x+6;}}
int count = 0;              //backup元素个数计数器
int repeat;             //隐式遍历开关(若有更新则打开)
void implicit()             //隐式唯一候选数解法
{int i,j,m,n,f,g;notice = 0;     //关闭唯一候选数开关repeat = 0;     //关闭隐式遍历开关int check1;     //数独单元完成开关for(i=0;i<81;i++){int backup[9];              //候选字备用数组if(shudu[i].num == 0){check1 = 0;      //数独单元未完成for(g=0;g<9;g++){backup[g] = shudu[i].candidata[g];}count = shudu[i].cannum;for(j=shudu[i].row*9;j<shudu[i].row*9+9;j++)        //按行搜索隐式候选字{if(shudu[j].num == 0 && j!=i){sub(backup,shudu[j].candidata);if(count == 0)break;}}if(count == 1){shudu[i].num = read(backup);//读取隐式唯一候选字并填入shudu[i].cannum = 0;update(shudu[i].row,shudu[i].line,shudu[i].Gnum,shudu[i].num);    //更新所在行列的元素候选字repeat = 1;check1 = 1;measure2 = 1;if(notice == 1){break;}}for(g=0;g<9;g++){backup[g] = shudu[i].candidata[g];}count = shudu[i].cannum;for(m=shudu[i].line;m<81;m=m+9)             //按列搜索隐式候选字{if(shudu[m].num == 0 && m!=i){sub(backup,shudu[m].candidata);if(count == 0)break;}}if(count == 1){shudu[i].num = read(backup);//读取隐式唯一候选字并填入shudu[i].cannum = 0;update(shudu[i].row,shudu[i].line,shudu[i].Gnum,shudu[i].num); //更新所在行列的元素候选字repeat = 1;check1 = 1;measure2 = 1;if(notice == 1){break;}}for(g=0;g<9;g++){backup[g] = shudu[i].candidata[g];}count = shudu[i].cannum;n=G[shudu[i].Gnum];for(f=0;f<3;f++)                //按宫搜索隐式候选字{for(j=0;j<3;j++){if(shudu[n].num == 0 && n!=i){sub(backup,shudu[n].candidata);if(count == 0){break;}}n++;}n=n+6;if(count == 0){break;}}if(count == 1){shudu[i].num = read(backup);//读取隐式唯一候选字并填入shudu[i].cannum = 0;update(shudu[i].row,shudu[i].line,shudu[i].Gnum,shudu[i].num); //更新所在行列的元素候选字repeat = 1;check1 = 1;measure2 = 1;if(notice == 1){break;}}if(check1 == 0){check = 0;}}}if(repeat == 1)         //若数独有更新,则再次搜索隐式候选字{if(check == 0)      //若数独未完成{implicit();}else{out();//输出}}else{if(check == 0)      //若数独未完成{if(notice == 1)     //若出现唯一候选字,则调回唯一法{seek();}else{renew();//out();measure3 = 1;search(0);// 调用回溯!!!!!!!!!!!!!!!!!}}else{out();}}
}void sub(int backup[],int candidata[])         //删除缓存数组中的候选字
{int k,j;//for(k=0;k<9;k++)printf("backup为%d",backup[k]);for(k=0;k<9;k++){if(backup[k] != NULL){for(j=0;j<9;j++){//printf("!");if(candidata[j] != NULL){if(backup[k] == candidata[j]){backup[k] = NULL;count--;}}}}}
}
void renew()
{int i,j;for(i=0;i<9;i++){for(j=0;j<9;j++){a[i][j] = shudu[i*9+j].num;}}
}void search(int n)
{int i;if(n==81){output();printf("\n解此数独使用方法:\n");if(measure1 == 1)printf("唯一候选法 ");if(measure2 == 1)printf("隐式候选法 ");if(measure3 == 1)printf("回溯法 ");printf("\n");exit(0);}else if(a[n/9][n%9]!=0){search(n+1);//若该位置上已有数字,则跳转至下一个位置}else if(a[n/9][n%9]==0){for(i=1;i<=9;i++){if(canplace(n,i))//判断该位置上能否放置数字i,若可以为其赋值,跳转至下一位置{a[n/9][n%9]=i;search(n+1);}a[n/9][n%9]=0;//若若找不到可以满足条件的数放置在该位置,还原它本来的值,回溯寻找下一组可能的解,每次调用完之后都需要让它返回与原来的值}}
}int canplace(int n,int i)
{int j,k,flag=1;for(j=0;j<=8;j++)//判断该列上是否有该数字{if(a[n/9][j]==i){flag=0;break;}}if(flag==1){for(j=0;j<=8;j++)//判断该行是否有该数字{if(a[j][n%9]==i){flag=0;break;}}}if(flag==1)//判断其所在的小九宫格里是否有该数字{for(j=(n/9/3)*3;j<(n/9/3)*3+3;j++){for(k=(n%9/3)*3;k<(n%9/3)*3+3;k++){if(a[j][k]==i){flag=0;break;}if(flag==0)break;}}}return flag;
}void output()//输出数组
{int i,j;for(i=0;i<9;i++){for(j=0;j<9;j++){printf("%d  ",a[i][j]);}printf("\n");}
}void out()
{int i;for(i=0;i<81;i++){if(i%9 == 0){printf("\n");}printf("%d  ",shudu[i].num);}
}

数独-比回溯法更优的人类思维逻辑的数独解法相关推荐

  1. python数独伪代码回溯法_数独的暴力回溯解法和Python GUI

    数独起源于18世纪初瑞士数学家欧拉等人研究的拉丁方阵,20世纪70年代,经过美国及日本学者的推广和改良,定名为数独(Sudoku),大致的意思是"独个的数字"或"只出现一 ...

  2. 阿法狗算法与人类思维的本质性差别,人还有好长的路要走

    看了一些对阿法狗数学算法的文章,以我现在还残留的数学知识得知,搜索算法和学习.评估和决策算法都属于数据处理一类的,比如说,在不能穷尽可能性的前提下,通过有限深度的演算来评估各个可能落点的分值以确定实际 ...

  3. python数独伪代码回溯法_数独 #回溯算法 #CTF

    1. intro:巅峰极客的一道逆向 刷巅峰极客2020里的rev题fu!kpy,复杂得不行但是看到if d[1][0] != '8' or d[1][7] != '2'和if check(h1) ! ...

  4. python回溯法解9*9数独

    文章目录 前言 一.回溯法解数独代码 总结 前言 看了labuladong算法小抄里的回溯法,照着模板,加入自己的理解,用python做了个解9*9数独的回溯法,. 一.回溯法解数独代码 直接上代码: ...

  5. 基本算法-回溯法(迷宫问题)

    作者:翟天保Steven 版权声明:著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处 前言 本文介绍一种经典算法--回溯法,可作为迷宫问题的一种解法,以下是本篇文章正文内容,包括算法 ...

  6. 彻底搞懂回溯法(本文真的很详细)

    目录 回溯法理论基础 组合问题 组合问题 组合总和 组合总和(一) 组合总和(二) 组合总和(三)(本题去重特别重要) 多个集合求组合 切割问题 子集问题 子集问题(一) 子集问题(二) 递增子序列 ...

  7. 算法笔记之回溯法(一)——溯洄从之,道阻且长;溯游从之,宛在水中央。

    回溯法理论基础 回溯法是一种搜索算法,从本质上来说,回溯法是一种穷举法,穷尽其所有可能而举其可行解:尽管回溯法有剪枝等操作,但也只是去除一些明显不可行的部分,仍改变不了回溯法暴力搜索的本质. 虽然回溯 ...

  8. P1118 [USACO06FEB]数字三角形`Backward Digit Su`… 回溯法

    有这么一个游戏: 写出一个11至NN的排列a_iai​,然后每次将相邻两个数相加,构成新的序列,再对新序列进行这样的操作,显然每次构成的序列都比上一次的序列长度少11,直到只剩下一个数字位置.下面是一 ...

  9. 三进制计算机_三进制半导体诞生,逻辑比二进制更接近人类思维?

    7月17日,据韩媒报道,韩国一个科研团队已成功在大尺寸晶圆上成功实现了一种更节能的三元金属氧化物半导体. 韩国蔚山科学技术大学(UNIST)电子和计算机工程系教授Kim Kyung Rok及其团队,在 ...

  10. 【算法分析】回溯法解数独(九宫格)算法

    这篇文章,是来详细介绍怎样写出一个算法,来解出所有的数独问题.算法的程序运行时间,缩减在了毫秒级别.等到这篇文章结束,我会抽时间写一篇文章,介绍如何生成一个随机的唯一解的数独问题.另外,为了做图形方便 ...

最新文章

  1. Python2爬虫学习系列教程
  2. 32M内存 跑linux内核,32位Linux单进程4G内存限制
  3. fwrite视频写入帧率测试(不用测了。。)
  4. 用devc++表白_教你用C语言加图形库打造炫酷表白连连看
  5. 给WIN7安装盘添加双PE3.0
  6. 关于JS中的定时器!!!
  7. 基于.NET平台常用的框架整理(收藏)
  8. Android之GridLayoutManager.setSpanSizeLookup问题
  9. SecurtCRT连接服务器自动断开
  10. 微软启动了自爆程序,让我们一起帮它倒计时
  11. 黑盒法测试c语言,黑盒测试的测试用例设计方法(经典理论知识,推荐)
  12. Metro UI 的设计感悟
  13. 世界首个!AI农作物病害检测竞赛火热进行中 | AI Challenger 全球AI挑战赛
  14. zabbix系列zabbix3.4监控mysql5.7
  15. python使用lxml库对解析后的DOM树形成的xpath计算得到平均值、中位数、方差
  16. cdrx7拼版工具在哪里_CorelDRAW X7标签怎么排版?
  17. SAR图像滤波去噪效果评价研究
  18. 微信公众号留言功能开通流程
  19. CE修改植物大战僵尸
  20. echarts绘制进度条

热门文章

  1. 有关苹果手机下载应用后提示不受信任的企业开发者解决方案:
  2. python项目打包部署到ios_Python实现iOS自动化打包详解
  3. 听听那冷雨 余光中
  4. 比尔沃服务器位置,防辐射植物哪个更好?
  5. OT网络安全-OT客户端安全防护要采取那些措施
  6. 汇编DOS与Windows Masm编译运行代码步骤详解
  7. Extraneous non-emits event listeners (xxxXxx) were passed to component but could not be automaticall
  8. Empty filename passed to function
  9. 十分钟快速入门 Pandas
  10. jsp如何跳转java_JSP的三种跳转方式