个人项目作业

1.github项目地址

https://github.com/easylliu/gitlearning/tree/master/Sudoku

2.开发时间

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

3.解题思路描述

(1).数独终局生成

开始在看到题目时想到的是通过初始点一直往后延伸推导,后来想了一会感觉有点复杂;在思考无果后通过上网查找资料了解到生成终局的两种方法
随机法,矩阵转换法;但相比之下感觉矩阵转换法更加直接,随机法不确定性加大了难度;而由于第一个数已经确定为9,因此第一行第一列不能参与变换,这样通过一个数独变换能产生共计5184种;但我们的要求最多有1000000种,因此想产生200个种子数独,从8个数中取出四位,共有70种取法,而每种取法又对应三种数字交换方式共计210种符合要求,这样产生的数独数就已经超过1000000种符合要求了。

(2).求解数独

在求解数独上,开始自己思考是想通过那些已确定的那些数字出发,沿着他们所在行列块进行推导,但随后感觉有点困难;通过网上查找感觉方法差不多,相对较为直接的就是回溯法求解了,从最开始点一直递归遍历找到所有存在的可能性,在通过行列和3*3块的验证来确定是否符合条件,简单粗暴,在实现上较为简单,初始计划打算就使用了这种方法。

4.设计实现过程

(1).数独终局生成

根据思路想到,首先产生的一个种子出发,首先通过其行列变换最多产生5184个数独,如果还不足的话从那70种取法中随机选出先交换数字产生新的种子数独,然后再行列变换,如此种种,在一定程度上还可以保证随机性,直到产生够所要的数独数即可结束。通过这种思路构建函数也就清晰起来,在数独产生的函数中通过种子数独不断调用矩阵变换函数,然后可将矩阵变换函数拆解成行变换函数和列变换函数,如此这样,函数之间的联系清晰明朗,实现起来目标也很明确。总得来说在这一部分产生4个函数,其直接的关系也是层层调用。通过这样的分析对函数的作用了解也清晰许多,通过他们直接的联系和他们的用途设计单元测试就比较轻松了。

(2).求解数独

通过暴力解法,对从0到81的所有空白进行1到9的选择,然后判断适合符合行列和块的要求,然后递归往下延伸,当后面推不出来递归返回继续延伸推导,直到求解出正确的结果结束。
相对来说这种接法也相对较为直接,从0到81,先填入数字,在判断是否符合行列和块的要求,然后递归向后延伸。如此这样分析,相对复杂点的就是递归的使用,想都来说3个函数足以。主函数调用填空递归函数,递归函数在填入数字后调用检查函数判断是否符合行列块的要求,如此实现。然后相对来说,对递归的单元测试也相对有些麻烦,通过从对函数功能的理解出发,结合和其他函数的联系设计单元测试会更加全面。

5.性能改进

改进:由于产生数独和求解数独的最大值已到达1000000,在输出到文件和读取时的效率问题就显得尤为重要了,因此在这方面花费时间能减就减,因此我就从每次写入到文件的数量上做出了改进,再往文件里写入数独时,我改进到每次写入两百个数独,这样减少写入的次数,提高效率。
由图片中可以看到,调用次数最多的为检查行列块的合理性函数,这也是暴力求解产生的负面效果,其次在面对文件操作时话费的时间较多,仍待改善。

6.代码说明

主要分做两个部分,一个是产生终局数独,一个是求解数独。
这四个为生成终局数独的四个关键函数:
主要就是sudo_create产生种数独通过调用矩阵变换得到扩展的数独,矩阵变换由行变换和列变换组成;

    void sudo_create(int sudo_count, char sudo[9][10], char changenum_array[70][5]);		//生成数独
    int matrix_change(int count, int sudo_count, char sudo[9][10], char output[1000000]);	//矩阵变换
    void line_range(int init, int n, char mid_sudo[9][10], char sudo[9][10]);				//行交换
    void column_range(int init, int n, char mid_sudo[9][10], char sudo[9][10]);				//列变换

