软件工程实践 2017 第二次作业(部分)
相关链接
作业题目地址:软件工程实践 2017 第二次作业
课程博客目录:软工课程作业博客目录-031502627
项目 GitHub 地址:egbertwang / Sudoku
估计耗时
项目 | more |
---|
题目概述
数独要点
每一行、每一列、每一个粗线宫(3*3)内的数字均含1-9,不重复。
工具清单
要求
输入
n //数独棋盘个数
输出
随机生成N个不重复的已解答完毕的数独棋盘,并输出到sudoku.txt中,并且左上角的第一个数为:(学号后两位相加)% 9 + 1。即 ( 2 + 7 ) % 9 + 1 = 1。
解题思路
认识数独
首先我之前从未做过数独题目,也不知道数独的规则。从题目描述中了解到了数独棋盘要满足的条件,要每一行、每一列、每一个九宫格都有 1-9 不重复的出现,而且左上角必须是 1(我自己的学号算出来的)。
资料搜索及算法选择过程
那么接下来就要找一些数独题目来做一下了。我找了一些在线生成数独题目的网站,发现自己并不能做出来,而且根据我的搜索,正常填空的方法并不适合完整的数独棋盘的生成,遂放弃。
在我 Google 的时候发现了一位博主用 Swing 和 Java 写的终盘生成法,分别使用矩阵转换法 [1] 和随机法 [2]。矩阵转换法就是将几个已生成的终盘数独棋盘作为模板,随机选取并进行随机的矩阵变换来生成新的数独棋盘。经计算一个终盘棋盘可经矩阵变换生成 1119744 个不重复且正确的棋盘,刚刚好满足题目 n < 1000000 的要求。当我们事先输入几个不同的棋盘,并且生成时随机选取并进行随机的一系列变换就可以生成足够多的棋盘。但是这个方法显然不够随机,我在进行了其他的尝试之后都不成功才会回来采用这个较为简单的方法。同时我也认识到自己程序设计的能力较差,还需要大量的练习,之后会抽时间做一些acm题目来训练自己。文中的随机生成法是随机生成一行来碰运气,我认为不妥,而且效率较为低下。我又考虑了将每个数依次填入九宫格的方法,比如说先将全部的 1 填入,再将全部的 2 填入,每次填入做一次行列的判断,若可行就填入,不可行就换个地方,直到无法放入,就全部清除,从头开始。再以此类推。我认为这个方法比较稳妥,如果生成的数量多的话可以考虑将已生成的棋盘做矩阵变换,同样是随机并且不重复的,而且更节省时间。当然还要写出来代码看看。
算法分析
首先,要有一个生成一系列随机坐标的函数,生成如 (x, y) 的坐标组。x 表示横坐标,y 表示纵坐标。
接着进行判断,当该坐标左边,上边都不重复的话就填入,否则将有冲突的坐标 + 1,依次循环,正常情况下总可以填入。当出现无法填入的情况时,若当前填入的数是 a,则将全部的 a、a - 1、a - 2 置 0,从 a - 2 开始填入,当重复次数到达规定阈值的时候还不能成功填入,就全部清楚,从头开始。此处为溯回的思想。此时不考虑矩阵变换,那么程序大概的流程如下:
当前填入数 a = 1;
检查当前九宫格位置(x,y),
若 x = y = 1 则填入最左上角一格,
否则生成随机坐标
检查随机坐标是否合法, // Judge()
若不合法则将对应坐标 + 1,
若两次内成功填入,进入下一个九宫格,重复,
若无法填入,则清除之前 a , a - 1, a - 2, 从这里重新填入,直到全部的数全部成功填入,
输出,初始化,进入下一次循环
代码实现
由于算法较为复杂,所以从里向外来实现算法。
首先,定义Sudoku[3][3]
,Pos_s[2]
,Pos_l[2]
分别为保存数独棋盘、随机坐标的数组 和 当前九宫格坐标。定义#define random(x) (rand() % x + 1)
来生成指定范围内的随机数。
接着,用Judge()
函数来判断是否可填入:
bool Judge() //判断是否可以填入 返回为 1 则可填入
{
int i, j;
bool flag = 1; // If Pos is avalible, set flag = 1, else set flag = 0
for(i = 0; i < Pos_s[0] + Pos_l[0] * 3; i++)
{
if(Sudoku[i][Pos_s[1] + Pos_l[1] * 3] == a)
{
flag = 0;
break;
}
}
if(flag == 1)
{
for(j = 0; j < Pos_s[1] + Pos_l[1] * 3; j++)
{
if(Sudoku[Pos_s[0] + Pos_l[0] * 3][j] == a)
{
flag = 0;
}
}
}
return flag;
}