【BUAA软工】第1次作业 个人项目 数独

一、项目地址

github地址:https://github.com/BuaaAlen/sudoku

二、PSP表格

三、解题思路描述

在拿到这个题目时,我的第一个想法是这样的:9乘9的大方格有9个3乘3的小方格,在每个方格中随机的写入2到3个数,同时不违背数独的规则,之后从第一个空格开始,按照从左至右,从上至下的原则进行回溯,每次回溯所选取的值是在当前情况下,满足数独游戏规则所能选取的值中的一个,如果发现当前空格内没有可选取的值,则证明至少上一步的选择是错误的,回溯到上次选择重新进行选取。这可能是逻辑上最简单的算法之一,但考虑到后续有对程序性能的考察,那么这种暴力回溯算法就放弃了。但是求解数独时还是采用的这种方法。

暴力回溯不可取,于是自然而然的想到了按照某一种数学规则进行数独的生成。请教了一位同学,将9个数字分为3组,比如147,258,369,之后在每个3乘3的方格中,将这9个数字分成3次填入,每次直接填1组的三个数字,下图就是一个例子:

后来在网上查资料时看到了一篇博客,博客中的算法是对于一个已知的数独矩阵,将矩阵中的{1,2,3,...,9}映射到{1,2,3,...,9}中,并且保证映射后的数字与之前的数字存在不同,按照映射后的数字重新生成数独,就会得到一个与之前不同的数独。这是算法的基本原理,需要进一步改进后才可以适应我们的问题。

首先,需要解决映射问题。现有123456789这样一个序列,我们要将它不断的加以变换,也就是要得到它的全排列。在大二学习的组合数学中,是有讲过生成全排列(不断得到下一字典序的序列)的算法的,具体的算法和代码请看之后的代码说明部分。

映射的问题解决了,之后我们要找到要施以变换的数独,需要几个数独呢?一个9个数字的序列的全排列一共可以产生9!个序列,但是,因为题目中有对数独左上角第一个数字的要求:

"在生成数独矩阵时,左上角的第一个数为:(学号后两位相加)% 9 + 1。例如学生A学号后2位是80,则该数字为(8+0)% 9 + 1 = 9"

所以实际上是对8个数字全排列,得到的序列数是8!个,近40000,为满足最后测试数据1000000的需求,我们要准备至少25个基础数独。

这里是一个百度文库的连接,上面是200个数独题目的答案,但是其中的数字排列是不规范的,因为只想得到字符串,复制粘贴下来后再一个个去删空格有太麻烦,于是我写了一个python程序,只有几行,但我觉得这几行应该节约了我差不多20分钟的时间。

  1. ans = ""
  2. while (True):
  3. s = input("Please Input String: ")
  4. if len(s) == 3:
  5. print(ans)
  6. ans = ""
  7. for i in s:
  8. if i in ['1','2','3','4','5','6','7','8','9']:
  9. ans += i

最后得到的效果是这样的:

四、设计实现过程

整体程序的实现过程是这样的:

1.在程序运行之前,要进行初始化工作。

  • initBasicSudokuNum()

    初始化每个基础数独的映射序列,全部初始化为123456789

  • initBasicSudoku()

    初始化基础数独,即将数字字符串全部转换成9*9的二维数组basicSudoku

2.当命令行输入生成数独的命令,"-c 数字",获取数字num,并进行num次循环,每次循环执行以下内容。

  • updateBasicSudokuNum()

    更新当前基础数独的序列,得到该序列下一字典序的序列

  • createCompleteSudoku()

    按照得到的序列进行一一映射,替换掉基础数独中的数字,得到一个全新的数独,用二维数组sudoku来存储,并输出到文件中

3.当命令行输入求解数独的命令,"-s 文件路径",按照输入的文件路径读取文件内容到二维数组puzzle后,进行以下操作。

  • solvePuzzle(i, j)

    回溯函数,i,j表示当前回溯到的坐标,通过调用下面的函数来决定某个值是否可以被选取,以此来决定之后的计算。

  • checkPuzzle(i, j, k)

    一个布尔型函数,表示在当前情况下,puzzle[i][j]是否可以选取值k,可以选取就返回true

4.如果生成数独的输入请求是不合法的,比如-1,abc,10000000000等,或者求解数独的puzzle文件输入是不合法的,则在控制台输出error,并将一个全局布尔变量赋值为false。

5.单元测试的设计:

