软件工程个人项目-数独

软件工程个人项目-数独

一.项目的Github地址链接:

https://github.com/tarxs/sudokuproject

二.psp预估耗时表格:

软件工程个人项目-数独_第1张图片

三.解题思路:

A.如何生成数独

看到题目,首先搜索数独具体的定义。由于题目和格局有关,自然而然想到n皇后问题,继而想用回溯法求解这个题目。但是输入的规模太大了,即使剪枝也不能达到很好的时间复杂度。于是我在网上搜索了一些如何用计算机解数独的算法。其中一个方法令我印象深刻。即一个完整的数独可以通过一个1-9的无重复的排列通过平移来生成。首先生成第一行是1-9的某种排列。剩下的所有行,由第一行向右平移不同的列,被顶出去的数放在最左边得到。原因是这样可以保证每一行都有不重复的1-9;如果平移的列均不相同,也可以保证每一列都有不重复的1-9。由于每个宫里出现的数字也要不相同,所以前三行,中间三行,后三行平移的列数之差应为3的倍数。
eg.
前三行:移动的列数0,3,6/0,6,3
中间三行:移动的列数1,4,7进行排列,共6种平移方式
后三行:移动的列数2,5,8进行排列,共6种平移方式
加上第一行的全排列共8!(因为第一位已经固定了)种
所以一共能生成8!×2×6×6=2903040>1000000,满足题目要求。

B.如何解数独

解数独我就是用普通的回溯法(暴力搜索)来做的。改进剪枝后减少了检查时的循环。用数组vis来记录一个数是否能放在某个位置。

四.设计实现过程:

因为题目有两个要求,所以设立两个函数Creat_Sudoku()Solve_Sudoku()分别用来生成数独和解数独残局。
Solve_Sudoku()需要一些函数辅助,TraceBack()用来进行回溯,check_num()用来检查数字能否放到某个空格中,prt()用来打印最后解出的数独终局。
改进后用数组vis来记录是否可以放某个数而不是循环检验。即用SetVis()ResetVis()CheckCanVis()来代替check_num()

下面是Solve_Sudoku()的流程图
软件工程个人项目-数独_第2张图片

五.性能测试、瓶颈及改进:

在完成初版的代码后,我使用vs自带的性能分析工具进行性能测试。分别测试生成1e6个数独和解1000个空很多的数独。

生成数独花了76s,发现其中96%的时间都是花在打印(性能瓶颈)上了。经过上网查询资料后发现,对文件的输出需要打开文件,反复的打开文件关闭文件,并向文件中输出这个过程会极大的增加时间消耗,故后来我采用了一种可以一次性输出所有结果的方法:将所有生成的数独终局以一个字符串的形式存在一个极大的字符数组中,最后一次性输出到文件中。对改进的生成数独的代码做性能测试,对于生成1e6的数独,需要3.8s。此时,next_premutation这个c++库函数花去了最多的时间
软件工程个人项目-数独_第3张图片
软件工程个人项目-数独_第4张图片
解数独我用上面所述的方法进行改进。我测试解1000个空极其多的数独,做性能测试花了10分钟48秒。

耗费时间最多的自然是回溯的部分。
软件工程个人项目-数独_第5张图片
软件工程个人项目-数独_第6张图片

六.测试:

1.对命令行进行测试(这里我直接改变vs中属性页中命令的调试参数,就不用麻烦地输入命令了)

(1)命令的字符串个数不是3
软件工程个人项目-数独_第7张图片
(2)第三个字符串非数字
软件工程个人项目-数独_第8张图片
(3)第三个字符串的数字不在1-1000000的范围内
软件工程个人项目-数独_第9张图片
(4)第二个字符串不是-c或者-s。
软件工程个人项目-数独_第10张图片

2.对生成数独进行测试

(1)生成1个数独
软件工程个人项目-数独_第11张图片
(2)生成100个数独
(3)生成1e6个数独

3.对解数独进行测试
(1)对于几乎全为0的数独进行求解
软件工程个人项目-数独_第12张图片
(2)对200个正常数独进行求解

