软件工程实践2017第二次作业

0、欢迎食用


1、解题过程

  • 第一版

    • 看到题目第一眼的想法就是和做数独一样按规律填。最开始想的是一个数字一个数字进行符合要求的随机填空,思考了好一会觉得实现起来有点点复杂,没什么好的想法。然后就上网搜了一下各种数独生成的算法,找到看起来一个比较好实现的算法(实际上也很好实现hhh)。参考了里面的随机方式的思路:
    • 写一个方法用于获取一个由1到9九个数随机排列的一维数组。
    • 循环行(下标从0到8),将这个随机产生的一维数组作为当前行的内容,如果是第一行(行标为0),那么直接作为该行的内容。如果是其它行,则验证数据是否都符合条件。
    • 如果符合条件,则再产生一个由1到9九个数随机排列的一维数组作为下一行的内容并验证数据是否可用。如果不符合条件,则……
    • (其实我只看了第一行233)后面的做法就跟上面的不一样啦。
  • 第二版

    • 第一版改不下去,跟可靠人士交流后决定转投DFS回溯的怀抱。第一遍通过 DFS 生成一个数独终盘,之后的就通过回溯来生成。
      主要想法就是一个个轮呗 0 0 对每个格子,判断1-9填入后是否符合数独要求。若符合要求则填入该数字并进行下一步搜索,不符合要求则换一个数字进行尝试。

2、设计实现

  • 第一版

  • 第二版

    和第一版还是有些差别的。换了个生成类,由于算法的关系,改成了直接在生成类里面调用打印类。


3、关键代码

  • 第一版初稿

    写的时候就能猜想到,整个项目的时间都耗在这里辣。不过先 make it work 咯。第一遍写除了一些知识点有点不清楚改了一小会,不考虑这些的话,还是全部一次性写完一次性成功的。非常满足。
    主要想法就是生成一行1-9的随机排列,然后进行对应的验证,符合要求则拼接起来,不符合要求就重新生成直至找到满足要求的随机数列。
void SudokuBuilder::generateSudoku(int(&sudoku)[LENGTH][LENGTH]) {

	initSudoku(sudoku);

	SudokuJudger sudokuJuder;

	// 第一行直接生成无须判断,故可直接拷贝。
	memcpy(sudoku[0], randomArray(0), sizeof(int) * LENGTH); 

	for (int i = 1; i < LENGTH; i++) {
		// 随机生成一行
		int* temp = randomArray(i); 

		bool succeed = false;
		while (!succeed) {
			// 若随机生成的数组符合要求则继续,否则重新生成数组
			if (sudokuJuder.judgeTempRow(i, tempArray, sudoku)) {
				succeed = true;
			} else {
				tempArray = randomArray(i);
			}
		}
		memcpy(sudoku[i], temp, sizeof(int) * LENGTH);
	}
}

  • 第一版终稿

    主要是对上面函数中随机生成一行数组进行了修改。
		while (!succeed) {

			// 获取当前非法列
			int column = sudokuJuder.judgeTempRow(i, tempArray, sudoku);

			if (column == -1) {
				succeed = true;
			} else {
				// 当前非法列与未被判断的数字进行随机交换
				tempArray = randomArray(column, tempArray);

				// 若当前列修改后仍为非法列则该行重新生成
				int tempColumn = sudokuJuder.judgeTempRow(i, tempArray, sudoku);
				if (tempColumn == column) {
					tempArray = randomArray(i);
				}
			}
		}

  • 第二版初稿

    因为实在改不来第一版算法,所以,我勇敢的决定换算法 0 0 并且据可靠人士亲测,DFS回溯效果不错。太久没写过了有点虚,网上找了一下模板。这个还是归纳的蛮清楚的,对着模板写一下,还是比较快就出来了,没有自己想象的那样艰难hhh。这算法效率比起原来那个提升了可真不止一点,回溯还能够保证所有生成的数独终盘一定不重复。开心。
    目前判定条件暂时暴力搜索行+列+小矩阵,争取有时间优化。
    其中DFS函数如下:
void SudokuGenerator::dfs(int(&sudoku)[LENGTH][LENGTH], int count, int(&n)) {

	// 所要求生成终盘数已完成,可结束算法。
	if (!n) {
		return;
	}

	// 已生成一个符合要求的数独终盘,输出所得终盘。
	if (count > 80) {
		sudokuPrinter.printSudoku(sudoku);
		--n;
		return;
	}

	// 根据count值获得对应数独棋盘的坐标
	int x = count / LENGTH, y = count % LENGTH;

	// 若当前坐标已赋值则继续深搜
	if (sudoku[x][y] != INITDATA) {
		dfs(sudoku, count + 1, n);
	}

	for (int j = 1; j <= LENGTH; j++) {

		// 回溯
		if (sudokuJudger.checkRequirement(x, y, j, sudoku)) {
			sudoku[x][y] = j;
			dfs(sudoku, count + 1, n);
			if (!n) {
				return;
			}
			sudoku[x][y] = INITDATA;
		}
	}
}

  • 发现邹老师一大早翻了一大波牌233
    偷偷瞄到邹老师在别的博客下的评论dfs的易读性,写的时候就想当然觉得dfs默认深度优先搜索了 0 0
    函数名已在github上更名为DepthFirstSearch了。

4、测试运行

  • 简单玩了一下单元测试,不过还是不太清楚具体的应用 0 0 感觉像是验证性的测试。但在这个数独生成器项目中,好像没什么能测的诶 0 0 特别在我改了第二版之后貌似更没什么能用来进行单元测试的了。
  • 运行成功截图
- **update:**单元测试覆盖率

5、性能分析

  • 第一版

    • 第一版初稿:最最原始爆炸的性能。
    • 第一版终稿:
      emmmm...看起来改进效果还是比较可观的。
    • 以上都是生成一次数独终盘的性能分析。如果生成终盘数目一多,就比较不好看了...折腾了很久还是不怎么会改,都没什么好的效果。不愧是随机算法,时间真的太随机了... 而且虽然重复的可能性也不太大,但是还是有可能存在重复的。
      (之前忘记 push 到 Github 上面了,所以 Github上只有第一版的终稿 T T 后来把第一版的当作注释加进去了...啊函数有变化,算了就这样意思一下吧)
  • 第二版

    • 生成100w次的性能分析
      的确时间都花在输出上面了 0 0
    • 输出换putchar()的100w
      输出一改就可以看到判断emmmm...有时间再改吧。

6、PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 20
· Estimate · 估计这个任务需要多少时间 10 20
Development **开发 ** 840 **900 **
· Analysis · 需求分析 (包括学习新技术) 180 320
· Design Spec · 生成设计文档 - -
· Design Review · 设计复审 (和同事审核设计文档) - -
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) - -
· Design · 具体设计 60 60
· Coding · 具体编码 180 80
· Code Review · 代码复审 60 60
· Test · 测试(自我测试,修改代码,提交修改) 300 320
Reporting 报告 60 90
· Test Report · 测试报告 - -
· Size Measurement · 计算工作量 - -
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 60 90
合计 910 1010
  • 做的时候就直接做了,没注意上面这些东西 0 0 都是大概估一下吧。下次会注意要求的 orz

7、参考资料

posted @ 2017-09-10 19:08  H_BING  阅读(197)  评论(2编辑  收藏  举报