软件工程基础——个人项目(解数独)

Github项目地址:https://github.com/2016bits/sudoku.git

附加题Github地址:https://github.com/2016bits/interface_of_sudoku.git

 

一、题目描述:

实现一个能够生成数独终局并且能求解数独问题的控制台程序。

 

二、要求描述:

实现一个命令行程序:

  1. 生成不重复的数独终局至文件;
  2. 读取文件中的数独问题,求解并将结果输出到文件

生成终局:

  1. 在命令行中使用-c参数加数字N(1<=N<=1000000)控制生成数独终局的数量,例如下述命令将生成20个数独终局至文件中:
    sudoku.exe -c 20
  2. 将生成的数独终局用一个文本文件(假设名叫sudoku.txt)的形式保存起来,每次生成的txt文件需要覆盖上次的txt文件,格式如下,数与数之间由空格分开,终局与终局之间空一行,行末无空行:
    2 6 8 4 7 3 9 5 1
    3 4 1 9 6 5 2 7 8
    7 9 5 8 1 2 3 6 4
    5 7 4 6 2 1 8 3 9
    1 3 9 5 4 8 6 2 7
    8 2 6 3 9 7 4 1 5
    9 1 7 2 8 6 5 4 3
    6 8 3 1 5 4 7 9 2
    4 5 2 7 3 9 1 8 6
  3. 程序在处理命令行参数时,不仅能处理正确的参数,还能处理各种异常的情况,如:
    sudoku.exe -c abc
  4. 在生成数独矩阵时,左上角的第一个数为:(学号后两位相加)% 9 + 1。例如学生A学号后两位是80,则该数字为(8 + 0)% 9 + 1,那么生成的棋盘如下(x表示满足数独规则的任意数字):
    9 x x x x x x x x
    x x x x x x x x x
    x x x x x x x x x
    x x x x x x x x x
    x x x x x x x x x
    x x x x x x x x x
    x x x x x x x x x
    x x x x x x x x x
    x x x x x x x x x

 

求解数独:

  1. 在命令行中使用-s参数加文件名的形式求解数独,并将结果输出至文件,如:
    sudoku.exe -s absolute_path_of_puzzlefile
  2. 格式如下,其中0代表空格,题目与题目之间空一行,行末无空格,最后一个数独题目后无空行:
    9 0 8 0 6 0 1 2 4
    2 3 7 4 5 1 9 6 8
    1 4 6 0 2 0 3 5 7
    0 1 2 0 7 0 5 9 3
    0 7 3 0 1 0 4 8 2
    4 8 0 0 0 5 6 0 1
    7 0 4 5 9 0 8 1 6
    8 9 0 7 4 6 2 0 0
    3 0 5 0 8 0 7 0 9
  3. sudoku.txt的格式如下(与生成终局的要求相同):
    9 5 8 3 6 7 1 2 4
    2 3 7 4 5 1 9 6 8
    1 4 6 9 2 8 3 5 7
    6 1 2 8 7 4 5 9 3
    5 7 3 6 1 9 4 8 2
    4 8 9 2 3 5 6 7 1
    7 2 4 5 9 3 8 1 6
    8 9 1 7 4 6 2 3 5
    3 6 5 1 8 2 7 4 9
  4. 数独题目个数N(1<=N<=1000000),保证数独格式正确。

 

三、时间估计:

PSP2.1Personal Software Process Stages预估耗时实际耗时
Estimate估计这个任务需要多少时间1month15days
Analysis需求分析(包括学习新技术)7days1days
Design Spec生成设计文档3days1days
Design Review设计复核(审核设计文档)3days1days
Coding Standard代码规范(为目前的开发制定合适的规范)1days2days
Design具体设计10days1days
Code Review具体编码1days6days
Test测试3days1days
Others其他2-3days2days

 

四、解题思路:

先审题,发现该题目包括两个部分:生成终局和求解数独,这两部分可以完全分开来做,其公用部分为输入、输出和数独规则的判断,所以可以将其分为两个不同的函数来执行。首先,实现输入的部分:本题与以往题目不同的地方就在于其输入,本题是通过在终端用命令调用exe文件,同时在此过程中传递参数。经百度,了解到main(int argc, char* argv[])中的参数argc和argv就是用来接收终端传来的参数,argc表示参数的个数,argv表示参数的数组。同时,该程序的输入和输出都是在文件中进行的,百度后了解到要用头文件fstream,用ifstream和ofstream来实现输入输出。下面具体分析生成终局和求解数独的做法:

生成终局:

如果完全按照搜索来做,复杂度会相当高,所以查阅相关资料后,结合数独的相关特点,总结了以下几点:

  1. 每行生成一个1-9的排列,从而保证行里的数字不会重复;
  2. 然后将下一行进行平移(平移距离不为0),从而保证列里的数字不会重复;
  3. 前三行的平移距离为0、3、6(036或063两种情况),接下来三行平移1、4、7(147、174、417、471、714、741六种情况),最后三行平移2、5、8(258、285、528、582、825、852六种情况),从而保证了每个3*3方格里的数字不会重复,且总方案数2*6*6*8!= 2903040 > 10^6,满足题目要求。

