零、今天是10月5号,我写出了数独游戏的解啦!

一、题目描述(伪)

输入一个未解的数独盘,输出它的结果。

Input

共9行,每行9个数字(数字之间没有空格)

备注:如果这一格已经填有数字,相应的输入就是这个数字;如果这一格没有填数字,相应的输入为0。

Output

如果有解,输出共11行,参照示例。

如果无解,输出“您的输入似乎有误!检查一下吧!”。

示例:对于下面的例子有相应的输入

Input:

860300000
002084010
150000000
008200700
000809004
009050000
200000008
600008923
080020007

Output:


二、事件起源

暑假以来,我一直在玩数独游戏。数独游戏太好玩了,但是一天一道新题,有时候我也会想偷懒,或者懒得算啥的。这才产生了自己写一个程序帮我解数独的想法。

好了,事情就是这么个事,那么我们到底应该怎么写嘞?


三、思路分析

10月4日下午的马原课上,博主没带电脑,只好用纸和笔在脑中构建基本的框架,那天的笔记还在:

就让我本人给你们总结一下上面写的思路:

1、读入九行数据。

2、对每一个格子,如果是已解状态,则放之不管;如果是待解状态,则生成它的预选数串

3、若预选数串中只有一个数字,说明这一格只有这种填法,直接填入即可,并且将该格子标记为已解状态。

4、循环第2步和第3步数次。

5、然后再考虑用BFS求解。

Q:什么是预选数串?

A:这个是我自己起的名称,是格子里可能填的数字所组成的数串。例如上面的每日挑战的那张截屏中,第一行第三列的格子里可能填的数字是4和7,那么它的预选数串就是47,明白了吧!

Q:为什么不直接BFS,而是先执行第2步和第3步数次?

A:主要是为了减少BFS的深度,加快速度。

不难看出,这个程序如果写出来,核心就在BFS。只要会BFS,程序真的不难写出。


四、代码实现

从昨晚11点动工,写到12点算是一个小时;上午满课,中午回来十二点半开始写,写到13:45写出来。这样看来,总共只花费了不到三个小时。这么一丁点时间支出却换来我内心巨大愉悦,我觉得很值。

1.1 结构体——单个小格子

typedef struct {         //单个小格子 int id;                 //id=1表示已填,id=2表示未填char Filled;            //Filled表示填入的数字 string Preselection;    //Preselection表示预选数列
} Single;说明:我原本是写成下面的模式
typedef struct {                //单个小格子 int id;                     //id=1表示已填,id=2表示未填union {char Filled;         //Filled表示填入的数字 string Preselection;    //Preselection表示预选数列  } info;
} Single;
因为union这块我确实不太熟悉,最后放弃,改成了别的模式。当然,union肯定更美观且节省空间。

1.2 结构体——整片数独盘

typedef struct {Single lattice[81];      //第i行第j列的格子位置为:k=9*i+jint cnt=0;              //cnt表示未填个数,当cnt=0时,表示已经全部填完了
} Sudoku;

2.1 函数——获取预选数串

//函数Get_Preselected_Sequence的作用是得到[i,j]位置的预选数串
void Get_Preselected_Sequence(Sudoku *S, int i, int j) {int number[9]={0,0,0,0,0,0,0,0,0};//检测第i行的数字 for (int m=0; m<9; m++) {if ( (*S).lattice[9*i+m].id==1 ) number[ (*S).lattice[9*i+m].Filled-'1' ] = 1;}//检测第j列的数字for (int m=0; m<9; m++) {if ( (*S).lattice[9*m+j].id==1 ) number[ (*S).lattice[9*m+j].Filled-'1' ] = 1;}//检测所在中格的数字 //先找一下这个小格子所在的中格子int i_medium_start, j_medium_start;i_medium_start = i%3==0 ? i : ( i%3==1 ? i-1 : i-2 );j_medium_start = j%3==0 ? j : ( j%3==1 ? j-1 : j-2 );//再检测for (int m=0; m<3; m++) {for (int n=0; n<3; n++) {if ( (*S).lattice[9*(i_medium_start+m)+(j_medium_start+n)].id==1 ) number[ (*S).lattice[9*(i_medium_start+m)+(j_medium_start+n)].Filled-'1' ] = 1;}}//生成预选数串:如果这个数字没出现过,就将其加入预选数串(*S).lattice[9*i+j].Preselection.erase();for (int m=0; m<9; m++) {if ( number[m]==0 ) {(*S).lattice[9*i+j].Preselection += (m+'1');}}
}

