数独终局的生成和求解
一、项目的GitHub地址
二、PSP表格中的预估时间
三、题目要求
四、题目流程图
五、解题思路描述
1、数独游戏规则
2、数独终局的生成
3、求解数独模块
六、代码设计及流程
1、需求分析
2、生成数独终局模块设计实现过程
3、求解数独残局模块
七、性能分析
八、代码主要部分说明
九、单元测试
十、PSP表格与预期时间对比
十一、个人总结
一、项目的GitHub地址
https://github.com/Bigwaterb/suduku-hrz
二、PSP表格中的预估时间
PSP2.1 | Personal SoftWare Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planing | 计划 | 60 | |
Estimate | 估计任务耗时 | 3000 | |
Development | 开发 | 1200 | |
Analysis | 需求分析 | 120 | |
Design Spec | 生成设计文档 | 60 | |
Design Review | 设计复审 | 60 | |
Coding Standard | 代码规范 | 30 | |
Design | 具体设计 | 180 | |
Coding | 具体编码 | 900 | |
Code Review | 代码复审 | 60 | |
Test | 测试 | 240 | |
Reporting | 报告 | 30 | |
Test Report | 测试报告 | 30 | |
Size Measurement | 计算工作量 | 30 | |
Postmortem | 事后总结合计 | 3000 |
三、题目要求:
四、题目流程图
五、解题思路描述
1、数独游戏规则
数独起源于18世纪初瑞士数学家欧拉等人研究的拉丁方阵(Latin Square)。拉丁方块的规则:每一行(Row)、每一列(Column)均含1-N(N即盘面的规格),不重复。这与前面提到的标准数独非常相似,但少了一个宫的规则。
数独 (日语:数独/すうどく sudoku)是一种逻辑性的数字填充游戏。数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次。每一个粗线宫内的数字均含1-9,不重复。
本次作业的数独问题主要分为生成数独终局和求解数独两大部分。
2、生成数独终局模块解题思路
虽然说对于数独游戏,是把9*9个数字按照行列不同的规则放入进去,表面是毫无次序,然而实际上有规律可循,即找到数独内在的规律可以大大减少生成数独终局的时间复杂度。
生成数独矩阵时,本题唯一的一个约束条件就是左上角第一个数为学号后两位相加%9+1,(我的学号1120171393,即9+3=12,12%9+1=4),这是一个唯一固定不动的点,我们要得到1000000种情况。例如,我们第一行是4,5,6,7,8,9,1,2,3。我门可以得到一种解决方案,即从第二行开始,每一行是第一行右移,3,6,1,4,7,2,5,8列的结果。按照同样的规律,可以获得8!即40320种不同的终局。同时,如果任意交换2-3行、4-6行或7-9行三行的位置,可以使每一种原先的终局扩展为72个终局,得到最多2903040种不同的终局情况,即可以满足题目中1000000种情况的要求。
通过如此分析后,我们可以设计生成数独终局的办法了。
3、求解数独残局模块解题思路
这一模块我们只需对一个合法的残局进行求解,给出一个解决方案即可,看到这个题目后,跑到我脑海里的第一个想法就是回溯法进行求解。运用回溯法,在一个给定的残局中,我们可以根据空结点的排列按自左向右、自上而下的顺序遍历寻找数独的解。如果当前结点可以放下一个数字,然后从该列该行中1-9剩下的数字存放在里面,我们继续往下进行到空格处,继续同样的操作,直到不满足要求,即不能再放任何一个数字,说明之前我们存放有误,此时我们回溯至上一步,继续寻找可行的结果,直到最后找到正确的结果。
但是过程显然比较复杂,浪费的空间也大,所以我们优化的过程中是根据如何加大它的回溯效率来节省时间和空间。假如我们进入到一个新的结点,如果一个空位周围(所在行、所在列、所在小九宫格)的已知数字越多,那么该空位能填的有效数字应该相对就越少。因此只要在扫描的过程中记录下每行、每列、每个小九宫格中已知的数字的数量,便可作为之后对空位排序的依据。
六、代码设计及流程。
1、需求分析:
2、生成数独终局模块设计实现过程
首先要判断输入生成命令格式是否符合,不符合的话会在文档提示格式错误,正确的话,会在txt文档中生成对应数量的解。
3、求解数独残局模块
递归求解数独残局函数:按上述回溯剪枝的递归求解数独残局算法,在执行该模块前,程序因通过对合法残局的扫描已经得到各空位组成的数组,各行各列各小九宫格的数据信息。因此,在该函数模块中,按照空位数组(预处理后)顺序进行深搜(DFS)
七、代码性能分析
这部分主要以生成终端部分说明
(1)sudoku.exe -c 1000
(2)sudoku.exe -c 1000000
优化策略:通过上述性能分析图,代码运行大部分时间都用在输入输出等待部分。因此对函数模块的输入输出部分进行部分优化,因为系统的读写速度相对于cpu的运算速度要慢许多,因此为了减少时间,我们需要减少读写次数,输入输出时将数字转化为字符串来提高生成终局效率。
八、代码主要部分说明
(1)CheckOrder函数,通过命令,生成的个数,还有类型来判断是否是正确的输入格式
(2) 数独终局生成模块函数
这一部分函数,主要是首先生成第一种解,通过数独游戏的规律来进行平移,最后通过交换位置得到不同的解,核心代码如下:
(3) 数独残局求解模块
此模块主要通过一个DFS递归和回溯法进行残局的求解,主要代码如下:
九、单元测试
对于CheckOrder()模块进行10个测试用例
输入和预期结果对比
当我们输入-c eee 时,
得到如下结果
当我们输入 -c 20 得到:
十、PSP表格与预期时间对比
PSP2.1 | Personal SoftWare Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planing | 计划 | 60 | 120 |
Estimate | 估计任务耗时 | 3000 | 3780 |
Development | 开发 | 1200 | 1500 |
Analysis | 需求分析 | 120 | 60 |
Design Spec | 生成设计文档 | 60 | 120 |
Design Review | 设计复审 | 60 | 30 |
Coding Standard | 代码规范 | 30 | 30 |
Design | 具体设计 | 180 | 120 |
Coding | 具体编码 | 900 | 1200 |
Code Review | 代码复审 | 60 | 60 |
Test | 测试 | 240 | 360 |
Reporting | 报告 | 30 | 120 |
Test Report | 测试报告 | 30 | 30 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem | 事后总结合计 | 3000 | 3780 |