七.代码说明:

1.生成数独:

软件工程个人项目-数独_第13张图片
move_up,move_mid,move_down分别是第一行向右移动的列数。例如,第一次时,第二行是由第一行向右移动3列,以此类推,分别是向右移动3,6,2,5,8,1,4,7行。
first_row中放着第一行的数。
软件工程个人项目-数独_第14张图片
next_premutation是c++STL库中的一个函数。作用是可以将一个数组中的元素进行排列。使用一次就可以生成比前一次略大的一个排列,如果已经是最大的排列,再使用一次它会变成最小的排列。比如int s[3] = {1,2,3};使用next_premutation(s,s+2)s会变成{1,3,2}。这里我们利用next_premutation使每一行移动的列数发生变化,从而生成不同的数独。先变后三行(move_down),每一次都让移动的列数变成另外一个排列,共6种;所以每6次变中间三行(move_mid),共6*6=36种;每36次变第二和第三行,共72种;每72次后,开始变第一行中数的顺序(第一个不能变,永远是(9+9)%9+1=1)。
软件工程个人项目-数独_第15张图片
move已经存放了每一行要移动的列数,根据move和第一行的值来生成其他行。
软件工程个人项目-数独_第16张图片
将结果读入超大char数组中,注意空格和换行的位置。

2.解数独:

软件工程个人项目-数独_第17张图片
软件工程个人项目-数独_第18张图片
打开文件,将所有待求解的数独一个一个读入。对每一个数独回溯求解,注意首先要将所有的已经填了数的格用vis做标记,这样会加快回溯的过程,避免很多不必要的尝试。将求解后的数独写入文件,关闭文件。

软件工程个人项目-数独_第19张图片
软件工程个人项目-数独_第20张图片
当n>80时,整个数独均被填完可以回到被调函数。当n对应当前格不是0,说明已经有数,直接跳过对下一个格进行求解;如果是0,从1到9依次尝试填入,首先检查该行,该列,该九宫格是否已经有这个数了,如果有,直接进行下一个数的尝试;如果没有,把该位置的0换成i,然后使用SetVis使vis对应位置置1,继续下一个n的回溯。退出回溯后,应用ResetVis标记表明该行,该列,该九宫格没有数i,接着把res该位置置1。

八.实际使用时间和预估时间对比psp表格:

软件工程个人项目-数独_第21张图片

九.心路历程与收获:

其实一开始听到这个项目的时候是很绝望的,感觉自己什么也不会,一拖再拖。不过真正静下心来开始做,我发现有些困难还是可以克服的。不停学习新的东西,并且问东问西,下载各种软件令人头疼。最后我还是用c的思想而不是面向对象来写程序了,以后还要多锻炼,使用c++的能力。编代码的过程大致就是看网上各种各样的代码,然后自己思考整个思路开始编代码。问题出现在自己以前从来不用vs,所以很多流程很多功能很不熟悉。具体编代码的问题是以前就对文件的读写很不熟练,这次也一样,那个在文件里换行的问题大概占我编代码时间的一半了,好在最后问了同学,知道了fopen_s这个函数。至于单元测试,因为这些函数大多都是void类型很难进行单元测试,于是我用了手工测试的方式,以后还是要学习如何使用单元测试。
从这次项目里我学习到了如何使用github,如何使用vs,体验了整个软件项目的全部流程。其中我觉得最有用的一个就是vs自带的性能分析了,这样就可以很轻松的知道你的代码会跑多长时间,并且知道你哪一部分花的时间最长,从而进行改进。除此之外,我感觉自己的耐挫折能力也有了增强。
最后感谢老师和网上各位大神以及我周围同学对我的帮助,如果不是老师,我可能很长时间也不会体验到这种项目的流程,也不能学习很多东西,如果不是同学们,我觉得自己绝对做不下来这个项目。前路还长,我知道我还有很多东西要学,很多事情要经历。

你可能感兴趣的:(软件工程个人项目-数独)