2.2 函数——生成 已经生成所有格子的预选数串 的数独盘

void Prepare(Sudoku *S) {void Get_Preselected_Sequence(Sudoku *, int, int);//每次S更新后,都可能有数字填入。而一旦有数字填入,意味着数独盘其他地方的预选数串可能发生改变,就要再次更新。 Sudoku RECORD;while (RECORD.cnt!=(*S).cnt) {RECORD = (*S);for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {if ( (*S).lattice[9*m+n].id==2 ) {Get_Preselected_Sequence(S, m, n);//如果预选数串中可选数量为0,说明这种填法有误,并将cnt标记为-1if ( (*S).lattice[9*m+n].Preselection.length()==0 ) {(*S).cnt = -1; return;}//如果预选数串中可选数量为1,说明只有这个数字能填,直接填进去即可if ( (*S).lattice[9*m+n].Preselection.length()==1 ) {(*S).lattice[9*m+n].id = 1;(*S).lattice[9*m+n].Filled = (*S).lattice[9*m+n].Preselection[0];(*S).cnt--;}}            }}}
}

2.3 函数——输出数独盘

//函数Print的作用的输出数独块S
void Print(Sudoku S) {for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {if (S.lattice[9*m+n].id==1) printf("%c ",S.lattice[9*m+n].Filled);if (n%3==2 && n!=8) printf("| ");}cout << endl;if (m%3==2 && m!=8) printf("---------------------\n");}
}

2.4 函数——按行优先的方式找到数独盘中第一个未填格子

int FindFirst(Sudoku S) {if (S.cnt>0)for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {if (S.lattice[9*m+n].id==2) {return (9*m+n);}}}
}

主程序中——

1、读入数独SDK,SDK是Sudoku类型。

for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {scanf("%c",&SDK.lattice[9*m+n].Filled);//如果读入的是0,id=2if (SDK.lattice[9*m+n].Filled=='0') {SDK.lattice[9*m+n].id=2;SDK.lattice[9*m+n].Preselection += SDK.lattice[9*m+n].Filled;SDK.cnt+=1;}//如果读入的不是0,id=1else {SDK.lattice[9*m+n].id=1;}}scanf("%*c");     //读回车
}

2、BFS写法

queue<Sudoku> QS;
while ( QS.size() ) {//取顶Sudoku SDK_dynamic = QS.front();//弹顶QS.pop();//找第一个未填的格int f; f=FindFirst(SDK_dynamic);int number_of_numbers = SDK_dynamic.lattice[f].Preselection.length();//依次在这个未填的格子中填入所有可能的数字情况,然后加入队列中for (int m=0; m<number_of_numbers; m++) {Sudoku SDK_temporary;SDK_temporary = SDK_dynamic;//深度+1,id=1,Filled填入,cnt-1,这都不用解释吧SDK_temporary.depth += 1;SDK_temporary.lattice[f].id = 1;SDK_temporary.lattice[f].Filled = SDK_temporary.lattice[f].Preselection[m];SDK_temporary.cnt--;//因为加入了新的数字,所以每次加入队列之前要先更新预选数串Prepare(&SDK_temporary); Prepare(&SDK_temporary); Prepare(&SDK_temporary);//更新后还要检查,如果是-1,表示无解,不能入列;如果是0,成功解出,直接输出;如果大于0,说明还没完全解完,入列。if (SDK_temporary.cnt!=-1)if (SDK_temporary.cnt==0) {Print(SDK_temporary);return 0;}else QS.push(SDK_temporary);}
}

五、完整代码

害,思路就这么个思路,情况就这么个情况,也并不难,洒洒水咯~

