软件工程个人项目— 数独
1120173032 王晗
Github:https://github.com/lion-han/sudoku
目录
PSP2.1 表格
PSP2.1 | Personal Software ProcessStages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 55 |
Estimate | 估计这个任务需要多少时间 | 20 | 20 |
Development | 开发 | 700 | 700 |
Analysis | 需求分析 (包括学习新技术) | 380 | 450 |
Design Spec | ·生成设计文档 | 100 | 60 |
Design Review | 设计复审 (和同事审核设计文档) | 50 | 30 |
Coding Standard | 代码规范 (为目前的开发制定合适的规范 | 30 | 30 |
Design | · 具体设计 | 80 | 60 |
Coding | 具体编码 | 750 | 1200 |
Code Review | 代码复审 | 60 | 80 |
Test | 测试(自我测试,修改代码,提交修改) | 250 | 1160 |
Reporting | 报告 | 240 | 480 |
Test Report | 测试报告 | 60 | 120 |
Size Measurement | 计算工作量 | 30 | 30 |
Postmortem & Process Improvement Plan | 事后总结, 并提出过程改进计划 | 40 | 50 |
合计 | 2850 | 4520 |
个人日报:
日期 | 工作情况 | 耗时 |
---|---|---|
12.23 | 分析并选题 | 60min |
12.24 | 创建博客、github仓库、需求分析、解题思路 | 90min |
12.25 | 解题思路 | 50min |
12.26 | 学习DLX算法、生成数独终局,github托管调试 | 190min |
12.27 | 完成数独题目生成部分,github托管成功 | 170min |
12.28 | 完成解数独部分(暴力解),加入命令行分析与报错,进行代码结构化调整 | 250min |
12.29 | vs项目出错,修改bug,重建项目,push | 180min |
1.14 | 发现没有进行行的交换,修改代码,实现生产1200000个不同的数独 | 150min |
1.15 | 学习单元测试工具之间的区别,学习安装使用gtest进行单元测试 | 200min |
1.16 | 优化数独生成时间,尝试不同文件读写方式 | 240min |
1.17 | 学习vs性能分析工具,进行string字符串处理的优化,文件读写的优化 | 300min |
1.18 | 继续学习单元测试,完成单元测试,进行覆盖率,进行代码分析,修改错误 | 360min |
1.19 | 补充单元测试,组织博客内容 | 300min |
程序功能简介:
实现一个命令行程序,具有以下功能:
-
生成不重复的N个数独终局至文件(1<=N<=1000000)
-
读取文件内的数独问题,求解并将结果输出到文件
具体需求详见软件工程基础-个人项目文档
结题思路:
生成终局
最开始的想法是一位一位的产生数独的每位数字,但是总也想不出高效的解决方法。于是上网查了些资料,并通过自己画图研究数独时发现,想要生成数目庞大的数独终局,不能通过随机的分配或者一位一位产生,必须通过一定规律的变化得到。
我发现通过确定第一行的数,后面7行通过对第一行不同的移位可产生一个数独终局。由于每个3*3的方块内不能有重复的数,所以1、2、3行,4、5、6行,7、8、9行内位移必须大于等于3,不然会出现重复。
通过变化第一行的数顺序,其余7行移位规律不变(),可产生8!=40320个终局(第一位固定,后面7行移位规律相同)
之后可以通过2、3行,4、5、6行,7、8、9行内交换任意两行可得到不同的矩阵,共2*6*6=72种变化
通过上述变化可产生 8! *(2*6*6)=2903040>1e6 可满足条件,并且分析后不会产生相同的数独。
但是如何进行第一行的全排列是一个问题,首先我想到的是,通过遍历来解决,但是9个数字需要9^9 的时间消耗来解决,显然不可行,然后我想到类似于桶排序的方法确定每位是否被用过,但是并不能提升效率。然后上网搜索,发现一种康托展开的方法,它是一个全排列到一个自然数的双射,可以把一个自然数转换成一个对应的全排列,也可以把一个全排列转换成一个对应的自然数,使用逆康托展开可以在O(N*N)的时间复杂度下形成一个全排列。效率有了很大的提升。实现个小模块之后,通过与ACM室友的交流,我了解到c++里有一个产生全排列的函数 next_permutation 可以产生全排列。于是我去网上搜索它的实现原理,next_permutation 通过分析全排列的规律来交换数字反向操作实现下一个全排列,时间复杂度可达O(N),已经非常好了。
全排列问题解决了,如何进行第一行的移位呢,首先顺序移位是有点慢的O(N),使用链表可达到O(1),但是链表比较麻烦,最终想出了将第一行数字再连接一个第一行数字,通过每次取不同位置的9个数即可,可达到O(1)。
然后便是交换数独的行,形成新的数独,由于要求第一行首为与学号有关的特定数,所以第一行不动,交换其他行。首先我想到的是实现一个函数用来交换特定行的内容,但是这不仅要求交换两行,而且要求交换3行,且交换的规律是特定的很难通过递归循环来实现。于是我想到根据上面形成的初始数独,对他的行的排列写一个30*9的数组,规定行的顺序,然后按照顺序进行数独的生成。
生成题目
由于上一步已经产生了许多数独终局,所以数独问题的产生就可以依靠产生的数独终局文件了。读取数独终局文件,通过输入参数n,可以生成n个数独题目。题目的生成通过随机给数独的位置赋值0来解决。也可以加入一个空白比例参数实现题目的难度级别。再写入文件。
求解数独
开始时感觉数独求解应该很费时间,所以先去网上搜索了一下数独的求解问题。发现了一个DLX算法用来解决精准覆盖的问题,刚好可以用在解数独上,它通过一种双向十字链表的数据结构加上搜索来解决问题。但是把它用来解数独实践起来还是有点难度。毕竟是一个软件工程项目,不仅仅是程序设计问题,所以我就先使用简单的DFS(深度优先搜索)进行数独的求解。在实现后发现挖空率在60%都可以非常快的求解出来,所以就先使用DFS来解数独了。分析挖空率为50%的数独,发现实际上求解还是挺简单的,每个空可填的数字都非常有限。所以决定使用DFS来实现数独求解。
其他博客: