最近更新时间2018.12.29(百忙之中实现了UI界面,另开了一篇)

GitHub连接:https://github.com/blingopen/sudoku

部分参考资料链接(有些参考之后忘记贴出来了):

  • Git和Github使用
  • C#读写文件
  • 模板法生成数独
  • 回溯法生成数独
  • 解决Random函数随机数重复
  • 在线画概要设计图网站

内容目录

需求分析

概要设计

详细设计&核心代码实现

求解数独算法

生成终局函数

随机函数(生成随机数组)

输入输出函数

主函数

改进与提升!

性能分析与提高

单元测试

Sudoku类内测试

主函数测试

代码质量分析

总结



此软件会用C#语言编写。

首先我对这个数独项目完成的时间做了一个大致的估计。

psp2.1表格
PSP2.1

Personal Software

Process Stages

预估耗时(分钟) 实际耗时(分钟)
Planning 计划 60  
·Estimate ·估计这个任务需要多少时间 60  
Development 开发 1170  
· Analysis · 需求分析(包括学习新技术) 300  
· Design Spec · 生成设计文档 30  
· Design Review · 设计复审(和同事审核设计文档) 0  
· Coding Standard · 代码规范(为目前的开发制定合适的规范) 60  
· Design · 具体设计 180  
· Coding · 具体编码 300  
· Coding Review · 代码复审 120  
· Test · 测试(自我测试,修改代码,提交修改) 180  
Reporting 报告 360  
· Test Reporting · 测试报告 120  
· Size Measurement · 计算工作量 60  
· Postmortem & Process Improvement Plan · 事后总结,并提出过程改进计划 180  
  合计 1590  

本项目为个人项目,所以设计复审为本人自行复审(算在需求分析中),故和同行设计复审一栏时间花费为0.

需求分析

拿到题目之后我读了好几遍,终于把这个数独的需求摸索清楚了:

  1. 在控制台输入指令“sudoku.exe -c 有效数字”可以输出改有效数字数量的数独终局到txt文件中。
  2. 在控制台输入“sudoku.exe -s 绝对路径”可以读取绝对路径中txt文本中的数独,求解并输出结果到txt文件中。

首先计划了一番,计划的大致时间如上表,因为需求比较明确,并且功能没有很复杂,所以准备用瀑布模型来开发。

接着,我思考了一下,程序结构很简单,主要可分为以下几个板块,区分指令板块(包括不同的有效指令和无效指令),生成多个数独终局板块,求解数独板块,第一个板块直接在主函数中写一个判断就可以,后面两个板块要分别设计一些算法。