定义了一个布尔函数checkSudoku(sudoku[][]),用来检测一个9乘9的数字矩阵是否满足数独的游戏规则,满足则返回true,否则返回false

  • TestMethod1

    生成1000个数独,并对这些数独做正确性的检查

  • TestMethod2

    生成1000个数独,并对这些数独做重复性的检查

  • TestMethod3

    测试生成数独的命令里,规定数独个数的部分为字符串,并检查程序是否能够检测到输入不合法

  • TestMethod4

    测试生成数独的命令里,规定数独个数的部分为负数,并检查程序是否能够检测到输入不合法

  • TestMethod5

    测试生成数独的命令里,规定数独个数的部分为无穷大(用一个足够大的整数代替),并检查程序是否能够检测到输入不合法

  • TestMethod6

    测试生成数独的命令里,规定数独个数的部分为0,并检查程序是否能够检测到输入不合法

  • TestMethod7

    当求解数独的输入为正常数独题目时,测试答案的正确性

  • TestMethod8

    当求解数独的输入全为0时,测试答案的正确性

  • TestMethod9

    当求解数独的输入有多个题目时,测试答案的正确性

  • TestMethod10

    当求解数独的输入为一个不合法的数独题目时,测试程序是否能够检测到输入不合法

五、性能分析

1.最开始的版本生成100万个数独的时间为73.43秒,之后通过visualStudio自带的性能分析工具进行检测,发现耗时最多的函数是fprintf

想到的优化方案是将要输出的数字先都整合在一个字符串上,最后一次性输出。优化后生成100万个数独的时间为38.67秒,提速了近50%。

2.将第一次优化后的版本再次使用性能分析工具进行分析,得到如下的结果。

发现占93%时间的是c++里string类中的字符串拼接符号 += ,然而,我们需要每个数字之间都有空格,红色最深的两行已经不能再进行优化了。但是,如果我们使用字符串数组中对于单个字符的赋值符 = ,会提速不少。经过改进,生成100万个数独的时间只需要1.88秒,与之前的结果不在一个数量级上。

最终的版本进行性能分析后,程序中消耗最大的函数是fputs。

3.呃,在debug环境生成的exe得到100万个数独需要1.88秒,而在release环境下得到100万个数独只需要0.88秒。

六、代码说明

生成数独功能最重要的部分是得到全排列,求解数独功能关键的部分则是递归函数。

生成全排列:

  1. /*得到全排列来进行数独中数字的映射*/
  2. void updateBasicSudokuNum(int num)
  3. {
  4. int k = -1;
  5. //从右向左,找到一个a[k],使得a[k]<a[k+1]
  6. for (int i = 6; i >= 0; i--) {
  7. if (basicSudokuNum[num][i] < basicSudokuNum[num][i + 1]) {
  8. k = i;
  9. break;
  10. }
  11. }
  12. if (k == -1) {
  13. return;
  14. }
  15. int min = 10;
  16. int minNum = 0;
  17. //a[k+1]到a[7]中,所有数字都比a[k]大,在其中找到最小的数字
  18. for (int i = k + 1; i <= 7; i++) {
  19. if (basicSudokuNum[num][i] < min && basicSudokuNum[num][i] > basicSudokuNum [num][k]) {
  20. min = basicSudokuNum[num][i];
  21. minNum = i;
  22. }
  23. }
  24. //将找到的这个最小的数字与a[k]进行交换
  25. int tmp = basicSudokuNum[num][minNum];
  26. basicSudokuNum[num][minNum] = basicSudokuNum[num][k];
  27. basicSudokuNum[num][k] = tmp;
  28. //之后将a[k+1]到a[7]的顺序颠倒
  29. for (int i = k + 1; i <= 7; i++) {
  30. int t = 7 - (i - k - 1);
  31. tmp = basicSudokuNum[num][i];
  32. basicSudokuNum[num][i] = basicSudokuNum[num][t];
  33. basicSudokuNum[num][t] = tmp;
  34. if (t - i <= 1) {
  35. break;
  36. }
  37. }
  38. }

回溯求解数独:

  1. /*回溯求解数独*/
  2. void solvePuzzle(int x, int y)
  3. {
  4. for (int i = x; i < 9; i++) {
  5. for (int j = 0; j < 9; j++) {
  6. if (puzzle[i][j] != 0)
  7. continue;
  8. for (int k = 1; k <= 9; k++) {
  9. //判断(i,j)位置上是否可以填数字k
  10. if (checkPuzzle(i, j, k)) {
  11. puzzle[i][j] = k;
  12. //递归下面的步骤
  13. solvePuzzle(i, j);
  14. //flag为true表示当前的数独已经正确的填好了,即可退出
  15. if (flag) return;
  16. puzzle[i][j] = 0;
  17. }
  18. }
  19. //如果该位置最终选取的数字是0,则证明之前的某一步填错了,需进行回溯
  20. if (puzzle[i][j] == 0)
  21. return;
  22. }
  23. }
  24. //判断数独的所有位置是否都已经填满
  25. flag = true;
  26. for (int i = 0; i < 9; i++) {
  27. for (int j = 0; j < 9; j++) {
  28. if (puzzle[i][j] == 0)
  29. flag = false;
  30. }
  31. }
  32. //判断生成的数独是否正确
  33. if (!checkSudoku(puzzle))
  34. flag = false;
  35. if (flag) {
  36. //输出数据到文件中
  37. ...
  38. }
  39. }

