软工作业2

数独作业

github链接

解题思路

  • 其实数独与八皇后问题是很类似的,八皇后要求的是每一行每一列不可以出现2个皇后,那么也就相当于数独中的每一行每一列不可以出现2个相同的数字,已经填好的数字就相当于已经放置的皇后(或是障碍物),所以应该想到要通过dfs求解
  • 那么确定了大致算法之后,需要思考如何具体的实现,减少递归的层数
  • 首先把9*9的大矩阵划分成9个3*3的小矩阵,我们按数字顺序填格子,而不是按照格子顺序去填数字。也就是说,我们先把数字1填到这9个小矩阵中,填好了之后把数字2填到9个小矩阵中,以此类推。
  • 这样就可以有效的减少递归层数。

设计实现

  • 主要有1个generator类去生成数独,在generator中主要的函数有4个,分别是init,work,dfs,Myprint
  • main函数传入n给init函数,work函数调用dfs生成数独,对于每一个合法方案,dfs中调用Myprint
  • 其中最核心的函数是dfs(Depth-First Search)
  • 伪代码如下
void generator::dfs(int num, int area)
{
    if (now == rep) exit(0); //数独数量达到了要求的n
    if (cnt == 81) 输出,并return;
    if (当前小矩阵area有当前的数字num)
    {
        if (最后一个area都有了num) dfs(num + 1, 1)//填下一个数字 
        else dfs(num, area + 1); //填下一个小矩阵
    }
    for (枚举当前小矩阵内9个格子,i,j表示坐标)
   {
        if ([i,j]这个位置可以填num)
        {
            [i,j]填入num;
            if (最后一个area都有了num) dfs(num+1, 1)//填下一个数字 
            else dfs(num, area + 1); //填下一个小矩阵
            清空[i,j]上的num;
        }
    }
}

代码说明

  • 按照数字的顺序,往9*9的矩阵内填写数字,也就是一开始先填入9个1,使得这9个1不矛盾,然后填入9个2,9个3。以此类推
  • 如果按照常规的想法,按照一个格子一个格子的填数字过去,有可能出现当前的格子无论填什么数字都不合法的情况,在这种情况下需要回溯,有可能回溯1步之后继续填这个格子,仍然无解,需要回退若干步,白白浪费了时间。
  • 而如果按数字去填,可以减少递归的层数,一般情况下,只要回退1层就可以纠正
  • 甚至在填入1和9的时候,直接就可以填过去,不需要回溯
  • 核心代码如下(其实就是翻译了一下上面的伪代码)
void generator::dfs(int num, int area)
{
	if (now == rep) exit(0);//数独数量达到了要求的n
	if (cnt == 81) //生成了一个数独
	{
		++now;
		Myprint();
		return;
	}
	if (InA[area][num])//当前小矩阵已经有数字
	{
		if (area == 9)//最后一个小矩阵已经处理
		{
			dfs(num + 1, 1);//处理下一个数字
		}
		else
		{
			dfs(num, area + 1);//处理下一个小矩阵
		}
	}
	int xleft = (area - 1) / 3 * 3 + 1;
	int xright = xleft + 2;
	int yup = (area - 1) % 3 * 3 + 1;
	int ydown = yup + 2;
	//分别表示在area内x,y坐标的上下限
	for (register int i = xleft; i <= xright; ++i)
	{
		for (register int j = yup; j <= ydown; ++j)
		{
			if (mat[i][j] == 0 && row[j][num] == 0 && col[i][num] == 0)//当前格子可以填入num
			{
				mat[i][j] = num;
				row[j][num] = col[i][num] = 1;
				++cnt;
				if (area == 9)
				{
					dfs(num + 1, 1);//处理下一个数字
				}
				else
				{
					dfs(num, area + 1);//处理下一个小矩阵
				}
				mat[i][j] = 0;
				row[j][num] = col[i][num] = 0;
				--cnt;
			}
		}
	}
}

测试运行

  • 输入的参数数量必须恰好2个,并且第一个是“-c”,第二个是1~100W之间的一个整数,否则均会报错
  • 如果用DEV运行,跑100W个数据,实际上速度还是挺快的
  • 正确性验证,写了一个check程序暴力判断一个数独是否合法,并用map进行判重,没有出现问题
  • 虽然验证100W个速度跑的还是慢了一点。。
  • 验证程序链接

改进性能


第一次改进

  • 一开始知道输出占据很大一部分时间,因为如果不输出的话,生成100w个数独大约只需要2~3s,加上输出之后大约需要10~20s,所以应该想办法优化输出
  • 那么用putchar输出会比printf快很多

第二次改进

  • 减少代码中的常数时间,把所有的i++改成++i,for循环加上register,void函数加上inline,发现其实并不会快很多。。。。

第三次改进

  • 引用了网络上吉司机的输出外挂,主要原理是用fwrite存下来,然后一次性输出
  • 快了挺多的。。

第四次改进

  • 把数独改成char类型的数组,直接对字符进行操作,输出的时候直接结合了字符输出以及fwrite
  • 又快了一点的。。
  • 用vs也不会太慢了。。

最新更新,改成release,原来VS也可以飞!

PSP 2.1表格

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