当然设计算法是后话,“工欲善其事,必先利其器”,要先把准备工作做好。所以先按照文档要求,创建一个CSDN博客和GitHub工程文件夹,在这里寻求百度的帮助,查找了一些使用GitHub和Git的方法,配置好了相关环境,建立了相应的文件夹和博客。(前三次commit是试验自己是否创建成功

然后按照正常的瀑布模型开发流程,计划了大致时间后,写了一个需求分析,参考了网上的模板和教材的流程,存入了GitHub中(文件 Analysis.docx),其中建模结果大致如下。

数据建模

本软件数据较为简单,抽象出来的实体只有一个完整的9×9的数独,和对应的每个3×3的小矩阵(共9个)。

图 1 sudoku系统初步ER图

功能建模

数据源即用户,数据终点为本地计算机,主要数据流即用户在控制台应用输入的各种指令和输出结果,主要处理过程即生成终局和求解数独。(详细设计回来发现此处图有些不恰当

图 2 sudoku系统顶层DFD图

继续划分,系统主要分为数独生成终局部分和数度求解部分,无法进一步精细化,故画到一层即可。

图 3 sudoku系统第1层DFD图

行为建模

主要状态分为等待指令的主界面状态,生成终局状态,求解数独状态和错误信息状态,其行为关系如下:(详细设计回来发现此处图有些不恰当

图 4 sudoku系统的状态图

通读文档需要学习的新技术有:

  1. 使用GitHub和git来管理代码。
  2. 生成数独终局和数独求解的算法。
  3. 如何使用代码质量分析工具,性能分析和诊断工具。
  4. 如何写GUI。

在之后的相应步骤处会仔细说明如何查找材料与具体步骤。接下来是设计阶段。(对应文档HighLevelDesign.docx)

概要设计

关于类的设计,需求分析中大概设计了两个类,后来觉得设置一个类应该就可以了,3×3矩阵的判断可以转化成一个二维数组。便于判定每个3×3矩阵内内行每列是否满足1-9只出现了一次,所以就除了本身的数独数组,分别设置了3个判定数组。

该类命名为Sudoku类,包含四个字段及其相应属性分别是:

字段

属性

类型

含义

shudu

Shudu

Char [9,9]数组

int[9,9]数组

数独矩阵

hang

Hang

Bool [9,9]数组

行判定,每一行代表数独每一行,从上至下依次标号,每一列代表数独该行该数是否出现

lie

Lie

Bool [9,9]数组

列判定,每一行代表数独每一列,从左至右依次标号,每一列代表数独该列该数是否出现

sansan

Sansan

Bool [9,9]数组

行判定,每一行代表数独每一个3*3小矩阵,从左至右从上至下依次标号,每一列代表数独该小矩阵该数是否出现

包含两个函数分别是public void GenerateSudokuEnding()和public void SolveSudoku(),分别用来生成终局和求解数独。

两个函数之间没有直接关联,是两种关于数独的功能(详细设计发现还是有调用关系的),不过内部会调用相关的字段,比如判断改行是否有重复数字的时候会查看hang,lie,sansan数组,同时会将数字写入shudu数组。

Sudoku类的数据结构大致如图所示:(以下类仅仅是概要设计,还不完全

class Sudoku{public void GenerateSudokuEnding(){//填充部分}public void SovleSudoku(){//填充部分}private char[,] shudu = new char[9, 9];//数独矩阵public char[,] Shudu{get { return shudu; }set { shudu = value; }}private bool[,] hang = new bool[9, 9];//行检验矩阵public bool[,] Hang{get { return hang; }set { hang = value; }}private bool[,] lie = new bool[9, 9];//列检验矩阵public bool[,] Lie{get { return lie; }set { lie = value; }}private bool[,] sansan = new bool[9, 9];//3×3检验矩阵public bool[,] Sansan{get { return sansan; }set { sansan = value; }}}
}

软件主操作界面为控制台,所有的参数输入都通过控制台传输给主函数,由主函数来判断具体的命令操作(同时也会判断命令是否正确),根据不同命令参数去执行不同板块:

  1. 在控制台输入指令“sudoku.exe -c 有效数字”可以输出改有效数字数量的数独终局到txt文件中。
  2. 输入“sudoku.exe -s 绝对路径”可以读取绝对路径中txt文本中的数独,求解并输出结果

当判断为命令1时,会调用Sudoku类的GenerateSudokuEnding函数来生成数独;当命令为2时,会调用Sudoku类的SolveSudoku函数来求解,否则,判定为无效命令,结束程序。

同时设置了一个输出函数static void OutputToTxt(Sudoku sudoku,StreamWriter streamWriter),统一将生成的数独输出到TXT文件中。

此时,程序的大体框架就构建出来了,接下来就是细化各个模块函数需要实现的算法,算法详细设计

详细设计&核心代码实现

为了方便处理,把数独数组的char型改成了int型(对应文档LowLevelDesign.docx)

求解数独算法

最开始的想法无论是求解数独还是生成终局,全部暴力+回溯,但是在1e6的大数量情况下显然会很慢。

经过思考,想了想还是用递归,不过要大量剪枝,先做预处理。给Sudoku类加了三个数组分别是hang[9,9],lie[9,9],sansan[9,9],即牺牲内存来换时间,第一个参数分别代表了第i行,第i列,第i个小3×3矩阵,第二个参数代表这行出现的数字,如果出现则标记为true,没有出现则标记false,这个方法极大地缩短了时间。

经典的回溯故不详细赘述,以下是代码:

public bool SolveSudoku(int position){int index = position;for (; index < 81 && 0 != shudu[index / 9, index % 9]; index++) ;//非零就不做if (index < 81){int line = index / 9;int col = index % 9;for (int i = 1; i < 10; i++){if (hang[line, i - 1] || lie[col, i - 1] || sansan[line / 3 * 3 + col / 3, i - 1])continue;shudu[line, col] = i;hang[line, i - 1] = true;lie[col, i - 1] = true;sansan[line / 3 * 3 + col / 3, i - 1] = true;if (SolveSudoku(index + 1))return true;shudu[line, col] = 0;hang[line, i - 1] = false;lie[col, i - 1] = false;sansan[line / 3 * 3 + col / 3, i - 1] = false;}return false;}return true;}

生成终局函数

最开始想暴力+回溯,外面再套一个大循环,显然在1e6的大数量情况下会很慢。

在网上搜索了很多的数独相关资料,发现最快的是模板法,即生成某一行或某一列或某个3*3小矩阵填入模板,即可构成一个完整的数独终局,由于左上角的数字固定为后两位模9+1(我的是(3+5)mod 9 + 1 =9),故共有8!= 40320种,另外23行(第一行固定不动),456行,789行也可互相交换,数独性质仍成立,故一共有40320 × 2!× 3!× 3!约等于3e6,超过了要求的1e6。

这个需要写一个全排列函数,C++有直接的函数可以调用,考虑到C#没有,而且如果之后做UI(学期内应该是没有时间做了)可以游玩,这种方法生成的数独矩阵很固定可玩性不高,于是想到了生成随机数,思路又回归到了回溯法上。

先思考我们也可以实现填上一部分数字,然后用回溯方法,这里可以调用求解数独的函数。一共有9个3×3矩阵,对角线上的3个矩阵是互不干扰的,所以可以随机生成这三个矩阵,然后用优化后的回溯来生成数独解,回溯方法生成的是固定解,所以种子相同,解就会相同,所以这种方法原则上可以生成8!× 9!× 9!约等于1e15,所以添加一个随机函数来打乱1-9的数组填进去就可以了,应该会有重复的情况,推测重复的概率会非常小。同时,每次生成完一个终局后要初始化Sudoku类的对象,重置预处理的标记等。

首先是生成3*3小矩阵的代码,由于第一个矩阵第一个数字不能动,所以将1与5,9区别开来,考虑到分开写两个函数重复度很大,故将其重构成一个函数用标号加以区分,首先调用void Program.Random159(int[])函数生成随机数组,然后将数字填进去,并且预处理。

 private void Generate33(int[] gene, int num){int temp = 0;int row;switch (num){case 1: { row = 1; temp = 1; } break;case 5: row = 4; break;default: row = 7; break;}Program.Random159(gene);//调用生成随机数组的函数for (int i = 0; i < gene.Length; i++){if (i + temp < 3){shudu[row - 1, row / 3 * 3 + (i + temp) % 3] = gene[i];hang[row - 1, gene[i] - 1] = true;}else if (i + temp < 6){shudu[row, row / 3 * 3 + (i + temp) % 3] = gene[i];hang[row, gene[i] - 1] = true;}else{shudu[row + 1, row / 3 * 3 + (i + temp) % 3] = gene[i];hang[row + 1, gene[i] - 1] = true;}lie[row / 3 * 3 + (i + temp) % 3, gene[i] - 1] = true;sansan[num - 1, gene[i] - 1] = true;}}

然后是数独终局生成函数,基本上是在调用其他函数。

 public void GenerateSudokuEnding(){int[] gene59 = new int[9] { 9,5,2,7,6,1,3,8,4 };//5,9矩阵的种子数组int[] gene1 = new int[8] { 5,2,7,3,6,8,1,4 };//1矩阵的种子数组Generate33(gene1, 1);Generate33(gene59, 5);Generate33(gene59, 9);SolveSudoku(0);//调用递归}

每次重新生成一个新的数独终局之前,都要将数独初始化,除了第一个位置为固定数字外,其余的均为0,并且预处理的数组也相应初始化。

public void Initialize(){for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){shudu[i, j] = 0;hang[i, j] = false;lie[i, j] = false;sansan[i, j] = false;}}shudu[0, 0] = 9;hang[0, 9 - 1] = true;lie[0, 9 - 1] = true;sansan[0, 9 - 1] = true;}

还是有些打脸,之前说两个函数彼此之间应该是独立的,然而最终变成了一个调用另一个,所以需求分析中画的功能(图2,3)和行为建模图(图4)不是很准确了。

随机函数(生成随机数组)

手动写了一个打乱数组的算法,即在当前数组号中随机抽一个,把该位置的元素与末尾元素交换,当前数组号减1,重复上述步骤直到数组号为1,因为用不到Sudoku类内的元素,所以写在了主函数类中。

public static void Random159(int[] gene){Random rand = new Random();for (int i = gene.Length - 1; i > 0; i--){int rdmchar = rand.Next(1000) % i;int temp = gene[rdmchar];gene[rdmchar] = gene[i];gene[i] = temp;}}

输入输出函数

用的是普通的流读写,很简单的文件操作,就不贴代码了,值得注意的是输出文件要输出到相对路径文件夹,所以调用了一个系统函数:

System.AppDomain.CurrentDomain.BaseDirectory

这个函数会记录当前应用程序的所在文件夹路径,并返回该路径的字符串,所以生成输出文件的地址也就很方便了。

主函数

主控模块(Main)主要用来接收参数并判断命令。

控制台接收命令参数,主要从args这个字符串数组传入,以空格分割,一个参数存入一个单元,由于两个命令都是双目操作,所以数组长度如果不等于2则判断命令有误。

当命令为-c 时,第二个参数我们试图将其变为整型,如果合法则调用生成数独函数,生成终局写入txt文件中,不合法则报错

当命令为-s 时,第二个参数我们先检验书读文件绝对路径是否存在,如果存在则按行读取直到文件尾,读入的数据存入到一个string中,再按空格进行分割到string数组中,然后每81个构成一个数独谜题求解输出,直到将所有的数字用完,否则报错

由于只有两个命令所以判断都写在了主函数中,日后有时间可以进行重构,使代码看起来清晰美观,若增加命令也可复用。主函数看起来比较乱,所以不在此贴代码,可以去GitHub中查看。

改进与提升!

输出发现有一些数独连续几个是完全相同的,查找了很多原因都没能解决,最终找到了问题的根本原因——随机函数有周期

通过查资料了解到,所有的随机函数都是伪随机函数,默认的Random是以时间为种子的,短时间内生成的随机数是相同的(这也就解释了写完发现有好几个终局是连续相同的),后来查到了一个延时的算法来上随机函数的种子刷新,random之前加入了sleep函数,效果还算可以,但也正是因为延时,所以时间变得很慢。

Thread.Sleep(1);//被弃用的延时

后来查到了一个获得随机数的办法,用一个加密算法生成一串比特位,然后将这个比特位转换成整型数作为种子即可。

这个方法生成的终局目前来看没有重复的,比较高效。

static int GetRandomSeed(){byte[] bytes = new byte[10];System.Security.Cryptography.RNGCryptoServiceProvider rng = new System.Security.Cryptography.RNGCryptoServiceProvider();rng.GetBytes(bytes);return BitConverter.ToInt32(bytes, 0);}

随机函数的生成对象也应改为:

Random rand = new Random(GetRandomSeed());

代码基本完毕,操作系统考试后继续更新,单元测试,代码分析等。

性能分析与提高

图5 性能分析1

可以看出,主要是SolveSudoku()函数耗时最多,点进去可以看出,递归调用最红(消耗最多),即回溯法生成数独终局的方法很慢,整体生成1e6个终局用时大致会在3-4分钟。

標图6 耗资源最多的函数

虽然之前说不用模板法,但最终还是要向模板法低头(真香!),所以我要重新写一个生成终局的函数,用模板法,由于没有内置全排列函数,只能自己写递归来生成,由上面可知递归很耗时间,所以我们要尽可能少的进行递归调用,由之前的计算结果,一个模板,可以经过行变换(第一行不动),生成72个不同的终局,所以我们最多会需要用到1000000/72+1=13889个全排列种子,而不必生成全部全排列(8!=40320个),大大降低了递归的使用。

更改这一部分代码进行了部分重构以及修改,相对麻烦,大概耗费了我3个小时的时间。

以下是递归生成全排列的函数:

public static int[,] pailie = new int[13890, 8];//存入所有生成的排列
public static int row = 0;//记录排列的行数
static int count = 0;//记录生成了多少个排列,便于跳出递归//nums为需要排列的数组,start为开始位置,length为数组长度,amount为生成排列的数量public static void Permutation(int[] nums, int start, int length, int amount){int i;if (count > amount)return;else{if (start < length - 1){Permutation(nums, start + 1, length, amount);if (count > amount)return;for (i = start + 1; i < length; i++){Swap(nums, start, i);//Permutation(nums, start + 1, length, amount);//if (count > amount)return;Swap(nums, start, i);}}else{count++;for (int j = 0; j < nums.Length; j++){pailie[row, j] = nums[j];}row++;}}}​

预处理函数:

​
public static void Pretreat(int amount)//amount为需要生成终局的数目{int preamount = amount / 72 + 1;int[] nums = new int[] { 1, 2, 3, 4, 5, 6, 7, 8 };Permutation(nums, 0, nums.Length, preamount);}​

模板法生成终局的函数写在了Sudoku类中,在网上找到了以下模板进行使用:

图6 数独终局模板

代码如下:

public void GenerateSudokuEnding2(int row)//row为Program.pailie数组的全排列序号{char[,] model = new char[9, 9] {{ 'i','g','h','c','a','b','f','d','e' },{ 'c','a','b','f','d','e','i','g','h' },{ 'f','d','e','i','g','h','c','a','b' },{ 'g','h','i','a','b','c','d','e','f' },{ 'a','b','c','d','e','f','g','h','i' },{ 'd','e','f','g','h','i','a','b','c' },{ 'h','i','g','b','c','a','e','f','d' },{ 'b','c','a','e','f','d','h','i','g' },{ 'e','f','d','h','i','g','b','c','a' }};//模板for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){if (model[i, j] == 'i') shudu[i, j] = 9;else if (model[i, j] == 'g') shudu[i, j] = Program.pailie[row, 0];else if (model[i, j] == 'h') shudu[i, j] = Program.pailie[row, 1];else if (model[i, j] == 'c') shudu[i, j] = Program.pailie[row, 2];else if (model[i, j] == 'a') shudu[i, j] = Program.pailie[row, 3];else if (model[i, j] == 'b') shudu[i, j] = Program.pailie[row, 4];else if (model[i, j] == 'f') shudu[i, j] = Program.pailie[row, 5];else if (model[i, j] == 'd') shudu[i, j] = Program.pailie[row, 6];else if (model[i, j] == 'e') shudu[i, j] = Program.pailie[row, 7];}//映射数字}}

交换的话,写了三个大循环,由外到里分别执行2,6,6次(共2*6*6),里面两层第0次什么也不做,第奇数次(1,3,5)交换三行中的后两行,非零偶数次交换三行中的前两行,为了不破坏数独的结构,交换时我们另开一个数组来记录所有行的顺序,然后按行的顺序来输出而不改变数独矩阵的结构

​
//该函数直接调用了输出函数。amount为需要生成的终局个数。
static void GeneAndTransAndOut(int amount,Sudoku sudoku,int[] order,StreamWriter streamWriter){for (int i = 0; i < row && amount > 0; i++){sudoku.GenerateSudokuEnding2(i);for (int p = 0; p < 2 && amount > 0; p++)//对1,2行的交换(0行不动){if (p == 0){}else{Swap(order, 1, 2);}for (int j = 0; j < 6 && amount > 0; j++)//对3,4,5行的交换{if (j == 0){}else if (j % 2 == 1){Swap(order, 4, 5);}else{Swap(order, 3, 4);}for (int k = 0; k < 6 && amount > 0; k++)//对6,7,8行的交换{if (k == 0){}else if (k % 2 == 1){Swap(order, 7, 8);}else{Swap(order, 6, 7);}OutputToTxt(sudoku.Shudu, order, streamWriter);//输出amount--;}}}}}​

由于改变了输出方式,我们的输出函数也随之改变了传入参数:

static void OutputToTxt(int[,] juzhen, int[] order, StreamWriter streamWriter){for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){if (j < 8){streamWriter.Write(juzhen[order[i], j]);streamWriter.Write(" ");}else{streamWriter.WriteLine(juzhen[order[i], j]);}}}streamWriter.WriteLine();}

这样,我们重新用代码分析工具来分析一下:

图7 优化后的性能分析图

发现大概需要10秒就可以生成1e6的不重复终局!分析后发现,生成终局的代码资源占用较少,可以看到频繁的读写,占用的时间多。

图8 优化后的耗资源的函数

对于读写的优化暂不做处理,但从分钟到秒的计量单位变换,足以说明了优化很成功!

单元测试(对应文档TesCase.xlsx)

Sudoku类内测试

VS内部可直接生成测试类,对于测试类测试用例的设计,每个函数都只生成一个矩阵来检验是否生成正确,由于一部分调用函数和变量写在了类外,测试只能测试Sudoku类内部函数,所以测试类中的测试数据会很奇怪。比如GenerateSudokuEnding2类直接调用的话Program.pailie数组中所有元素都为0,所以测试用例要这样设计:

[TestMethod()]public void GenerateSudokuEnding2Test(){Sudoku sudoku = new Sudoku();sudoku.GenerateSudokuEnding2(0);int[,] ending = new int[9, 9] {{ 9,0,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,9,0,0 },{ 0,0,0,9,0,0,0,0,0 },{ 0,0,9,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,0,9 },{ 0,0,0,0,0,9,0,0,0 },{ 0,9,0,0,0,0,0,0,0 },{ 0,0,0,0,0,0,0,9,0 },{ 0,0,0,0,9,0,0,0,0 }};for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){Assert.AreEqual(ending[i,j], sudoku.Shudu[i, j]);}}}

初始化和构造函数的设计相同,即除了第一个数字是9之外,其余的数字均为0。

求解数独函数也是求解一个第一个数字为9,其余的全是空白的数独,由于是回溯法,所以求解结果是固定的。

[TestMethod()]public void SolveSudokuTest(){Sudoku sudoku = new Sudoku();sudoku.SolveSudoku(0);int[,] ending = new int[9, 9] {{ 9,1,2,3,4,5,6,7,8 },{ 3,4,5,6,7,8,1,2,9 },{ 6,7,8,1,2,9,3,4,5 },{ 1,2,3,4,5,6,8,9,7 },{ 4,5,6,8,9,7,2,1,3 },{ 7,8,9,2,1,3,4,5,6 },{ 2,3,7,5,6,1,9,8,4 },{ 5,6,1,9,8,4,7,3,2 },{ 8,9,4,7,3,2,5,6,1 }}; for (int i = 0; i < 9; i++){for (int j = 0; j < 9; j++){Assert.AreEqual(ending[i, j], sudoku.Shudu[i, j]);}}}

最后运行结果:(没有找到在哪里看覆盖率)

图9 单元测试结果

测试成功。

主函数测试

主函数的测试,由于读入控制台指令的工作在主函数中完成,所以就只能手动在控制台测试,列了一张测试用例表如下:

(从左到右依次是 模块,序号,输入,说明,预期输出,是否测试成功)

图10 主函数测试用例

达到了预期目标。

代码质量分析

此环节选择了StyleCop.Analyzers,对代码风格进行了分析修正。

屏蔽了SA1652规则(与XML有关),最终修正后的代码编译无警告信息。

图10 编译后无警告

总结

终于赶完了这个大作业,学期内应该是做不了UI了,有一些遗憾,不过假期应该会继续把UI部分做出来的。

总的来说,自己还有很多东西要学,设计和编码的能力也都需要提高,整个开发过程都是自己一边查资料一边学习做出来的,感觉做成一个完整的项目实属不易。

总结了一下自己还有许多不足:

  1. 当初计划过于笼统,导致时间安排不是很正确,效率不是很高。
  2. 概要设计的时候把整个系统想的过于简单,实际实现的过程中发现,有许多细节是之前没有考虑到的。比如开始设计的随机函数在短时间内无法随机的问题。
  3. 虽然用的是面向对象语言,但是实际写代码时,封装的不是很好,类内类外的函数互相随便调用,导致耦合性太强,单元测试的时候无法好好测试。
  4. 主函数负担过多,可以进一步重构,多弄几个函数增加复用性,不过没时间了。

自己也收获了很多:

  1. 学会了如何简单地使用代码分析工具,性能分析工具和单元测试工具。
  2. 积累了一点开发软件的经验,对之后的开发心里有了一些B数。
  3. 学会了更认真的思考,如何思考如何解决问题。
  4. 对编程语言更加熟悉了一些。

psp表格如下:(不知道为啥表格粘贴失败,只好在excel里截图发了)

图11 PSP表格

学无止境,还是要多多加油!

数独生成求解器——软件工程2018年个人作业项目(完成)相关推荐

  1. 2022北航敏捷软件工程 第一次博客作业

    2022北航敏捷软件工程 第一次博客作业 项目 内容 这个作业属于哪个课程 2022年北航敏捷软件工程 这个作业的要求在哪里 个人阅读作业-阅读和调研 我在这个课程的目标是 了解软件工程的方法论.在实 ...

  2. 软件工程基础个人项目——数独终局生成求解

    目录 1.源代码的GitHub链接: 2.PSP表格(预估): 3.题目要求: 4.解题思路: 1)数独游戏规则 2)生成数独终局 2)求解数独 5.设计实现过程: 第一部分:sudoku类的构建 第 ...

  3. C++数独求解器与生成器

    前几天笔者外出培训,刚刚学习了深度优先搜索,突然想到了数独的求解其实也可以用深搜实现,遂写了数独求解器与生成器. 1 数独求解器 1.1 预备 一开始,当然是头文件~ #include <ios ...

  4. 好用的z3数独求解器

    github 上发现一个好用 用z3 编写的数独求解器 传送门: https://github.com/dferri/z3-skyscrapers Generate a skyscrapers puz ...

  5. Python Z3约束求解器解决数独问题

    Z3是由Microsoft Research开发的高性能定理证明器.接下来将使用Python3中的Z3库来实现对数独问题的解决. 关于Python中Z3的使用入门,可以参考这篇博文https://bl ...

  6. java课程 数独 文库_一次数独生成及求解方案的剖析(Java实现)

    数独生成及求解方案剖析(Java实现) 关键词 数独9x9 数独生成 数独解题 序言 最近业务在巩固Java基础,编写了一个基于JavaFX的数独小游戏(随后放链接).写到核心部分发现平时玩的数独这个 ...

  7. MATLAB 自动数独求解器(导入图片自动求解)

    做了一个导入图片自动求解数独的软件,不过由于目前是通过最小二乘法匹配数字的,所以导入图片中的数字最好不要是手写的..,图片大概就像这样: 使用效果: 完整代码: function sudokuApp ...

  8. 编程之美之数独求解器的C++实现方法

    编程之美的第一章的第15节,讲的是构造数独,一开始拿到这个问题的确没有思路, 不过看了书中的介绍之后, 发现原来这个的求解思路和N皇后问题是一致的, 但是不知道为啥,反正一开始确实没有想到这个回溯法, ...

  9. JAX-FLUIDS:可压缩两相流的完全可微高阶计算流体动力学求解器

    原文来自微信公众号"编程语言Lab":论文精读 | JAX-FLUIDS:可压缩两相流的完全可微高阶计算流体动力学求解器 搜索关注"编程语言Lab"公众号(HW ...

最新文章

  1. Java字符串String相关2
  2. 即将从TechReady5归来
  3. 使用org.apache.commons.io.FileUtils,IOUtils工具类操作文件
  4. 图解 继电器模组接线图_中间继电器如何接线_中间继电器接线图图解
  5. 二进制转换 html,javascript 处理回传的二进制图像并显示在html上
  6. 【信息系统项目管理师】第3章-项目立项管理 知识点详细整理
  7. java 内置注解入门
  8. 《代码大全》阅读笔记01
  9. 生儿子的绝妙方法汇总,对程序猿特管用!!!
  10. 后现代婚礼机器人显神通_机器人+无人机 江西新余智能消防显“神通”
  11. 哒哒的马蹄,由心而生的感情
  12. catia中的螺旋伞齿轮画法,基于CATIA的螺旋锥齿轮参数化建模方法研究
  13. FPGA——按键消抖
  14. 发出警报声的c语言程序,1、编写一个函数能够发出警报声并打印HelloWorld!;
  15. python网课 知乎_如何看待风变编程的 Python 网课
  16. ISA防火墙简单安装配置实例
  17. 悦诗风吟网络营销的目标_悦诗风吟产品网络营销推广策划方案
  18. SCI、EI、ISTP、ISR简介
  19. aspose html转换pdf,aspose html转pdf java
  20. awd的批量脚本 pwn_北极星杯 awd复现

热门文章

  1. linux如何运行sshpass,Linux中sshpass命令起什么作用呢?
  2. 真正的学懂结型场效应管入门篇(经典)
  3. php集成环境用哪个,常用的php集成环境有哪些
  4. SLEEP:识别创伤后应激障碍病人睡眠的可重复的高密度EEG标识物
  5. Android 7.0 状态栏显示运营商名称
  6. 2022-2028全球无人机探测雷达行业调研及趋势分析报告
  7. 限制性与非限制性定语从句区别
  8. 10oa协同办公系统如何在大数据时代脱颖而出
  9. 人工智能之百度“神灯搜索”
  10. 印度之行-1:从抢先看《复联3》开始