软件工程实践2019第三次作业

1.Github项目地址 :

https://github.com/DFHG10/031702429

2.PSP表格

PSP是卡耐基梅隆大学(CMU)的专家们针对软件工程师所提出的一套模型:Personal Software Process (PSP, 个人开发流程,或称个体软件过程)。

PSP2.1 Personal Software Process Stages 预估耗时(小时) 实际耗时(小时)
Planning 计划 1h 0.5h
Estimate 估计这个任务需要多少时间 28h 26h
Development 开发 4h 2h
Analysis 需求分析 (包括学习新技术) 4.5h 5h
Design Spec 生成设计文档 2h 2h
Design Review 设计复审 2h 1h
Coding Standard 代码规范 (为目前的开发制定合适的规范) 1h 0.5h
Design 具体设计 2h 1h
Coding 具体编码 6h 8h
Code Review 代码复审 1.5h 1h
Test 测试(自我测试,修改代码,提交修改) 0.5h 1h
Reporting 报告 1.5h 2h
Test Repor 测试报告 0.5h 0.5h
Size Measurement 计算工作量 0.5h 0.5h
Postmortem & Process Improvement Plan 事后总结, 并提出过程改进计划 1h 1h
合计 28h 26h

3.思路描述:

作业刚发布的时候,看到是数独的题,就想到之前做过的八皇后问题,感觉是很类似的,是通过深搜加回溯求解,我就想着这题应该是差不多的。由于之前没怎么做过数独,特意去网站上做了几个数独。做了一圈,发现有很多的做法,网络上提供了三链数法,侯选数法,频率法多种方法。最后思来想去,还是决定用常规方法,也就是回溯(其他方法试过,不过没有成功。。。,留下泪水/(ㄒoㄒ)/~~)。然后去网络上学习vs项目生成,文件管理等等各个没用过的知识。

4.实现过程:

深搜加回溯:我们首先将数独中空缺的部分记录下来,然后依次判断1-9在各个位置上是否能填入,如果能的话则将其填入,进行下一个空缺位置的填入,如果某一个位置所有数字都不能继续填入,那么将本位置还原,回溯到上一层,将上一层所填入的数字清0,重复上述过程,直到所有位置都填入成功,生成数独的一个解。这次是说不考虑多解,所以我是解出数独的一个解就退出求解函数,进行输出。这部分有两个函数,一个判断合法性,一个进行深搜回溯的操作。

文件的输入输出:查找资料之后,决定用c的文件处理进行文件的读写。

错误整理:这部分也不知道会有什么错误,与同学交流之后,想着可以是命令行参数错误,就写了一个判断的函数,判断输入的参数是否错误,这个有待改进。

5.改进思路:

改进思路这部分,主要有两点。一是深搜算法,是从没有填写的格子逐个开始,且每个格子都是从1开始判断,但实际做数独时,对于某些格子我们可以通过同行同列以及宫格之中,已经填写的数,进行直接的判断填写,或者说消除可填写数的“可能性”,这样可以让需判断的格子减少,或者让回溯时次数减少,这样会让算法更好一些。二是对于多解的判断。一般的数独是没有多解的,但如果出现多解,要怎么去完成,这是一个问题。另外,还有一个,就是上面说的改进,也是基于深搜加回溯的基础,能不能换种思路,把平时手动求解时的那些技巧化为编码实现,目前正在尝试。这些目前还只是一些思路,尚处于试验阶段,如果有结果,再进行补充吧。

6.代码说明:

定义全局变量:

int board[10][10];//定义数独二维数组//
int num, x;//定义数独阶数x,盘面数num;

检测合法性函数:检测数字是否能否放在某个格子中

bool Check(int check_number, int check_now_row, int check_now_column,
	int check_block_row, int check_block_column)

Check函数用于判断行、列、和宫格的合法性(如果有宫格的话)

check_number:当前待判断数字

int check_now_row:待判断格子行坐标

int check_now_column:待判断格子列坐标

int check_block_row:如果有宫格,为所在宫格左上角第一格行坐标

int check_block_column:如果有宫格,为所在宫格左上角第一格行坐标

行列判断:

for (int i = 0;i <= x;i++)//检查与待检查的坐标同行或同列的位置 
	{
		if (board[check_no_row][i] == check_number || board[i][check_now_column] == check_number)
			return false;
	}

此外,有对四,六,八,九宫格进行宫格判断,这里举个九宫格的例子:

对于宫格的判断,关键是从当前坐标定位,推断出所在宫格左上角那个格子的坐标,经过纸上画图,得出结论:
左上角行坐标=当前坐标/宫格行数宫格行数
左上角列坐标=当前坐标/宫格列数
宫格列数

	if (x == 9) {
		check_block_row = (check_now_row / 3) * 3;
		check_block_column = (check_now_column / 3) * 3;
		for (int i = 0;i <= 2;i++)//检查与待检查的坐标同3*3方格的位置 
		{
			for (int j = 0;j <= 2;j++)
			{
				if (board[check_block_row + i][check_block_column + j] == check_number)
					return false;
			}
		}
	}