#include<bits/stdc++.h>
using namespace std;
typedef struct {            //单个小格子 int id;                 //id=1表示已填,id=2表示未填char Filled;            //Filled表示填入的数字 string Preselection;    //Preselection表示预选数列
} Single;typedef struct {Single lattice[81];        //第i行第j列的格子位置为:k=9*i+jint cnt=0;              //cnt表示未填个数,当cnt=0时,表示已经全部填完了
} Sudoku;
Sudoku SDK;//函数Get_Preselected_Sequence的作用是得到[i,j]位置的预选数串
void Get_Preselected_Sequence(Sudoku *S, int i, int j) {int number[9]={0,0,0,0,0,0,0,0,0};//检测第i行的数字 for (int m=0; m<9; m++) {if ( (*S).lattice[9*i+m].id==1 ) number[ (*S).lattice[9*i+m].Filled-'1' ] = 1;}//检测第j列的数字for (int m=0; m<9; m++) {if ( (*S).lattice[9*m+j].id==1 ) number[ (*S).lattice[9*m+j].Filled-'1' ] = 1;}//检测所在中格的数字 //先找一下这个小格子所在的中格子int i_medium_start, j_medium_start;i_medium_start = i%3==0 ? i : ( i%3==1 ? i-1 : i-2 );j_medium_start = j%3==0 ? j : ( j%3==1 ? j-1 : j-2 );//再检测for (int m=0; m<3; m++) {for (int n=0; n<3; n++) {if ( (*S).lattice[9*(i_medium_start+m)+(j_medium_start+n)].id==1 ) number[ (*S).lattice[9*(i_medium_start+m)+(j_medium_start+n)].Filled-'1' ] = 1;}}//生成预选数串:如果这个数字没出现过,就将其加入预选数串(*S).lattice[9*i+j].Preselection.erase();for (int m=0; m<9; m++) {if ( number[m]==0 ) {(*S).lattice[9*i+j].Preselection += (m+'1');}}
}//函数Prepare的作用是生成数独盘
//当预选数串中可选数量为1时,直接将其填入格子。
//如果预选数串中可选数量为0,说明这种填法有误。令cnt=-1,表示有误。
void Prepare(Sudoku *S) {void Get_Preselected_Sequence(Sudoku *, int, int);//每次S更新后,都可能有数字填入。而一旦有数字填入,意味着数独盘其他地方的预选数串可能发生改变,就要再次更新。 Sudoku RECORD;while (RECORD.cnt!=(*S).cnt) {RECORD = (*S);for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {if ( (*S).lattice[9*m+n].id==2 ) {Get_Preselected_Sequence(S, m, n);//如果预选数串中可选数量为0,说明这种填法有误,并将cnt标记为-1if ( (*S).lattice[9*m+n].Preselection.length()==0 ) {(*S).cnt = -1; return;}//如果预选数串中可选数量为1,说明只有这个数字能填,直接填进去即可if ( (*S).lattice[9*m+n].Preselection.length()==1 ) {(*S).lattice[9*m+n].id = 1;(*S).lattice[9*m+n].Filled = (*S).lattice[9*m+n].Preselection[0];(*S).cnt--;}}           }}}
}//函数Print的作用的输出数独块S
void Print(Sudoku S) {for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {if (S.lattice[9*m+n].id==1) printf("%c ",S.lattice[9*m+n].Filled);if (n%3==2 && n!=8) printf("| ");}cout << endl;if (m%3==2 && m!=8) printf("---------------------\n");}
}int FindFirst(Sudoku S) {if (S.cnt>0)for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {if (S.lattice[9*m+n].id==2) {return (9*m+n);}}}
}int main(){freopen("input.txt","r",stdin);void Prepare(Sudoku *S);void Print(Sudoku);int FindFirst(Sudoku);//读入待解数独S。//说明:读入0,表示这个空格是待填的。for (int m=0; m<9; m++) {for (int n=0; n<9; n++) {scanf("%c",&SDK.lattice[9*m+n].Filled);if (SDK.lattice[9*m+n].Filled=='0') {SDK.lattice[9*m+n].id=2;SDK.lattice[9*m+n].Preselection += SDK.lattice[9*m+n].Filled;SDK.cnt+=1;}else {SDK.lattice[9*m+n].id=1;}}scanf("%*c");}Prepare(&SDK);//简单的数独已经解完了 if (SDK.cnt==0) { Print(SDK); return 0; }//接下来需要往更难的方向走,考虑使用BFS queue<Sudoku> QS;{int f; f=FindFirst(SDK);int number_of_numbers = SDK.lattice[f].Preselection.length();for (int m=0; m<number_of_numbers; m++) {Sudoku SDK_temporary;SDK_temporary = SDK;SDK_temporary.lattice[f].id = 1;SDK_temporary.lattice[f].Filled = SDK_temporary.lattice[f].Preselection[m];SDK_temporary.cnt--;Prepare(&SDK_temporary);if (SDK_temporary.cnt!=-1)if (SDK_temporary.cnt==0) {Print(SDK_temporary);return 0;}else QS.push(SDK_temporary);}}while ( QS.size() ) {Sudoku SDK_dynamic = QS.front();QS.pop();int f; f=FindFirst(SDK_dynamic);int number_of_numbers = SDK_dynamic.lattice[f].Preselection.length();for (int m=0; m<number_of_numbers; m++) {Sudoku SDK_temporary;SDK_temporary = SDK_dynamic;SDK_temporary.lattice[f].id = 1;SDK_temporary.lattice[f].Filled = SDK_temporary.lattice[f].Preselection[m];SDK_temporary.cnt--;Prepare(&SDK_temporary);if (SDK_temporary.cnt!=-1)if (SDK_temporary.cnt==0) {Print(SDK_temporary);return 0;}else QS.push(SDK_temporary);}}if ( !QS.size() ) cout << "您的输入似乎有误!检查一下吧!\n";return 0;
}

