[BUAA_SE_2017]个人项目-Sudoku
个人项目作业-数独
时间预估
PSP2.1 | Personal Software Process Stages | 预估时间(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | |
· Estimate | · 估计这个任务需要多少时间 | 60 | |
Development | 开发 | 1350 | |
· Analysis | · 需求分析 (包括学习新技术) | 180 | |
· Design Spec | · 生成设计文档 | 180 | |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | |
· Design | · 具体设计 | 180 | |
· Coding | · 具体编码 | 300 | |
· Code Review | · 代码复审 | 120 | |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | |
Reporting | 报告 | 260 | |
· Test Report | · 测试报告 | 120 | |
· Size Measurement | · 计算工作量 | 20 | |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 120 | |
合计 | 1670 |
解题思路
生成数独终局:
- 首先生成左上角第一个3x3正方形,其中左上角格为LEFTTOP,共可生成
8!=40320
种组合; - 每一种组合都可通过(1x3或3x1的单元)排列组合扩散得到一个数独终局,记作该组合下基础数独终局;
- 某一组合下的基础数独终局可通过分别交换456列三列、789列三列、456行三行、789行三行得到正确衍生数独终局;根据排列组合原理,任一组合下的数独终局可生成衍生数独终局
(3!)^4=1296
个,且不重复;
- 首先生成左上角第一个3x3正方形,其中左上角格为LEFTTOP,共可生成
求解数独:
- 回溯法求解,当且仅当当前格赋值使得其它未填格无值可取时,更换当前格值;
- 若数独有解,则一定能找到;
实现过程
生成数独终局:
- 左上第一个3x3方格除第一个元素(左上角)外,利用遍历全排列得到基础数块;
- 再根据该基础数块扩散得到基础数独;
- 分别利用456列三列、789列三列、456行三行、789行三行的全排列,替换相应行列,得到合法数独终局。
求解数独:
- 从(1,1)至(81,81)遍历,将每一次赋值压入栈中;
- 遇某格无数可填时,弹出栈顶元素,更换取值,遍历位置回到栈顶元素,直至有值可取;
- 因此,若数独有解,则遍历结束时,所有未完成格均合法,即找到一解。
类:
- 程序除主类外共有6个类,其中输入、输出处理模块两个类,数独结构存储三个类,计算功能一个类;
InputHandler
类识别输入有效性,提取输入信息;SudokuNode
类表示每一个数独格子结点,有自身取值、所在行、列、格的指针;SudokuUnit
类代表行或列或格,存储各数字存在情况;SudokuHead
类封装9x9个SudokuNode,代表一个结构完整的数独;Calculator
类实现了生成、求解数独的方法;OutputHandler
类利用静态方法实现了数独的快速输出。
单元测试设计:
- 单元测试设计针对如下方面:
- 非法参数
- 数独存储结构检验
- 基础数独的生成检验
- 求解数独的顺序检验
- 如下图,单元测试均通过。
- 单元测试设计针对如下方面:
优化改进
生成数独:
- 第一版生成1百万需要32.67秒,最终版需要7.15秒;
- 优化手段:该方法生成的耗时本身不长,重点在于输出时对文件写入,以及整形转换为字符串的耗时。
优化前:
public static int Show(int[][] matrix)
{
for (int i = 0; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
sw.write((matrix[i][j]);
sw.write(" ");
}
sw.write("\n");
}
sw.Write("\n");
sw.Flush();
return 0;
}
优化后:
```
public static int Show(int[][] matrix)
{
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
sb.Append((char)(matrix[i][j] + 48));
sb.Append(' ');
}
sb.AppendLine();
}
sw.WriteLine(sb.ToString());
return 0;
}
```
**另外一点,StreamWriter的flush()耗时较长,因此优化后设置每10000个数独flush()一次,此时flush()耗时忽略不计;其次,10000个数独对于缓冲区来说不会溢出,但缓冲区存在上限,因此应及时将其清空。**
- 求解数独:
- 第一版生成1百万需要5分钟...,最终版需要59秒(好不容易压进一分钟,测试数据应该属于不是那么难得数独)
- 优化手段:削减复杂度,减少建立存储结构时的时间浪费。利用StringBuilder处理读如的一行文本信息;部分类成员变量作用域修饰符用public取代private,以空间换时间;构造SudokuHead时,减少时间复杂度;
**因改动比较细小,在此不列出代码**
关键代码
- 生成数独关键代码部分:生成全排列
利用离散数学中讲到的箭头生成排列方法生成,因为其每一次生成取决于上一次生成结果,因此适合用于此。
public int generateBasic(int[] prev, bool[] arrow)
{
if(prev[0] == 0)
{
for (int i = 0; i < prev.Length; i++)
{
prev[i] = i + 1;
arrow[i] = false;
}
return 0;
}
else
{
int max = -1;
int index = -1;
for(int i = 0; i < prev.Length; i++)
{
if(arrow[i] && i == prev.Length - 1 || !arrow[i] && i == 0)
{
continue;
}
int compare = arrow[i] ? prev[i+1] : prev[i-1];
if(compare < prev[i])
{
if(prev[i] > max)
{
max = prev[i];
index = i;
}
}
}
if(max == -1)
{
return -1;
}
int tmp = prev[index];
prev[index] = arrow[index] ? prev[index + 1] : prev[index - 1];
prev[index + (arrow[index] ? 1 : -1)] = tmp;
for(int i = 0; i < prev.Length; i++)
{
if(prev[i] > tmp)
{
arrow[i] = !arrow[i];
}
}
return 0;
}
}
- 求解数独关键代码:回溯、出入栈
利用C#中Collections中的Stack类做栈操作
public int Solve(SudokuNode[][] nodes, Stack operationStack)
{
int count = 0;
for(int i = 0; i < 9; i++)
{
for(int j = 0; j < 9; j++)
{
if(!nodes[i][j].getFlag() && nodes[i][j].getValue() != 0)
{
count++;
}
else
{
SudokuNode node = nodes[i][j];
bool[] validation = new bool[9];
if (node.getValidation(validation))
{
node.setValue(node.nextValue());
operationStack.Push(node);
}
else
{
node.reset();
SudokuNode prev = (SudokuNode)(operationStack.Pop());
i = prev.x;
j = prev.y - 1;
prev.Flag();
}
}
}
}
return 0;
}
实际耗时
PSP2.1 | Personal Software Process Stages | 预估时间(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 60 | 30 |
· Estimate | · 估计这个任务需要多少时间 | 60 | 30 |
Development | 开发 | 1350 | 1180 |
· Analysis | · 需求分析 (包括学习新技术) | 180 | 150 |
· Design Spec | · 生成设计文档 | 180 | 90 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 10 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 0 |
· Design | · 具体设计 | 180 | 240 |
· Coding | · 具体编码 | 300 | 360 |
· Code Review | · 代码复审 | 120 | 30 |
· Test | · 测试(自我测试,修改代码,提交修改) | 300 | 300 |
Reporting | 报告 | 260 | 65 |
· Test Report | · 测试报告 | 120 | 30 |
· Size Measurement | · 计算工作量 | 20 | 5 |
· Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 120 | 30 |
合计 | 1670 | 1275 |
总结
本次个人项目作业我花了非常多的时间在研究怎么优化IO上,虽然最后的成果是大幅减小了我的程序运行时间,但是和其他同学以及巨佬比起来还是太naive了,而且求解数独的算法上使用的是复杂度很高的回溯法。因此,通过这次个人项目作业,最大的收获在于认识到了自身极大的不足,继续好好学习去。。
在PSP2.1上,认为要在coding前先全部设计好,定好代码规范,然而实际上并没有认认真真好好设计。回过头看,如果在设计阶段做好了调研以及好的优化想法,那么到后来的优化会显得非常容易。因此,下次项目coding前一定做足准备工作。(更多关于这方面想say的,就放在week1另外一个作业里吧)
简单制作了一个GUI界面,支持数独题生成并显示,用户可在上进行输入,并检查答案是否正确。(不需要什么依赖包,C#wpf桌面应用)
[BUAA_SE_2017]个人项目-Sudoku的更多相关文章
- [BUAA_SE_2017]结对项目-数独程序扩展
结对项目-数独程序扩展 Runnable on x64 Only sudoku17.txt 须放置在可执行文件同目录中,可移步以下链接进行下载 Core-Github项目地址 GUI-Github项目 ...
- Sudoku 小项目
Sudoku 小项目 - 软工第二次作业 Part 1 · 项目相关 Github 地址: https://github.com/TheSkyFucker/Sudoku 项目的更多信息以及所有开发文档 ...
- 17秋 软件工程 第二次作业 sudoku
2017年秋季 软件工程 作业2:个人项目 sudoku Github Project Github Project at Wasdns/sudoku. PSP Table PSP2.1 Person ...
- C++解决error C4996报错
今天用c++写了个数独程序,在编译过程中报了一个错误: 1>------ 已启动生成: 项目: sudoku, 配置: Debug Win32 ------1> main.cpp1> ...
- 第二次作业——个人项目实战(Sudoku)
Github:Sudoku 项目相关要求 利用程序随机构造出N个已解答的数独棋盘 . 输入 数独棋盘题目个数N 输出 随机生成N个 不重复 的 已解答完毕的 数独棋盘,并输出到sudoku.txt中, ...
- 软工实践作业2:个人项目实战之Sudoku
Github:Sudoku 项目相关要求 项目需求 利用程序随机构造出N个已解答的数独棋盘 . 输入 数独棋盘题目个数N(0<N<=1000000). 输出 随机生成N个不重复的已解答完毕 ...
- Sudoku 个人项目1
Github项目地址:Github 项目相关要求 随机构造出N个不重复的已解答的数独棋盘(0 < N <= 1000000) 在生成数独矩阵时,左上角的第一个数为:(学号后两位相加)% 9 ...
- 第二次作业——个人项目实战(sudoku)
第二次作业--个人项目实战(sudoku) 一.作业要求地址 第二次作业--个人项目实战 二.Github项目地址 softengineering1--sudoku 三.PSP表格估计耗时 PSP2. ...
- C#开源资源项目
一.AOP框架 Encase 是C#编写开发的为.NET平台提供的AOP框架.Encase 独特的提供了把方面(aspects)部署到运行时代码,而其它AOP框架依赖配置文件的方式.这种部署方面(as ...
随机推荐
- innerHTML、innerText和outerHTML、outerText的区别
区别描述如下: innerHTML 设置或获取位于对象起始和结束标签内的 HTML outerHTML 设置或获取对象及其内容的 HTML 形式 innerText 设置或获取位于对象起始和结束标签内 ...
- HDU - 4336 (容斥)
题意:给你n个奖,每个机会只能中一个奖,中奖的概率分别是{p1,p2,p3......pn}:并且这些奖是两两没有交集.(pi*pj=0)问,需要多少次才能把所有奖都中完的期望值. 先来分析:中所有奖 ...
- Linux系统学习之软件安装
一.源码包编译安装 由于计算机无法直接执行用高级语言编写的源程序,因此想要运行程序,就需要一种机制来让计算机识别,这样程序才可能运行起来.一般来说,计算机中存在解释型和编译型两种语言. 所谓解释型语言 ...
- [转]Qt中定时器使用的两种方法
Qt中定时器的使用有两种方法,一种是使用QObject类提供的定时器,还有一种就是使用QTimer类. 其精确度一般依赖于操作系统和硬件,但一般支持20ms.下面将分别介绍两种方法来使用定时器. 方法 ...
- 获取键盘的ascii码
waitKey(1) & 0xFF获取当前按的键的ascii码,
- vue 实现tab切换动态加载不同的组件
vue 实现tab切换动态加载不同的组件 使用vue中的is特性来加载不同的组件.具体看如下代码:这个功能对于vue比较复杂的页面可以使用上,可以把一个页面的功能拆分出来,使代码更简单.使用方式具体看 ...
- VUE2第五天学习---自定义指令
阅读目录 1.理解VUE中的自定义指令 回到顶部 1.理解VUE中的自定义指令 默认核心指令有 (v-model 和 v-show), 但是有时候我们需要用到自定义指令,在vue中,代码复用主要形式和 ...
- 【Codeforces Round 1120】Technocup 2019 Final Round (Div. 1)
Codeforces Round 1120 这场比赛做了\(A\).\(C\)两题,排名\(73\). \(A\)题其实过的有点莫名其妙...就是我感觉好像能找到一个反例(现在发现我的算法是对的... ...
- android客服端+eps8266+单片机+路由器之远程控制系统
用android客服端+eps8266+单片机+路由器做了一个远程控制的系统,因为自己是在实验室里,所以把实验室的门,灯做成了远程控制的. 控制距离有多远------只能说很远很远,只要你手机能上网的 ...
- 零基础入门到精通:Python大数据与机器学习之Pandas-数据操作
在这里还是要推荐下我自己建的Python开发学习群:483546416,群里都是学Python开发的,如果你正在学习Python ,小编欢迎你加入,大家都是软件开发党,不定期分享干货(只有Python ...