求解数独:

暂时除了深搜没有太好的方法,所以用一个结构体(或类)保存所有空格的行、列、3*3方阵,从而一个个进行试探,如果1-9均不合适,则返回上一层,若到达最后一个数字也没有冲突,则输出该结果。

五、流程图:

无

 

六、具体实现:

总体采用了面向对象的思想:

  1. 设计了一个名为Sudo的类,实现生成终局的函数Finality和实现解数独的函数Solution作为该类的方法,以及在实现生成终局时移位的函数Movesudo和实现解数独时生成时的搜索函数Tryspace也作为该类的方法;
  2. 将输入输出的文件变量fin和fout作为该类的属性,以及解数独时的空格结构体space和空格的数量space_num和存放数独的题目数组array作为该类的属性。

在主函数里面新建对象sudo,通过命令行传来的参数来判断对象具体调用的方法。

 

七、性能分析:

  1. 最初的版本使用ofstream来将生成的终局输出到文件,在dev上生成的.exe文件生成1000000个终局需要二十多秒,在vs上需要两分半;之后将输出改成fputc之后,在dev上生成.exe文件生成1000000个终局需要四秒多,在vs上需要十三秒多。可以说,代码的性能得到了很大的提升。
  2. 最初在找到一个解后没有返回,所以速度极慢;之后加入了判断,用变量ok来标记是否全部试探完毕,之后速度快多了。

 

七、主要代码:

生成移动规则并遍历:

char rule0[10][5] = {"036", "063"};
char rule1[10][5] = {"258", "285", "528", "582", "825", "852"};
char rule2[10][5] = {"147", "174", "417", "471", "714", "741"};
int count = 0;					//记录终局个数
bool flag = true;
do {
	a[8] = '3';					//插入学号 :(2+0)% 9 + 1 = 3 
	//平移的2*6*6种方式
	for (int i = 0; i < 2 && flag; ++i) {
		for (int j = 0; j < 6 && flag; ++j) {
			for (int k = 0; k < 6 && flag; ++k) {
				Movesudo(rule0[i], rule1[j], rule2[k], a, fout);
				++count;
				if (count >= num) {
					flag = false;
					break;
				}
			}
		}
	}
} while (next_permutation(a, a + 8));			//使用STL全排列函数

根据移动规则将终局输出到文件:

//开始移位并存储在文件中
	
for (int i = 0; i < 9; ++i) {
	int step = steps[i];
	fputc(a[(8+step)%9], fout); 
	for (int j = 1; j < 17; ++j) {
		fputc(' ', fout);
		fputc(a[((16-j)/2 + step) % 9], fout);
		++j;
	}
	fputc('\n', fout);
}
fputc('\n', fout);

读取文件中的数独题目并存储:

while (!fin.eof()) {
	//读取题目
	int count = 1;					//空格的编号
	for (int i = 0; i < 9; ++i) {
		for (int j = 0; j < 9; ++j) {
			fin >> array[i][j];
			if (array[i][j] == 0) {
				space[count].row = i;
				space[count].col = j;
				space[count++].anum = (i / 3) * 3 + (j / 3);
			}
		}
	}
	num_space = count - 1;
	ok = false;
	Tryspace(1);
	fout << endl;
}

深搜:

	//逐个试探空格中的数字
	if (ok) {
		return;
	}
	if (now > num_space) {
		for (int i = 0; i < 9; ++i) {
			for (int j = 0; j < 8; ++j) {
				fout << array[i][j] << " ";
			}
			fout << array[i][8] << endl;
		}
		return;
	}
	int row = space[now].row;				//当前空格的所在行
	int col = space[now].col;				//当前空格的所在列
	int anum = space[now].anum;				//当前空格的所在方格数
	for (int i = 1; i <= 9; ++i) {
		bool flag = true;					//检测是否符合数独规则
		array[row][col] = i;						//试探该空格的值
		for (int j = 0; j < 9 && flag; ++j) {
			//检测该行是否出现重复的数字
			if (array[row][j] == i && j != col) {
				flag = false;
				break;
			}
		}
		for (int j = 0; j < 9 && flag; ++j) {
			//检测该列是否出现重复的数字
			if (array[j][col] == i && j != row) {
				flag = false;
				break;
			}
		}
		for (int j = (anum / 3) * 3; j <= (anum / 3) * 3 + 2 && flag; ++j) {
			for (int k = (anum % 3) * 3; k <= (anum % 3) * 3 + 2 && flag; ++k) {
				//检测该方格里是否出现重复的数字
				if (array[j][k] == i && (j != row || k != col)) {
					flag = false;
					break;
				}
			}
		}
		if (flag) {
			//向下一个空格试探
			Tryspace(now + 1);
		}
	}
	array[row][col] = 0;

 

总结:

通过这个数独项目的学习和制作,我学习了一些在完整软件制作过程中的一些基本技巧,学会了使用性能分析软件来指导自己对代码进行优化。而且,还借此机会学习了CSDN和GitHub的使用。帮助自己养成在开发前进行计划,安排和设计的习惯,为自己以后的软件开发打下了良好的基础。

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值