数独个人项目
一、Github项目地址
https://github.com/wdfcode/sudoku
二、PSP表格
PSP2.1 | Personal Software Process Stages | 预估耗时(分钟) | 实际耗时(分钟) |
---|---|---|---|
Planning | 计划 | 50 | 60 |
· Estimate | · 估计这个任务需要多少时间 | 30 | 30 |
Development | 开发 | 1220 | 1540 |
· Analysis | · 需求分析 (包括学习新技术) | 120 | 240 |
· Design Spec | · 生成设计文档 | 30 | 40 |
· Design Review | · 设计复审 (和同事审核设计文档) | 60 | 120 |
· Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 60 |
· Design | · 具体设计 | 60 | 90 |
· Coding | · 具体编码 | 600 | 800 |
· Code Review | · 代码复审 | 60 | 90 |
· Test | · 测试(自我测试,修改代码,提交修改) | 60 | 100 |
Reporting | 报告 | 50 | 80 |
· Test Report | · 测试报告 | 50 | 100 |
· Size Measurement | · 计算工作量 | 5 | 10 |
· Postmortem & Process Improvement Plan · | 事后总结, 并提出过程改进计划 | 30 | 30 |
合计 | 1435 | 1770 |
三、解题思路
首先,看到数独的概念:每行,每列和每个小的九宫格都要由1到9这九个数字不重复填充。首先想到的就是暴力解法,在一个空的矩阵中填入随机数,之后往旁边能够填的位置上继续随机填入能够填入的数字。但是,构成数独的规则比较复杂,填完当前位置之后往下个位置填的时候填入的数字很可能不满足之前的数字或者是这个空格没有可以填的数字,所以,还要加入回溯。具体的回溯做法就是从第一行第二列(因为第一行第一列已被填掉)往右来填,来寻找1-9中能填的数字来填到这个方格里。每填一个各自就把该行/列/九宫格的这个数字标记为不可填。每次到行末就调到下一行继续执行。
另外,因为还要考虑到不能由重复的矩阵,有两点就需要注意一下:这种映射只能作用于最开始的矩阵;进行行变换的时候不能动第一行(同时也是为了保证首数字不变)。
四、实现过程:
我们的作业中要实现的功能有两个:生成数独终局和求解数独。第一部分初始版就是裸的回溯,第二部分一个主要的概念是每个点的限制数。已经填好元素的限制为8(代表只有1个元素可以填) 然后每填一个位置的元素后,我们都去更新该行、该列、该九宫格的其它元素的限制数,如果某一点的限制数达到了8,我们继续更新这个点(这样重复操作就使得我们能够将先期不需要回溯就解决的点全部填掉,避免后期回溯在这些点上浪费时间)
单元测试主要包括两部分,是否是合法的数独以及是否与题意一致。第二点只有第二部分求解数独有。针对这两个判断我写了如下两个函数,并对第一部分进行了1、10、100、1000、10000的检测数独以及几个参数,第二部分进行了几个不同类型数独的测试。
五、代码说明
主要列举部分2的两个主要函数modifyElement和deleteElement来分析说明,其中a[i][j]代表数独中第i位置有没有j的限制,1代表限制即不能填,0代表能填。初始的时候都是0代表每一个位置可以填1~9。两个函数返回true代表可以修改/删除,否则就是和现有的情况矛盾。
bool modifyElement(int pos, int r) {//把pos位置填上r int p = pos / 9;//行号 int q = pos % 9;//列号 int h; x[pos] = r; rep(i, 1, 9) a[pos][i] = 1; a[pos][0] = 8;//限制数为8代表只有r一个值可以填 a[pos][r] = 0;//除了r可以填其它都标记为不能填 rep(j, 0, 8) {//同行的元素增加限制 h = Position(p, j); if (h != pos && !deleteElement(h, r)) return false; } rep(i, 0, 8) {//同列的元素增加限制 h = Position(i, q); if (h != pos && !deleteElement(h, r)) return false; } rep(i, 0, 2)//同九宫格的元素增加限制 rep(j, 0, 2) { h = (3 * (p / 3) + i) * 9 + (3 * (q / 3) + j);//简单推导可得 if (h != pos && !deleteElement(h, r)) return false; } return true; }
bool deleteElement(int pos, int r) { int i; if (a[pos][r] == 1)//如果这个pos位置不能填i,代表之前已经删除过,直接返回true return true; a[pos][r] = 1; if (++a[pos][0] == 9)//增加了一个限制后,没有可填的元素了,直接返回false return false; if (a[pos][0] == 8)//优化!某一个点只剩一个元素可以填,那么这个点的值也就固定,我们应当立即去modify这个点的值。 { for (i = 1; i <= 9; ++i) if (a[pos][i] == 0)//找到是哪个值可以填 break; if (!modifyElement(pos, i)) return false; } return true; }