[BUAA2017软工]第1次个人项目 数独的更多相关文章

  1. 软工造梦厂团队项目(Alpha版本发布2)

    课程 (https://edu.cnblogs.com/campus/xnsy/GeographicInformationScience) 作业要求 https://www.cnblogs.com/h ...

  2. [BUAA2017软工]个人作业week-1

    一.快速看完整部教材,列出你仍然不懂的5到10个问题,发布在你的个人博客上. 1.在第二章个人技术和流程,邹欣老师提到了一张表格,主要解释了效能分析的一些名词,其中有这么几个概念: 调用者:函数Foo ...

  3. [敏捷软工团队博客]Beta阶段项目展示

    团队成员简介和个人博客地址 头像 姓名 博客园名称 自我介绍 PM 测试 前端 后端 dzx 秃头院的大闸蟹 大闸蟹是1706菜市场里无菜可卖的底层水货.大闸蟹喜欢音乐(但可惜不会),喜欢lol(可惜 ...

  4. 2020BUAA软工结伴项目作业

    2020BUAA软工结伴项目作业 17373010 杜博玮 项目 内容 这个作业属于哪个课程 2020春季计算机学院软件工程(罗杰 任健) 这个作业的要求在哪里 结伴项目作业 我在这个课程的目标是 学 ...

  5. [BUAA软工]第一次博客作业---阅读《构建之法》

    [BUAA软工]第一次博客作业 项目 内容 这个作业属于哪个课程 北航软工 这个作业的要求在哪里 第1次个人作业 我在这个课程的目标是 学习如何以团队的形式开发软件,提升个人软件开发能力 这个作业在哪 ...

  6. BUAA2020软工团队beta得分总表

    BUAA2020软工团队beta得分总表 [TOC] 零.团队博客目录及beta阶段各部分博客地址 团队博客 计划与设计博客 测试报告博客 发布声明博客 事后分析博客 敏 杰 开 发♂ https:/ ...

  7. 关于软工项目beta版本

    项目总结 项目成员: 黄丰润 031302307 王旭銮 031302320 张家俊 031302329 张晓燕 031302343 项目完成度:实现了专业信息填写.查看,教师信息填写,报课和查看课表 ...

  8. 【2017集美大学1412软工实践_助教博客】团队作业10——项目复审与事后分析(Beta版本)

    写在前面的话 转眼轰轰烈烈本学期的软工实践就结束了,这个过程中想必在熬夜敲代码,激烈讨论中留下诸多回忆的同时,也收获了不少.恭喜所有团队完成了本阶段冲刺,此外,由于大家的贡献分给的都很平均,将个人贡献 ...

  9. [2017BUAA软工助教]个人项目准备工作

    BUAA软工个人项目准备工作 零.注册Github个人账号(你不会没有吧..) 这是Git的使用教程: http://www.cnblogs.com/schaepher/p/5561193.html ...

随机推荐

  1. 网络编程_IP对象_InetAddress

    import java.net.InetAddress; import java.net.UnknownHostException; public class IPDemo { public stat ...

  2. 难点--均方误差(MSE)和均方根误差(RMSE)和平均绝对误差(MAE)

    MSE: Mean Squared Error 均方误差是指参数估计值与参数真值之差平方的期望值; MSE可以评价数据的变化程度,MSE的值越小,说明预测模型描述实验数据具有更好的精确度. MSE=1 ...

  3. java.lang.NoClassDefFoundError: org/eclipse/core/resources/IContainer

    启动eclipse报错:java.lang.NoClassDefFoundError: org/eclipse/core/resources/IContainer 解决办法: 删除以下文件.metad ...

  4. 【html5】如何让Canvas标签自适应设备

    javascript方法: var oC=document.querySelectorAll('canvas')[0];oC.width=document.documentElement.client ...

  5. js点滴

    1. promise用法 https://www.cnblogs.com/lvdabao/p/es6-promise-1.html https://segmentfault.com/a/1190000 ...

  6. node.js如何将远程的文件下载到本地、解压、读取

    其实要解决的问题,很简单,获取远程文件,然后解压到本地读取. 在vscode中通过node.js来实现是比较方便的,相比之前的zip.js,我觉得我还是比较喜欢node.js实现方式. test.js ...

  7. maven项目红叉问题

    maven项目红叉问题,有的时候是因为代码报错,有的时候是因为JDK的缘故,比如新建Maven项目,默认JDK为5,这时你在pom.xml配置了JDK为8.这时项目就报错了,就需要你update pr ...

  8. <计算机网络>计算机网络和应用层

    1.端系统通过通信链路和分组交换机连接在一起,构成网络.网络和网络之间通过路由器相连,组成了因特网. 2.ISP(Internet Service Provider)因特网服务提供商.端系统通过ISP ...

  9. Objective-C Collection was mutated while being enumerated crash

    Collection was mutated while being enumerated

  10. three.js - 动画 图形统计帧频 dat.GUI

    运行一把: 代码解释: <!DOCTYPE html> <html lang="en"> <head> <meta charset=&qu ...