【2019.09.19】数独(Sudoku)游戏之我见(软工实践第三次作业)
Github项目地址:https://github.com/MokouTyan/suduku_131700101
【2019.09.20】更新:代码经过Code Quality Analysis工具的分析并消除所有的警告。
【2019.09.21】更新:使用性能分析工具Studio Profiling Tools来找出代码中的性能瓶颈。
【2019.09.21】更新:想到了“即使题目是唯一解,但同时每个格子有多种可能性”的情况,新增函数check,以及格式化输出规范
【2019.09.22】更新:更新了Github上的代码,可以测试了
【2019.09.23】更新:新增全局变量change与chess_count,在循环中如果不发生改变即产生双解,并结束本表盘的执行
【2019.09.24】更新:实现对“-m”,“-n”,"-i"、"-o"的判断
【2019.09.25】更新:《构建之法》前三章读后感:https://www.cnblogs.com/mokou/p/11582103.html
尚未完成内容:
PSP表格
Personal Software Process Stages | 预估耗时 | 实际耗时 | |
---|---|---|---|
Planning | 计划 | 1小时 | 5分钟 |
Estimate | 这个任务需要多少时间 | 26小时 | 24小时 |
Development | 开发 | 5小时 | 4小时 |
Analysis | 需求分析 (包括学习新技术) | 2小时 | 1小时 |
Design Spec | 生成设计文档 | 2小时 | 1小时 |
Design Review | 设计复审 | 2小时 | 1小时 |
Coding Standard | 代码规范 | 30分钟 | 1小时 |
Design | 具体设计 | 30分钟 | 30分钟 |
Coding | 具体编码 | 5小时 | 2小时 |
Code Review | 代码复审 | 2小时 | 4小时 |
Test | 测试(自我测试,修改代码,提交修改) | 1小时 | 3小时 |
Reporting | 报告 | 1小时 | 2小时 |
Test Repor | 测试报告 | 1小时 | 30分钟 |
Postmortem | 事后总结, 并提出过程改进计划 | 1小时 | 2小时 |
Improvement Plan | 过程改进计划 | 1小时 | 2小时 |
合计 | 26小时 | 24小时 |
视频介绍所用思路
最近因为有自己的视频在做,所以有点忙不过来,这次的视频也是花了两小时赶出来的,以后如果太忙了可能不会做视频啦见谅(咕咕咕)
因为这个方法是自己想的,我没有依据可以证明“唯一的解必须由唯一的可能性推导而出”,所以我也不知道对不对
(我的舍友是认为,即使所有的格子有一个以上的可能性数量,也可以推出唯一的解来)
视频里是我的思路,如果看不清表盘填的数字可以在视频右下角切换清晰度
不论是inside函数还是outside函数,他们的执行顺序都是横纵宫
(其实这三个顺序无所谓的,但是三个都要去执行)
黄色是传入函数的位置,而绿色是所要检测的格子
源代码解释
全局变量介绍:
void check();
//记录棋盘上的标记点
bool sign[10][10][10];
//记录标记点的可能性剩余个数
int sign_count[10][10];
//记录棋盘
int checkerboard[10][10];
//记录类型
int type;
int small_sign[10];
//用于记录是否有变化
bool change;
int chess_count;
初始化表盘:
在CMD中输入type和棋盘个数后进入循环
在开始处理数据前先进行表盘重置化
每个位置的可能性初期都为9个(根据输入type大小而定)
表盘上所有数字为0(代表空)
sign第三个[]内的0号位都为False,其意义是还未填入数字
每个位置上的第一格到第九格可能性都存在(标为True)
void reset()
{
for (int i = 1; i < type + 1; i++)
{
for (int j = 1; j < type + 1; j++)
{
//假设每个位置都有type种可能
sign_count[i][j] = type;
//每个位置都是空
checkerboard[i][j] = 0;
//每个位置未曾填写
sign[i][j][0] = false;
//假设每个位置的type种可能都是可实现的
for (int k = 1; k < type + 1; k++)
{
sign[i][j][k] = true;
}
}
}
return;
}
inside函数解释:
我用的第一个函数,我称为inside函数
从头到尾遍历,向函数传递格子的位置
它会检测当前位置横、纵、九宫格有没有数字的格子
如果有数字存在并且该数字可能性还未去掉
便把该格子上的相同数字的可能性去掉,同时可能性数量-1
如果当可能性等于1时,立即写入数字
//查出所有空缺的可能性(位置上还没有数字)
//此时是扣除所在位置的可能性
int inside(int x, int y)
{
//排除横纵向可能性
int remove;
for (int i = 1; i < type + 1; i++)
{
//如果检测位置存在数
if (sign[x][i][0])
{
remove = checkerboard[x][i];
//则这个空位不能出现相同数字
//防止sign_count被误减去,前面先判断是不是已经变否了,未变否才变否
if (sign[x][y][remove])
{
sign[x][y][remove] = false;
//可能性-1
sign_count[x][y]--;
}
if (sign_count[x][y] == 1 && !sign[x][y][0])
{
write(x, y);
return 0;
}
}
}
for (int i = 1; i < type + 1; i++)
{
if (sign[i][y][0])
{
remove = checkerboard[i][y];
if (sign[x][y][remove])
{
sign[x][y][remove] = false;
sign_count[x][y]--;
}
if (sign_count[x][y] == 1 && !sign[x][y][0])
{
write(x, y);
return 0;
}
}
}
//宫格判断
if (type == 4 || type == 6 || type == 8 || type == 9)
{
int beginx, beginy;
int xplus, yplus;
switch (type)
{
case 4:
xplus = 2;
yplus = 2;
break;
case 6:
xplus = 2;
yplus = 3;
break;
case 8:
xplus = 4;
yplus = 2;
break;
case 9:
xplus = 3;
yplus = 3;
break;
}
beginx = ((x - 1) / xplus)*xplus + 1;
beginy = ((y - 1) / yplus)*yplus + 1;
for (int i = beginx; i < beginx + xplus; i++)
{
for (int j = beginy; j < beginy + yplus; j++)
{
if (sign[i][j][0])
{
if (sign[x][y][(checkerboard[i][j])])
{
sign[x][y][(checkerboard[i][j])] = false;
sign_count[x][y]--;
}
if (sign_count[x][y] == 1 && !sign[x][y][0])
{
write(x, y);
return 0;
}
}
}
}
}
//经过上面的判断,如果该位置只剩下一种可能性,那么执行write()
return 0;
}
write函数:
写入数字的时候会把位置上的标记改为存在数字(该位置sign第三个的[0]=True)
可能性数量变为0(该位置的sign_count=0;)
防止被二次修改
//填入确定值
int write(int x, int y)
{
//这个位置标记为存在数字
change = true;
chess_count--;
sign[x][y][0] = true;
sign_count[x][y] = 0;
//填入数字
for (int i = 1; i < type + 1; i++)
{
if (sign[x][y][i])
{
checkerboard[x][y] = i;
break;
}
}
outside(x, y);
return 0;
}
在写入数字的函数结束前
此时调用第二个函数,我称为outside函数
进行横纵宫的外部检查,将这个数字影响扩出去,当这个格子的“横纵宫”检查完成后等于说就成为一张新的表盘了
outside函数:
传入所写数字的位置
将它的横纵九宫格上所有格子上的相同数字的可能性去掉
当其他位置可能性数量为1的时候
再次立即调动write函数
//去除所填位置的横纵九宫格所有同数可能性(位置上刚填入数字)
//此时是扣除所填位置的横纵九宫格的其他位置可能性
int outside(int x, int y)
{
//remove是当前位置填入的数字
int remove = checkerboard[x][y];
for (int i = 1; i < type + 1; i++)
{
if (!sign[x][i][0] && sign[x][i][remove])
{
sign[x][i][remove] = false;
sign_count[x][i]--;
if (sign_count[x][i] == 1 && !sign[x][i][0])
{
write(x, i);
}
}
}
for (int i = 1; i < type + 1; i++)
{
if (!sign[i][y][0] && sign[i][y][remove])
{
sign[i][y][remove] = false;
sign_count[i][y]--;
if (sign_count[i][y] == 1 && !sign[i][y][0])
{
write(i, y);
}
}
}
//宫格判断
if (type == 4 || type == 6 || type == 8 || type == 9)
{
int beginx, beginy;
int xplus, yplus;
switch (type)
{
case 4:
xplus = 2;
yplus = 2;
break;
case 6:
xplus = 2;
yplus = 3;
break;
case 8:
xplus = 4;
yplus = 2;
break;
case 9:
xplus = 3;
yplus = 3;
break;
}
beginx = ((x - 1) / xplus)*xplus + 1;
beginy = ((y - 1) / yplus)*yplus + 1;
for (int i = beginx; i < beginx + xplus; i++)
{
for (int j = beginy; j < beginy + yplus; j++)
{
if (!sign[i][j][0] && sign[i][j][remove])
{
sign[i][j][remove] = false;
sign_count[i][j]--;
if (sign_count[i][j] == 1 && !sign[i][j][0])
{
write(i, j);
}
}
}
}
}
return 0;
}
经常会出现这样的情况,(write)填入第一个数字→(第一个outside)检查第一个数字的横向,刚好找到可能性数量为1的存在→(write)填入第二个数字→(第二个outside)检查第二个数字的横纵宫,减去外部的可能性,没有出现可能性数量刚好为1的点(此时return回调用自己write,再return回上一个outside)→(第一个outside)重新返回第一个数字的函数内,继续检查完第一个数字的横纵宫
(也正是因为这样所以write里面要对填入的格子进行锁死,防止第一个outside会遍历 第二个及其以后的outside 会再次填入)
write和outside就这样子互相嵌套调用,每当outside函数彻底完成后(即直到当前的outside是由 inside调用的write所调用的)是相当于生成一个新的表盘
check函数:
想到一种特殊情况
假如在同一排内有三个空格,他们的可能性分别为“12”,“23”,“12”
尽管它们都有一个以上的选项,但是中间这一格必须填3,因为它只在这一排出现过一次
所以要检查同一排内是否有只有一个可能性种类的情况出现
void check()
{
//检查每一横
for (int i = 1; i < type + 1; i++)
{
for (int j = 1; j < type + 1; j++)
{
small_sign[j] = 0;
}
for (int j = 1; j < type + 1; j++)
{
if (!sign[i][j][0])
{
for (int k = 1; k < type + 1; k++)
{
if (sign[i][j][k])
{
small_sign[k]++;
}
}
}
}
for (int k = 1; k < type + 1; k++)
{
if (small_sign[k] == 1)
{
for (int j = 1; j < type + 1; j++)
{
if (sign[i][j][k] && !sign[i][j][0])
{
//这个位置标记为存在数字
chess_count--;
change = true;
sign[i][j][0] = true;
sign_count[i][j] = 0;
checkerboard[i][j] = k;
outside(i, j);
}
}
}
}
}
//检查每一纵
for (int j = 1; j < type + 1; j++)
{
for (int i = 1; i < type + 1; i++)
{
small_sign[i] = 0;
}
for (int i = 1; i < type + 1; i++)
{
if (!sign[i][j][0])
{
for (int k = 1; k < type + 1; k++)
{
if (sign[i][j][k])
{
small_sign[k]++;
}
}
}
}
for (int k = 1; k < type + 1; k++)
{
if (small_sign[k] == 1)
{
for (int i = 1; i < type + 1; i++)
{
if (sign[i][j][k] && !sign[i][j][0])
{
//这个位置标记为存在数字
chess_count--;
change = true;
sign[i][j][0] = true;
sign_count[i][j] = 0;
checkerboard[i][j] = k;
outside(i, j);
}
}
}
}
}
}
主函数:
int main(int argc, char *argv[])
{
int n;
FILE* fp1;
FILE* fp2;
type = atoi(argv[2]);
n = atoi(argv[4]);
char* InputName = argv[6];
char* OutputName = argv[8];
//以只读方式打开文件
fp1 = fopen(InputName, "r");
if (fp1 == NULL) //
return -1;
//fscanf(fp1, "%d%d", &type,&n);
//打开output.txt,并立即关闭,意义为清空文本内容
fp2 = fopen(OutputName, "w");
if (fp2 == NULL) //
return -1;
fclose(fp2);
while (n > 0)
{
//重置棋盘
reset();
//输入棋盘
for (int i = 1; i < type + 1; i++)
{
for (int j = 1; j < type + 1; j++)
{
//cin >> checkerboard[i][j];
fscanf(fp1, "%d", &checkerboard[i][j]);
if (checkerboard[i][j] != 0)
{
sign[i][j][0] = true;
sign_count[i][j] = 0;
chess_count--;
}
}
}
//棋盘上以填格子的数量,当它等于零的时候棋盘被填满
chess_count = type * type;
change = true;
while (chess_count != 0 && change)
{
//先默认棋盘不发生变化
change = false;
//找出空缺位置
for (int k = 0; k < 2; k++)
{
for (int i = 1; i < type + 1; i++)
{
for (int j = 1; j < type + 1; j++)
{
if (!sign[i][j][0])
{
inside(i, j);
}
}
}
}
check();
}
//以只写方式打开文件
fp2 = fopen(OutputName, "a");
if (fp2 == NULL)
return -1;
bool sign_complete = true;
if (chess_count != 0) sign_complete = false;
for (int i = 1; i < type + 1; i++)
{
for (int j = 1; j < type + 1; j++)
{
//cout << checkerboard[i][j];
fprintf(fp2, "%d", checkerboard[i][j]);
if (j != type)
{
fprintf(fp2, " ");
//cout << ' ';
}
}
if (n != 1 && i == type && sign_complete)
{
//cout << "\n\n";
fprintf(fp2, "\n\n");
}
else if (n != 1 && i == type && !sign_complete)
{
//cout << "\n无法再确定地填入任何一格\n因此棋盘中有空位\n\n";
fprintf(fp2, "\n无法再确定地填入任何一格\n因此棋盘中有空位\n\n");
}
else if (n == 1 && i == type && sign_complete) {}
else if (n == 1 && i == type && !sign_complete)
{
//cout << "\n无法再确定地填入任何一格\n因此棋盘中有空位";
fprintf(fp2, "\n无法再确定地填入任何一格\n因此棋盘中有空位");
}
else
{
//cout << "\n";
fprintf(fp2, "\n");
}
}
//cout << '\n';//
//fprintf(fp2, "\n");
n--;
fclose(fp2);
}
fclose(fp1);
}
代码调试阶段
Code Quality Analysis检查结果:
测试样例结果:
九宫格展示:
其他类型宫格展示:
性能分析工具Studio Profiling Tools分析结果
分别是上面的四个四宫格,三个六宫格,两个九宫格题目的分析结果
总结
这个解决方案只能解决唯一解的数独问题
面对多个解的数独棋盘,这个方法可能解不完整,会有空缺位置
因为都是自己的思考,在完成这道题的时候完全按照自己思路,所以并不一定正确
完成后去查看过别人的代码,他们的程序也都很有道理,我还是要学习一个~
【2019.09.19】数独(Sudoku)游戏之我见(软工实践第三次作业)的更多相关文章
- 2019软工实践_Alpha(3/6)
队名:955 组长博客:https://www.cnblogs.com/cclong/p/11872693.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...
- 2019软工实践_Alpha(2/6)
队名:955 组长博客:https://www.cnblogs.com/cclong/p/11862633.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...
- 2019软工实践_Alpha(5/6)
队名:955 组长博客:https://www.cnblogs.com/cclong/p/11898112.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...
- 软工实践第二次作业-sudoku
说明 Github项目地址 作业题目 解题思路 一开始拿到的时候,有一个思路,可以先填写全盘的"1",然后在插空填满全盘的"2".后来觉得自己理不清那个思路.遂 ...
- 软工实践作业2:个人项目实战之Sudoku
Github:Sudoku 项目相关要求 项目需求 利用程序随机构造出N个已解答的数独棋盘 . 输入 数独棋盘题目个数N(0<N<=1000000). 输出 随机生成N个不重复的已解答完毕 ...
- 2019软工实践_Alpha(事后诸葛亮)
组长博客 感谢组长 总结思考 设想和目标 我们的软件要解决什么问题?是否定义得很清楚?是否对典型用户和典型场景有清晰的描述? 弥补Powerpoint中模板转换存在的缺陷,完善PPT模板一键转换的功能 ...
- 2019软工实践_Alpha(6/6)
队名:955 组长博客:https://www.cnblogs.com/cclong/p/11913269.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...
- 2019软工实践_Alpha(4/6)
队名:955 组长博客:https://www.cnblogs.com/cclong/p/11882079.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...
- 2019软工实践_Alpha(1/6)
队名:955 组长博客:https://www.cnblogs.com/cclong/p/11841141.html 作业博客:https://edu.cnblogs.com/campus/fzu/S ...
随机推荐
- 仿EXCEL插件,智表ZCELL产品V1.7 版本发布,增加自定义右键菜单功能
详细请移步 智表(ZCELL)官网www.zcell.net 更新说明 这次更新主要应用户要求,主要解决了自定义右键菜单事件的支持,并新增了公式中自定义函数传参.快捷键剪切等功能,欢迎大家体验使用. ...
- 【Python】模块和包
模块 模块的概念 1. 每一个以扩展名 `py` 结尾的 `Python` 源代码文件都是一个 模块 2. 模块名 同样也是一个 标识符,需要符合标识符的命名规则 3. 在模块中定义的 全局变量 .函 ...
- docker中基于centos镜像部署lnmp环境 php7.3 mysql8.0 最新版
Docker是一个开源的应用容器引擎,基于Go语言并遵从Apache2.0协议开源. Docker可以让开发者打包他们的应用以及依赖包到一个轻量级.可移植的容器中,然后发布到任何流行的Linux机器上 ...
- linux档案和目录的管理
资料来自鸟哥的linux私房菜,记录下来供自己平常使用参考 一:目录和路径: cd:change direcoty,变换目录的意思,就是从一个目录变到另一个目录,然后可以用绝对路径去变换目录,也可以用 ...
- 动态管理upsteam---nginx_http_dyups_module
nginx_http_dyups_module nginx_http_dyups_module是第三方开源软件,它提供API动态修改upstream的配置,并且支持Nginx的ip_hash.kee ...
- 【MySQL】测试MySQL表中安全删除重复数据只保留一条的相关方法
第二篇文章测试说明 开发测试中,难免会存在一些重复行数据,因此常常会造成一些测试异常. 下面简单测试mysql表删除重复数据行的相关操作. 主要通过一下三个大标题来测试说明: 02.尝试删除dept_ ...
- P4560 [IOI2014]Wall 砖墙
题目描述 给定一个长度为 nn且初始值全为 00的序列.你需要支持以下两种操作: Add L, R, hL,R,h:将序列 [L, R][L,R]内所有值小于 hh的元素都赋为 hh,此时不改变高度大 ...
- python_常用断言assert
python自动化测试中寻找元素并进行操作,如果在元素好找的情况下,相信大家都可以较熟练地编写用例脚本了,但光进行操作可能还不够,有时候也需要对预期结果进行判断. 常用 这里介绍几个常用断言的使用方法 ...
- kvm创建windows2008虚拟机
virt-install -n win2008-fushi001 -r 16384 --vcpus=4 --os-type=windows --accelerate -c /data/kvm/imag ...
- 小米BL不解锁刷机
关于小米NOTE顶配近期解锁的问题中发现还有很多人不会用9008模式刷机,现出个简单教程方便米粉们救砖.硬件:小米NOTE顶配手机 win10系统的电脑 手机与电脑相连的数据线软件:老版本的mifla ...