Github地址
<tr align=center>
<td>Estimate</td><td>這個任務需要多少時間</td><td>24小時</td><td>36小時</td>
</tr>
<tr align=center>
<td>Development</td><td>開發</td></td><td>8小時</td><td>12小時</td>
</tr>
<tr align=center>
<td>Analysis</td><td>需求分析(包括學習新技術)</td><td>2小時</td><td>3小時</td>
</tr>
<tr align=center>
<td>Design Spec</td><td>生成設計文檔</td></td><td>2小時</td><td>4小時</td>
</tr>
<tr align=center>
<td>Design Review</td><td>設計復審</td></td><td>3小時</td><td>2小時</td>
</tr>
<tr align=center>
<td>Coding Standard</td><td>代碼規范</td></td><td>1小時</td><td>3小時</td>
</tr>
<tr align=center>
<td>Design</td><td>具體設計</td></td><td>1小時</td><td>40分鍾</td>
</tr>
<tr align=center>
<td>Coding</td><td>具體編碼</td></td><td>6小時</td><td>4小時</td>
</tr>
<tr align=center>
<td>Code Review</td><td>代碼復審</td></td><td>5小時</td><td>3小時</td>
</tr>
<tr align=center>
<td>Test</td><td>測試(自我測試,修改代碼,提交修改)</td><td>1小時</td><td>2小時</td>
</tr>
<tr align=center>
<td>Reporting</td><td>報告</td></td><td>1小時</td><td>2小時</td>
</tr>
<tr align=center>
<td>Test Repor</td><td>測試報告</td></td><td>1小時</td><td>2小時</td>
</tr>
<tr align=center>
<td>Size Measurement</td><td>計算工作量</td></td><td>1小時</td><td> 1小時</td>
</tr>
<tr align=center>
<td>Postmortem</td><td>事后總結,並提出過程改進計划</td></td><td>2小時</td><td> 1小時</td>
</tr>
<tr align=center>
<td>Improvement Plan</td><td>過程改進計划</td></td><td>2小時</td><td> 2小時</td>
</tr>
<tr align=center>
<td>合計</td><td></td></td><td> 38小時</td><td> 40小時40分鍾</td>
</tr>
PSP表格 | |||
Personal Software Process Stages | 預估耗時 | 實際耗時 | |
Planning | 計划 | 2小時 | 1小時 |
需求
實現一個命令行程序,不妨稱之為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]+" ");
異常處理:主要是對文件的讀取進行異常處理
單元測試樣例:
性能測試截圖():