深搜回溯:

对每个格子进行判断是否有数字,即判断board[now_row][now_column]是否为0;

从第一个为0(即无数字)的格子开始操作,尝试填入数字1~x(x为阶数),并进行合法性分析,调用Check函数;

通过Check函数判断填入的数字是否合法,如果合法,则对下一个空格进行同样的操作;

如果从数字1到数字x均不合法,则说明上一个数填写出错,进行回溯退后上一个数,并将正在操作的格子的数字还原为0;

数独求解完成,则完成。

bool Work(int now_row, int now_column)
{
	if (now_row == x)
	{
		return true;//如果将数独解完,返回true 
	}
	else
	{
		int next_row, next_column;
		int block_row = 0, block_column = 0;
		next_column = now_column + 1;
		next_row = (next_column >= x ? now_row + 1 : now_row);//如果最后一列,换行 
		next_column = (next_column >= x ? 0 : next_column);//如果最后一行,列置为0 
		if (board[now_row][now_column] != 0)//如果当前坐标有数字,则对下一个坐标进行工作 
		{
			if (Work(next_row, next_column)) return true;//如果数独最终有解,则不断向前返回true 
		}
		else
		{
			for (int i = 1;i <= x;i++)
			{
				if (Check(i, now_row, now_column, block_row, block_column))
				{
					board[now_row][now_column] = i;//如果i值合法,则对下一个坐标进行工作
					if (Work(next_row, next_column)) return true;
				}
			}
			board[now_row][now_column] = 0;//回溯操作 
			return false;//如果i的值为1-x均不合法,则返回上一层继续循环 
		}
		return  0;
	}
}

命令行判断函数:

int canshu(int argc)//判断命令行参数是否有误
{
	if (argc != 9)
	{
		cout << "参数个数出错!";
		return 1;
	}
	if (x<3 || x>9 || num<0 )
	{
		cout << "数字出错!";
		return 1;
	}
	return 0;
}

命令行参数:

主函数main可以接收两个参数

int main(int argc, char *argv[])

  其中: argc:代表启动程序时,命令行参数个数。C/C++语言规定,可执行程序程序本身的文件名也算一个命令行参数。因此,argc的值至少是1.

      argv:是一个指针数组,里面每一个元素都是一个char* 类型的指针,该指针指向一个字符串,即指向命令行参数。如argv[0]指向第一个命令行参数,也就是可执行文件名。argv[1]指向第二个命令行参数……

本题中argv[2],argv[4],argv[6],argv[8]分别对应m,n,输入文件名,输出文件名。注意,我们接受到的参数都是字符型,需要进行转换。

x = atoi(argv[2]);
num = atoi(argv[4]);

然后是文件的读入和写出,在这里其实还好一些,看了书,之后就差不多了解了,这部分有一个问题就是文件使用方式,我先用的“w”,发现只有一个输出,

在这里我用了两个FILE变量,用fopen打开input文件,fscanf读取数据。然后用fopen打开output文件,fprintf输出数据到output,在这里我是输入一个盘面后,立马进行解答的操作,然后输出到output,然后重置数组,不知道会不会出问题,自己做的时候是行的。

FILE* fp1;
FILE* fp2;
fp1 = fopen("input.txt", "r");//只读打开文件
fp2 = fopen("output.txt", "a");// 追加打开文件		
fclose(fp2);//关闭文件,防止数据丢失
fclose(fp1//关闭文件,防止数据丢失

7.心得体会:

总结一下,这次的作业过程中,学习到了很多,Visual Studio 项目的创建,.h文件和.cpp文件的关联,Github建立库和文件的上传,VS的C4715警告的解决办法(不是所有控件都返回路径)/(ㄒoㄒ)/~~(这个处理了好久),Code Quality Analysis工具使用,性能分析工具Studio Profiling Tools(这个还是不太会用)还是学到了一些东西,但是花的时间也很多。。。继续努力吧,上次的学习计划也在慢慢步入正轨,加油。

用例测试:

VS的C4715警告:


这个主要是没有每一个都有返回值,比如:

BOOL MyClass::GetValue()
{
if(……)
 return 0;
else if(……)
      return 1;
}

上面的函数有一个很明显的漏洞:当if……else if…… 不包括所有的条件在内,也就是说有可能会出现条件不符合if(……),也不符合else if(……)的情况,这时候函数就不知道该返回什么值了。经过修改,把警告消除了。

性能分析(暂未完成,有待修改):这个整了半天,没搞明白。。。

posted @ 2019-09-25 00:51  qnfn  阅读(275)  评论(3编辑  收藏  举报