六、一些后话

首先是准确率,我测试了很多大师难度的题,正确率100%,基本可以放心食用!

第二是速度,每个程序的平均运行时间都在一秒以内,不用担心BFS超时!

第三是使用体验,输入上没有乱七八糟的符号,只用数字输入即可;而输出形式美观,自动分成了九块。输入输出都十分友好!

用电脑帮我算以后我的解题速度也显著提升啦!毕竟电脑算的快嘛!

如果有需要的同学可以直接把代码搬走,没有关系的。但是,能不能留下一个小小的点赞甚至是关注呢(可怜状)自己的构思和自己的设计是真的真的很希望得到读者的认可!!!!

好了,以上就是今天分享的内容,同学们也可以把程序设计用在自己的日常生活中呀~!

数独游戏 | c++ | BFS相关推荐

  1. ACM之八数码问题----BFS搜索----数独游戏的模拟(下)

    题目描述;数独游戏的内核代码 八数码问题; 编号为1到8的8个正方形滑块被摆成3行3列;(有一个格子留空); 每次可以把与空格相邻的滑块(有公共边才算相邻)移到空格中; 而它原来的位置就成为了新的空格 ...

  2. 【第二十三题】带旋转的数独游戏|dfs(北理工/北京理工大学/程序设计方法与实践/小学期 )

    目录 ​ 前言 dfs学习 思路 代码修正 源代码 我修正后的代码 前言 说实话这题我弄的也不是很明白,而csdn上只有一份代码@loveumozart,还没多少注释,关键还有很多冗余代码,于是我就做 ...

  3. 用 JS 做一个数独游戏(二)

    用 JS 做一个数独游戏(二) 在 上一篇博客 中,我们通过 Node 运行了我们的 JavaScript 代码,在控制台中打印出来生成好的数独终盘.为了让我们的数独游戏能有良好的体验,这篇博客将会为 ...

  4. Leetcode0037--Sudoku Solver 数独游戏

    [转载请注明]http://www.cnblogs.com/igoslly/p/8719622.html 来看一下题目: Write a program to solve a Sudoku puzzl ...

  5. python数独游戏源代码100行_python实现解数独程序代码

    偶然发现linux系统附带的一个数独游戏,打开玩了几把.无奈是个数独菜鸟,以前没玩过,根本就走不出几步就一团浆糊了. 于是就打算借助计算机的强大运算力来暴力解数独,还是很有乐趣的. 下面就记录一下我写 ...

  6. 注入游戏没有焦点_数独游戏 数学之美(三)

    一揭晓上期答案同学们,上周的数独题目你们做对了吗?让我们一起来看看正确答案吧! 二学生讲解数独游戏是一种非常益智的推理游戏,它可以提高你的逻辑思维能力.让我们来听一听这位同学的解题思路吧!三讲解新知这 ...

  7. python数独游戏源代码_使用Python编写数独游戏自动出题程序

    数独是一个很好玩的游戏,可以锻炼推理能力.下面的代码可以自动生成数独游戏题目. from random import shuffle, randrange def generate(): # 初始网格 ...

  8. 2014/School_C_C++_A/6/“数独”游戏

    你一定听说过"数独"游戏. 如[图1.png],玩家需要根据9×9盘面上的已知数字,推理出所有剩余空格的数字,并满足每一行.每一列.每一个同色九宫内的数字均含1-9,不重复. 数独 ...

  9. 【dfs】数独游戏(ybtoj dfs-1-2)

    数独游戏 ybtoj dfs-1-3 题目大意 给出一个未完成的数独,让你完成它 输入样例 4.....8.5.3..........7......2.....6.....8.4......1.... ...

  10. c语言编程数独游戏,编程做数独游戏

    数独游戏非常好玩,可以训练玩家的逻辑推理能力.数独游戏的规则是: 1.在9×9的大九宫格内,已给定若干数字,其他宫位留白,玩家需要自己按照逻辑推敲出剩下的空格里是什么数字. 2.必须满足的条件:每一行 ...

