数独作业
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 |