其中最为关键一个:产生种子,调用扩展

void sudo_create(int sudo_count, char sudo[9][10], char changenum_array[70][5]) {
	int index_changenum[70] = { 0 };	//是否随机到标识符	
	char sudo_middle[9][10];				//中间种子数独
	int randnum = 0, i, j;
	int count = 0;							//已生成的数独数
	char a, b, c, d;
	for (i = 0; i<9; i++) {									//初始化
		strcpy_s(sudo_middle[i], sudo[i]);
	}
	count = matrix_change(count, sudo_count, sudo_middle, output);	//根据一个种子转换输出
	while (count < sudo_count) {
		if (count == -1) break;
		randnum = rand() % 70 + 0;
		if (index_changenum[randnum] == 0) {				//未随机到
			a = changenum_array[randnum][0];
			b = changenum_array[randnum][1];
			c = changenum_array[randnum][2];
			d = changenum_array[randnum][3];
			for (i = 0; i < 9; i++) {
				for (j = 0; j < 9; j++) {
					if (sudo[i][j] == a) {
						sudo_middle[i][j] = b;
					}
					else if (sudo[i][j] == b) {
						sudo_middle[i][j] = a;
					}
					else if (sudo[i][j] == c) {
						sudo_middle[i][j] = d;
					}
					else if (sudo[i][j] == d) {
						sudo_middle[i][j] = c;
					}
					else
						sudo_middle[i][j] = sudo[i][j];
				}
			}
			count = matrix_change(count, sudo_count, sudo_middle, output);
			if (count == -1) break;

			for (i = 0; i < 9; i++) {
				for (j = 0; j < 9; j++) {
					if (sudo[i][j] == a) {
						sudo_middle[i][j] = c;
					}
					else if (sudo[i][j] == b) {
						sudo_middle[i][j] = d;
					}
					else if (sudo[i][j] == c) {
						sudo_middle[i][j] = a;
					}
					else if (sudo[i][j] == d) {
						sudo_middle[i][j] = b;
					}
					else
						sudo_middle[i][j] = sudo[i][j];
				}
			}
			count = matrix_change(count, sudo_count, sudo_middle, output);
			if (count == -1) break;
			for (i = 0; i < 9; i++) {
				for (j = 0; j < 9; j++) {
					if (sudo[i][j] == a) {
						sudo_middle[i][j] = d;
					}
					else if (sudo[i][j] == b) {
						sudo_middle[i][j] = c;
					}
					else if (sudo[i][j] == c) {
						sudo_middle[i][j] = b;
					}
					else if (sudo[i][j] == d) {
						sudo_middle[i][j] = a;
					}
					else
						sudo_middle[i][j] = sudo[i][j];
				}
			}
			count = matrix_change(count, sudo_count, sudo_middle, output);
			if (count == -1) break;
		}
	}
	//cout << output;
}

这三个为求解数独的主要函数:
主要就是solve_sudo函数调用填空进行递归,填入一个数后就判断其合理性

int solve_sudo(char output[], int count);//求解数独
bool solve_blank(int sudo_num);//填空
bool matrix_judge(int line, int column, int choose);//判断合理性

关键一个函数:递归调用,逐步展开

//填数
bool solve_blank(int sudo_num) {
	int line, column, i;
	line = sudo_num / 9;
	column = sudo_num % 9;
	if (sudo_num >= 81)
		return true;
	if (sudo1[line][column] == 0) {
		for (i = 1; i <= 9; i++) {
			sudo1[line][column] = i;
			if (matrix_judge(line, column, i)) {
				if (solve_blank(sudo_num + 1)) return true;
			}
			sudo1[line][column] = 0;
		}
	}
	else {
		return solve_blank(sudo_num + 1);
	}
	return false;
}
posted @ 2017-09-26 18:20  修炼爱情  阅读(233)  评论(2)    收藏  举报