Github地址
PSP表格 | |||
Personal Software Process Stages | 预估耗时 | 实际耗时 | |
Planning | 计划 | 2小时 | 1小时 |
Estimate | 这个任务需要多少时间 | 24小时 | 36小时 |
Development | 开发 | 8小时 | 12小时 |
Analysis | 需求分析(包括学习新技术) | 2小时 | 3小时 |
Design Spec | 生成设计文档 | 2小时 | 4小时 |
Design Review | 设计复审 | 3小时 | 2小时 |
Coding Standard | 代码规范 | 1小时 | 3小时 |
Design | 具体设计 | 1小时 | 40分钟 |
Coding | 具体编码 | 6小时 | 4小时 |
Code Review | 代码复审 | 5小时 | 3小时 |
Test | 测试(自我测试,修改代码,提交修改) | 1小时 | 2小时 |
Reporting | 报告 | 1小时 | 2小时 |
Test Repor | 测试报告 | 1小时 | 2小时 |
Size Measurement | 计算工作量 | 1小时 | 1小时 |
Postmortem | 事后总结,并提出过程改进计划 | 2小时 | 1小时 |
Improvement Plan | 过程改进计划 | 2小时 | 2小时 |
合计 | 38小时 | 40小时40分钟 |
需求
实现一个命令行程序,不妨称之为Sudoku
百度百科简介:
数独盘面是个九宫,每一宫又分为九个小格。在这八十一格中给出一定的已知数字和解题条件,利用逻辑和推理,在其他的空格上填入1-9的数字。使1-9每个数字在每一行、每一列和每一宫中都只出现一次,所以又称“九宫格”。
具体任务:
现在我们想一步一步来,完成从三宫格到九宫格的进阶;完成三宫格和其他博客任务,就算过了初级考核,其他的算升级。具体各阶规则如下:
输入:
输入文件名以命令行参数传入。例如我们在命令行窗口(cmd)中进入Sudoku.java所在文件的目录然后输入:
javac Sudoku.java
java Sudoku -m 9 -n 2 -i input.txt -o output.txt
-m 宫格阶级(3~9的整数)
-n 待解答盘面数目
-i 指定输入文件(需要在自己的电脑磁盘里面提前创建好)
-o 指定程序的输出文件(也需要在自己的电脑里面提前创建好)
上面语句对应的输入文件如下:
0 0 8 0 0 4 0 0 9
0 6 0 0 0 0 1 0 0
0 3 7 0 0 0 0 0 0
8 0 1 2 6 9 0 0 3
0 2 5 4 7 0 0 6 8
0 9 0 0 0 5 0 0 0
9 0 0 1 5 2 3 7 4
0 4 0 3 9 8 0 1 6
1 5 3 6 4 7 8 0 2
2 0 0 0 0 0 0 0 0
0 0 6 3 0 0 0 7 0
5 0 0 0 0 0 1 0 0
9 6 7 4 0 0 0 0 5
8 1 3 0 0 0 0 0 0
4 2 0 7 1 8 9 6 3
3 5 0 0 4 1 6 9 7
6 9 8 2 7 3 5 4 1
0 4 0 0 5 9 2 0 8
输出
输出n个程序解出的盘面,每两个盘面间空一行,每个盘面中,每两个小格之间有一个空格。
上面的命令行对应的输出文件output.txt组织如下:
5 1 8 7 2 4 6 3 9
2 6 9 5 8 3 1 4 7
4 3 7 9 1 6 2 8 5
8 7 1 2 6 9 4 5 3
3 2 5 4 7 1 9 6 8
6 9 4 8 3 5 7 2 1
9 8 6 1 5 2 3 7 4
7 4 2 3 9 8 5 1 6
1 5 3 6 4 7 8 9 2
2 7 9 1 8 4 3 5 6
1 8 6 3 2 5 4 7 9
5 3 4 9 6 7 1 8 2
9 6 7 4 3 2 8 1 5
8 1 3 5 9 6 7 2 4
4 2 5 7 1 8 9 6 3
3 5 2 8 4 1 6 9 7
6 9 8 2 7 3 5 4 1
7 4 1 6 5 9 2 3 8
解题思路:
拿到题目的时候其实没有看懂到底要求做什么,对于命令行传入参数也是一无所知,在群里面询问大佬们,了解命令行如何传参之后,才正式开始构思如何求解九宫格盘面,好在自己平时也喜欢玩数独,给我一个九宫格的盘面30分钟不到就能解完,可如今要自己来手写代码,让代码来解读,这到难倒我了,以自己目前的水平和知识面,写完估计的要300分钟吧!废话不多说了,先讲讲自己的思路吧:首先我们得知道3-9宫格最终盘面里每个数字所应满足的要求:
三宫格:盘面是3*3。使1-3每个数字在每一行、每一列中都只出现一次,不考虑宫;
四宫格:盘面是2*2四个宫,每一宫又分为2*2四个小格。使1-4每个数字在每一行、每一列和每一宫中都只出现一次;
五宫格:盘面是5*5。使1-5每个数字在每一行、每一列中都只出现一次,不考虑宫;
六宫格:盘面是2*3六个宫,每一宫又分为3*2六个小格。使1-6每个数字在每一行、每一列和每一宫中都只出现一次;
七宫格:盘面是7*7。使1-7每个数字在每一行、每一列中都只出现一次,不考虑宫;
八宫格:盘面是4*2八个宫,每一宫又分为2*4八个小格。使1-8每个数字在每一行、每一列和每一宫中都只出现一次;
九宫格:盘面是3*3九个宫,每一宫又分为3*3九个小格。使1-9每个数字在每一行、每一列和每一宫中都只出现一次;
根据这个要求写一个方法legal,以判断在九宫格中的坐标(x,y)的位置上插入value,是否符合上述规则,代码如下
public static Boolean legal(int a[][],int x, int y, int value,int m) {
for (int i = 0; i < m; i++) {
//如果列中有value,则返回false
if (i != x && a[i][y] == value) {
return false;
}
//如果行中有value,则返回false
if (i != y && a[x][i] == value) {
return false;
}
}
if(m==9){
//(minX,minY)是(x,y)所属小九宫格的左上角的坐标
int minX = x / 3 * 3;
int minY = y / 3 * 3;
for (int i = minX; i < minX + 3; i++) {
for (int j = minY; j < minY + 3; j++) {
//如果小九宫格中的非(x,y)的坐标上的值为value,返回false
if (i != x && j != y && a[i][j] == value) {
return false;
}
}
}
}
if(m==4){
//(minX,minY)是(x,y)所属小4宫格的左上角的坐标
int minX = x / 2 * 2;
int minY = y / 2 * 2;
for (int i = minX; i < minX + 2; i++) {
for (int j = minY; j < minY + 2; j++) {
//如果小九宫格中的非(x,y)的坐标上的值为value,返回false
if (i != x && j != y && a[i][j] == value) {
return false;
}
}
}
}
if(m==8){
//(minX,minY)是(x,y)所属小8宫格的左上角的坐标
int minX = x / 4 * 4;
int minY = y / 2 * 2;
for (int i = minX; i < minX + 4; i++) {
for (int j = minY; j < minY + 2; j++) {
//如果小九宫格中的非(x,y)的坐标上的值为value,返回false
if (i != x && j != y && a[i][j] == value) {
return false;
}
}
}
}
if(m==6){
//(minX,minY)是(x,y)所属小6宫格的左上角的坐标
int minX = x / 2 * 2;
int minY = y / 3 * 3;
for (int i = minX; i < minX + 2; i++) {
for (int j = minY; j < minY + 3; j++) {
//如果小九宫格中的非(x,y)的坐标上的值为value,返回false
if (i != x && j != y && a[i][j] == value) {
return false;
}
}
}
}
return true;
}
legal方法写完之后,并没有结束,求解九宫格的核心思想让我为之思考了一整天,首先想到的是按照平时玩数独的思维来解答:也就是自己常用的排除法,先将每行每列每个宫里面不可能出现的数字排除掉,然后将一些确定的数字填上去,然后再排除,再填......显然这种方法就是没脑子的人才会想的出来的,写完估计都猴年马月了,于是去询问ACM的算法大佬,提示了我一下,让我使用回溯法,刚提完,我瞬间“柳暗花明又一村”,马上有了思路:
具体代码和注释如下:
shuDu[][]是用来存放数独游戏的二维数组。
public static int shuDu[][] = new int[9][9];
public static void setShuDu(int[][] shuDu) {
Sudu.shuDu = shuDu;
}
使用回溯法求解数独
public static void shuDu_solution(int k,int m) throws IOException {
if (k == (m*m)) {
String src= "D:\\sudoku\\"+outputFilename;
try{
FileWriter fw = new FileWriter(src,true);
for(int i=0;i<m;i++){
for(int j=0;j<m;j++){
fw.write(shuDu[i][j]+" ");
}
fw.write("\r\n");
}
fw.write("\r\n");
fw.close(); // 最后记得关闭文件
}
catch (Exception e) {
e.printStackTrace();
}
return;
}
int x = k / m;
int y = k % m;
if (shuDu[x][y] == 0) {
for (int i = 1; i <= m; i++) {
shuDu[x][y] = i;
if (legal(shuDu,x, y, i,m)) {
shuDu_solution(k + 1,m);
}
}
shuDu[x][y] = 0;
} else {
shuDu_solution(k + 1,m);
}
}
初始化命令行的传入的参数
public static void loadArgs(String args[]){
if(args.length>0&&args!=null){
for(int i=0;i<args.length;i++){
switch (args[i]) {
case "-i":
inputFilename = args[++i];
break;
case "-o":
outputFilename = args[++i];
break;
case "-m":
m=Integer.valueOf(args[++i]);
break;
case "-n":
n=Integer.valueOf(args[++i]);
break;
default:
break;
}
}
}
}
最后就是主函数
public static void main(String[] args) throws IOException {
loadArgs(args);
int generateShuDu[][]=new int[10][10];
File myFile = new File("D:\\sudoku",inputFilename);
Reader reader = new InputStreamReader(new FileInputStream(myFile),"UTF-8");
int tempchar; int i=0; int j=0;
while ((tempchar = reader.read()) != -1) {
if ( (((char) tempchar) != '\n') &&(((char) tempchar) != ' ')) {
if(i<m){
if(j<m){
if(tempchar!=13){
generateShuDu[i][j]=((char) tempchar)-48;
j++;
}
}else{
i++;
j=0;
generateShuDu[i][j]=((char) tempchar)-48;
}
}
if(i==m){
if(n!=0){
setShuDu(generateShuDu);
shuDu_solution(0,m);
n--;
i=0;j=0;
}
}
}
}
reader.close();
}
遇到的问题(这个问题耽误了我6个小时左右):
FileWriter fw = new FileWriter("c.txt",true);
fw.write("hello");
fw.close();
文件写入建议用FileWriter
如果用BufferedWriter会导致多次写入时被覆盖!
String outfile="D:\\sudoku\\out.txt";
File writename = new File(outfile); // 相对路径,如果没有则要建立一个新的out.txt文件
writename.createNewFile(); // 创建新文件
BufferedWriter out = new BufferedWriter(new FileWriter(writename));
out.write(shuDu[i][j]+" ");