第二次作业——个人项目实战

本次作业的项目地址:Github仓库地址

解题思路

嗯,这次的作业要求是

利用程序随机构造出N个已解答的数独棋盘

刚开始看到的时候可以说是非常茫然的,数独以前虽然玩过,但要用程序来构造那就是完全的另一回事了。一般来说,数独的构造是你根据已有的条件,来判断剩下的空格该填什么数。我自己对这个没什么研究,平时玩的时候挺随心所欲的,这个直接导致我在面对如何用程序来解答的时候毫无头绪。何况这次的要求根本就连条件都没有,要自己凭空造。

凭空,还要随机,只有第一位数是指定的,完全没有方向,所以我就先去网上搜了“如何构建数独”,发现不少前人对此都有研究,我在其中找到了两篇对我来说很有用的,如下:
[http://blog.csdn.net/qq_31558353/article/details/50615760]
[http://blog.csdn.net/xiahn1a/article/details/50852849]

第一篇记录了该如何去解一个数独。这次作业对数独棋盘的要求只有一个,就是左上角第一个数字为(学号后两位相加)%9+1,我的学号末两位为16,也就是说我的矩阵左上角那位应为8
该篇的具体做法大致是说,从第一位开始逐个填数,如果已经有数字了就跳过,如果是空的,就从1开始看能不能填入,判断基准是该位所在的行列和小九宫里是否有跟这个数重复的数,如果没有,就填入这个数,如果有,就跳到下一个数判断能不能填,以此类推。
这种做法很容易遇上某一格一个数都填不进,这个时候就返回到它的前一格,重新走下一个数判断,直到全部填满。
这篇代码的作者非常厉害,仅用了一个n就可以表示数独矩阵每一位的坐标。我因为一开始是用num[i][j]的方法在做,不想改,本身也不能完全照他的走,所以就化用了他的方法。

但即便这样也仅仅是能构建一个以8为头的数独阵,并不能做到随机。这个时候我看到了第二篇博文。这位作者的代码有点繁杂,用到的陌生函数太多我一时看不来,但最开头部分的他的思路给了我非常大的提示。

首先第一行肯定是1~9的一种排列,直接使用shuffle进行随机。

当时看到这句的时候简直是茅塞顿开。数独阵这么多,用第一行随机可不就是能制造大批的不一样的数独嘛!然而很惭愧我不会使用shuffle,而且经过查找发现这个似乎是要在1~9里随机排列。
说到这个就要提老师的那个固定了第一位的要求了,我的首位是8,也就是说剩下8个数得在1~7和9之中随机排列,这就很不一样了,因为我在查“该如何生成一组随机数”的时候,基本上都是要在一个固定范围里随机的,并不能像我希望的那样可以自己指定数字。所以必须要想另外的办法,使我可以随心所欲的用指定的那些数随机排序。

于是我就查找到了第三篇对我来说非常重要的博文!如下:
[http://blog.csdn.net/cxllyg/article/details/7986352]
洗牌算法,我只要自己指定好数组,就可以将[0]位后面的八个数随机排列了。因此我定义了一个数组,内容是{8,1,2,3,4,5,6,7,9},随机的时候将首位除外,剩下的随机排。这也是我第一次认识rand和srand函数。

这些基本就是我的代码的主体构成了。先是在第一个数为8的基础下把1~9随机排序,然后根据已有的第一行推出剩下72个空位应该填什么。

老实说这份作业做得很跌宕起伏,经常是我前一个问题还没理清楚后脚又冒出了另一个问题。命令行输入和查重都是最后一天才发现自己遗漏的,可以说是非常不小心非常不应该的了。何况我最后一天还要返校……命令行还好解决,查重真的就是太困难。一开始做的时候没考虑到,后面想加也很有难度。把全部随机出来的第一排存在同一个数组里,新随机一个就从头开始一个个查有没有重复,数量少还能应付,但多了似乎就会运转不来。之后应该会试着尝试有没有什么更好的方法吧。

设计实现

我一共在头文件里定义了五个函数和一个全局变量,分别是:

bool sign=false;		//用以判断数独阵是否完成 
extern int num2[1000000][9];	//定义一个用于查重的数组

void firstrank(int num[][9],int n,int sum[][9]);		//随机取数独矩阵第一排的值
bool check(int n,int i,int j,int num[][9]);		//判定数字n能否放入num[i][j]中
int build(int num[][9],int x,int y);		//构建数独矩阵
void change();			//重置sign的值为false
bool diffrent(int a[][9],int b[][9],int n);		//比较a,b两个二维数组是否不相同

Main函数在生成一个数独棋盘之前,首先要先运行firstrank,定好第一行数的排列顺序。
然后要运行diffrent来判断是否有重复。每个新生成的一排随机数都要同num2里存放前几轮随机过的数进行比较。一行一行比,每行一遇到不同就跳转下一行继续比较,如果相同,则判断是否是该行最后一个数,如果是,则说明有完全相同的一行,返回false,如果全部比较完都没有重复,那就返回true。
于是就可以开始运行build,从第二行首位开始构建数独。Build中会用到check来判定这个数能否被放进这个位置里,如果check返回的值为true,就下一位;返回的值若为false,就换一个数继续判定。当所有空位都被填上数字时,build里下一个位置就是第十行第一列,这个时候sign会被置为true,build直接return 0,表明数独已经构建完成,可以等待输出。
函数change是为了重复构建函数而备的,因为sign并非是在main函数里所定义,所以为了能够重复使用,在一个数独开始构建前,要先运行change来确保sign的值确实为false。
输出部分因为涉及写入文本文件,所以我直接放在main函数里了,没有另外定义。
输入部分要求命令行输入,还要判断输入的值是否正确,我设定的是输入错误就会提示“输入错误,请重试”。

代码说明

首先是firstrank的第一行随机排序,代码如下

	int a[9]={8,1,2,3,4,5,6,7,9};		//定义一个数组,第一个数为我的学号末两位16经计算后得出的固定值8
	int ran,temp;
	
	for(int i=2;i<9;i++)	//数组除了第一个数固定为8,其余重组 
	{
		ran=rand()%(9-i)+i;
		
		temp=a[i-1];
		a[i-1]=a[ran];
		a[ran]=temp;		
	}

check从行,列,小九宫的角度判断是否能够赋值,如下:

	for(a=0;a<9;a++)		//判断第i行是否有与n重复的数字 
	{
		if(num[i][a]==n) return false;
	}
	
	for(b=0;b<9;b++)		//判断第j列是否有与n重复的数字 
	{
		if(num[b][j]==n) return false;
	}
	
	if(i<3) x=0;		//num[i][j]所在的小九宫左上角的行 
	else if(i>5) x=6;
	else x=3;
	
	if(j<3) y=0;		//num[i][j]所在的小九宫左上角的列 
	else if(j>5) y=6;
	else y=3;
	
	for(a=x;a<x+3;a++)		//判断该九宫中是否有与n重复的数字 
	{
		for(b=y;b<y+3;b++)
		{
			if(num[a][b]==n) return false;
		}
	}

build从第二行第一列开始为一个一个空位赋值,全部完成后将sign的值标为true,表示数独构造成功。

	if(x==9)		//当全部填满时,数独矩阵完成 
	{
		sign=true;
		return 0;
	}

	for(k=1;k<=9;k++)		//从1开始逐个测试是否能放入num[x][y]的位置中 
	{
		if(check(k,x,y,num)==true)
		{
			num[x][y]=k;
			if(y<8) build(num,x,y+1);		//该位不是这一行的最末位,则继续对其下一位进行置数 
			else build(num,x+1,0); 		//该位已经是这一行的最末位,跳至下一行首位进行置数 
			
			if(sign==true) 	return 0;		//当数独阵完成时,直接返回 
		 
			num[x][y]=0;		//如果置数失败,则将这位置0,继续寻找下一个适合的数字 
		} 
	}

Main函数先要确定随机排序的随机种子和数独最后要输出的文件“sudoku.txt”,然后根据在命令行输入的n来构造数独,如果输入的不属于规定范围内,还要给出错误提示。

n=atoi(argv[argc - 1]);		//命令行输入
	srand((unsigned)time(NULL));		//随机种子 
	ofstream outFile;			//输出文件“sudoku.txt” 
	outFile.open("sudoku.txt");
	if(n<1000000 && n>1)			//判断输入的变量是否为int型 
	{
		for(i=0;i<n;i++)
		{
			change(); 
			int num1[9][9]={0};
			firstrank(num1,i,num2);			//确定第一行的排序
			if(diffrent(num1,num2,i)==1)		//判断是否重复
			{
				build(num1,1,0);		//开始构建数独
				for(j=0;j<9;j++)		//输出数独阵到文件“sudoku.txt”中 
				{
					for(k=0;k<9;k++)
					{
						outFile << num1[j][k] << " ";
					}
					outFile << endl;
				}
				outFile << endl;		//每个数独阵间隔一个空行 
			}
			else i=i-1;
		}
	}
	else
	{
		cout << "输入错误,请重试。" << endl;		//若变量不为int型,则输出错误提示 
	}
	outFile.close(); 

测试运行

截图如下:

性能分析

执行力,泛泛而谈的理解

执行力,按百科上的说法就是“有效利用资源、保质保量达成目标的能力”,也就是说利用现有的条件和知识,通过自己的力量又快又好的完成需要完成的任务。而泛泛而谈,指浮于表面,没有深入研究,一个人讲出来的东西很肤浅毫无思考力,大概就是泛泛而谈了。
按我个人的理解,拿这次作业作比,如果一个人在看到作业后立刻开始思考入手,然后在deadline来临前从容而高水平的完成了它,那就说明他有很强的执行力了。
泛泛而谈嘛,比如说,我上面提及我要换一种更稳妥的方法来查重复,不用事先设一个那么庞大的数组,我说了,却没有去做,并且在想起时不断强调以后会去做,那就是一种很浅薄的表现。
……老实说,我觉得我现在在这里谈论这些,谈论代码的言辞就挺肤浅挺没有见识的……

遇到的困难及解决方法

关于代码思路之中的困难上面已经提过了,现在说的都是一些细节上但是又很恼人的问题。

一个是命令行输入,今天最后了才发现的,int main(int argc ,char *argv[]),乍一看很莫名其妙,好在我舍友为我倾情指点了一下,可以简单进行使用了。
下一个是输出到txt。这个我查了我的一本讲C++的书,按照书上的说法这个创建的文档一般会跟可执行文件在一起。于是我就照着书上的做法直接用了。我比较喜欢在编代码的时候一个一个功能加,所以在建txt之前我都是用cout直接进行输出的。先前的时候是另设了一个output函数来输出,后来为了用outFile又改成直接放在main函数里。
最困难的还是没有办法查重复。我如果要改的话前面firstrank()就要跟着变,时间不足,所以就直接建数组。数组的话必须视线声明大小,[1000000][9]太大了,我一开始在main函数里建的,后来发现没办法执行,又改成全局变量。这下不会报错了,但我自己试了下,我的电脑最多只能随机出5000个左右的数独阵,如果输入6000,就会一直停留在运行,不能结束。我猜测是电脑容量的问题。

PSP表格

PSP2.1 Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
Planning 计划 10 30
· Estimate · 估计这个任务需要多少时间 10 30
Development 开发 620 890
· Analysis · 需求分析 (包括学习新技术) 60 50
· Design Spec · 生成设计文档 0 0
· Design Review · 设计复审 (和同事审核设计文档) 0 0
· Coding Standard · 代码规范 (为目前的开发制定合适的规范) 20 0
· Design · 具体设计 30 60
· Coding · 具体编码 240 300
· Code Review · 代码复审 30 60
· Test · 测试(自我测试,修改代码,提交修改) 240 420
Reporting 报告 90 90
· Test Report · 测试报告 60 70
· Size Measurement · 计算工作量 10 5
· Postmortem & Process Improvement Plan · 事后总结, 并提出过程改进计划 20 15
合计 870 1010
posted on 2017-09-10 22:49  inblue  阅读(207)  评论(5编辑  收藏  举报