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

Github项目地址:https://github.com/Kzy-pan/git_data

项目要求

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

输入

输入需要生成的数独盘数N

输出

随机生成N个不重复的以解答完毕的数独棋盘,并输出到sudoku.txt中且左上角第一个数为(2+8)%9+1=2

遇到的困难及解决方法

困难描述:

没试过使用命令行传过参数
全部通过随机的可能性太多,产生多个盘面时间过长

做过的尝试:

上网查找传参方法
将随机的个数缩小,从全部到两个宫再到一个宫,再用行列变换增加可产生的总的盘面,以提高运行速度

是否解决:

已解决

有何收获:

要活用搜索引擎
可以通过牺牲一些东西来获得另外一些东西

解题思路以及优化改进

一开始,我去百度了下数独的基本规则,还找了个数独的在线网站并亲自填了几个,想要尝试使用随机填数,可是仔细想想,全部随机的话,可随机的总数太大了(查了下一共有10的21次方数量级),而且还要实现宫的检查。之后想到使用1-9的乱序来产生一个一个宫,再通过行列检查将它们调整后拼在一起,但是随机生成九个宫的总数还是太多了,而且检查还是很麻烦。那就只随机出第一宫,用第一宫按某种方法填充出其它宫,之后随机出第五宫,再调整其它宫。在尝试了很久后,发现无论如何,总是会出现随机出的两个宫无法拼在一起,不过已经可以通过改变行列将第一宫扩展至其它宫了。这时已经可以实现8!种(头一个数固定为2)可能的输出,少于题目要求的上限。这时突然想到了之前看到过用一个终盘通过行列变换多产生出其它终盘,这样的话再乘以8!就可以轻松达到1000000种以上。
经过以上思考,我决定先随机出第一宫,利用以宫的行列变换产生其他宫,再进行整行、整列的变换来产生终盘,可以达到8!x4x4x4x4种,而效率几乎没有变化。

关键代码or设计说明

设计说明

  • 第一步,填满整个盘面。首先将数独盘面左上角第一个数置2,在乱序1-9除了2的数,将第一宫填满。之后通过将第一宫的第二三行上升一行,第一行方至最后生成第二宫,以同样方法用第二宫生成第三宫;然后第一宫用类似方法变换列得到第四宫,再用四宫以第一宫生成二三宫的方法生成五六宫;第七八九宫以类似生成四五六宫生成。

  • 第二步,对生成的盘面利用随机数进行随机的行列变换。对调123,456,789行,不会影响正确性,但因为第一宫头个数字固定,故只变换456,789行,列同理只对456,789列进行操作。
    生成结果以第一宫为(213)(456)(789)例,并进行46行交换:

      213    456    789          213    456    789
      456    789    213          456    789    213 
      789    213    456     \    789    213    456 
      132    564    897  ----\   897    132    564
      564    897    132  ----/   564    897    132    
      897    132    564     /    132    564    897  
      321    645    978          321    645    978
      645    978    321          645    978    321 
      978    321    645          978    321    645
    
  • 第三步,对生成的结果进行重复检查。因为只需检查头一宫以及后面的行列变换,故用一个树即可解决。利用生成树时是否存在新节点来判断是否重复。若重复,则重新开始一二三步,直到输出新的终盘为止。

这样子一共需要5个函数,一个函数用来将数独终盘打入文件(print_shudo),两个函数用来分别进行行列变换(changeC,changeR),一个用来检查重复(check),最后用一个函数生成未行列变换的盘面并调用以上函数(generator_shudo)。另外需要一个函数进行乱序(ShuffleArray_Fisher_Yates)和参数检查(isNum)。

关键代码

利用第一宫填充其他宫

(仅给给出第四宫填充第五、六宫的代码,其余操作类似)

for(int i=3;i<9;i++)
{//mini储存第四宫的数字
	shudo[3][i]=mini[i/3][i%3];
	shudo[4][i]=mini[(i/3+1>2)?0:i/3+1][i%3];
	shudo[5][i]=mini[i/3-1][i%3];
}//利用mini填充第五宫,第六宫

行列变换

(仅给出行变换,列变换类似)

void changeR(char shudo[][9],char *ch)
{//对于第4-6行,第7-9行进行对调	
int jd;
for(int j=1;j<3;j++)
{
	jd=rand()%4;//利用随机数jd进行操作判断
	//jd=0不变换,1对调一二行,2对调二三行,3对调一三行	
	ch[j-1]=jd;//存入数组方便后面重复检查
	if(jd)
	{//整行整行的对调
		for(int i=0;i<9;i++)
		{
			char tmp=shudo[jd-1+j*3][i];
			shudo[jd-1+j*3][i]=shudo[(jd>2?0:jd)+j*3][i];
			shudo[(jd>2?0:jd)+j*3][i]=tmp;
		}
	}
}
}

重复检测

树节点的结构体:struct Nod{struct Nod* next[9];};

bool check(char arr[][3],struct Nod* r,char ch[4])
{
struct Nod* p=r;
bool jd=0;//判断是否重复,初始化为0
for(int i=1;i<9;i++)
{//利用第一个宫的数字建树
	int j=arr[i/3][i%3]-'0'; 
	if(p->next[j-1])
		p=p->next[j-1];//跳过已经存在的节点
	else
	{//若节点不存在,则说明不重复,将jd置1
		jd=1;
		struct Nod* q=new struct Nod;
		for(int i =0;i<9;i++)
			q->next[i]=NULL;
		p->next[j-1]=q;
		p=q;//生成后面的节点
	}
}
for(int i=0;i<4;i++)
{//利用变换方式继续建树
	int j=ch[i];
	if(p->next[j])
		p=p->next[j];
	else
	{//若节点不存在,则说明不重复,将jd置1
		jd=1;
		struct Nod* q=new struct Nod;
		for(int i =0;i<9;i++)
			q->next[i]=NULL;
		p->next[j]=q;
		p=q;}//生成后面的节点
}return jd;
}

测试运行

命令行下的运行结果

生成文件的内容

性能测试的结果

对于执行力,泛泛而谈的理解

执行力,字面意思就是对于执行的能力,也就是把想法变成行动的能力,也就是行动力。在有了想法或者目标后,能否付出行动是很关键的,因此好的执行力是必要的。
泛泛而谈,指肤浅的讲出看法。浮于表面,没有深入研究。也就是说讲空话,而没有去研究,去行动。这种行为很明显是不对的,是应该避免的。

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

学习进度条

第N周 新增代码(行) 累计代码(行) 本周学习耗时(小时) 累计学习耗时(小时) 重要成长
0 226 226 10 10 学会了vs基本操作以及代码的测试方法
posted @ 2017-09-10 13:21  kzy_pan  阅读(333)  评论(4编辑  收藏  举报