最新文章

  1. JavaScript异步流程控制的前世今生
  2. 【高并发】高并发环境下诡异的加锁问题(你加的锁未必安全)
  3. win7为什么打开桌面上的计算机很卡很慢,windows7很卡怎么处理_windows7系统卡慢的解决方法...
  4. 汽车行业最大创新仍未到来,四大力量将重塑未来汽车新纪元
  5. 静态移值编译的关键环境变量
  6. (转载)jsp与servlet之间页面跳转及参数传递实例
  7. OpenCV不规则ROI提取
  8. android 卡片3d效果_小米发布伸缩镜头技术:卡片机失业,多摄下岗?
  9. Ansible常用模块详解
  10. 如何在 C# 平台调用云开发?
  11. Linux基线合规检查中各文件的作用及配置脚本
  12. 小黑框如何连接mysql_珍藏版(cmd小黑框)数据库命令及操作
  13. greenplum配置高可用_高可用hadoop集群配置就收藏这一篇,动手搭建Hadoop(5)
  14. 第一阶段意见汇总以及改进
  15. 两篇介绍IIS的文章
  16. 英特尔无线蓝牙启动服务器,如何在英特尔Edison上部署蓝牙安全网关
  17. 数据在网络中如何传输的
  18. 辐射4核能选项用计算机失败,玩游戏出问题了?《辐射4》PC版常见问题汇总和解决方案...
  19. Vue:使用elementUI upload组件上传excel文件
  20. 安卓电子市场_五款安卓工具神器

热门文章

  1. 毕加索传记的艺术和历史
  2. 隐藏桌面上计算机图标不见了怎么办,电脑的快捷图标不见了怎么办
  3. 木瓜移动每日出海快讯0428:谷歌发布Q1财报
  4. 为你的兔小巢加上实时消息推送
  5. iMeta | 深圳先进院戴磊组开发可同时提取共存菌株的组成和基因成分谱的菌株分析工具...
  6. android 热补丁工具,Hotfix补丁工具报错排查步骤
  7. 微软提供的无限次延长Vista激活
  8. (待填坑)【数据结构】笛卡尔树
  9. CentOS 8 添加中文语言包
  10. python 今日头条视频自动上传_抖音视频怎么上传到今日头条?这个软件可一键操作很方便...