github:ThinMoon\031702541
PSP
PSP2.1 | Personal Software Process Stages | 預估耗時(h) | 實際耗時(h) |
---|---|---|---|
Planning | 計划 | 3 | 3 |
Estimate | 估計這個任務需要多少時間 | 29 | 26 |
Development | 開發 | 26 | 21 |
Analysis | 需求分析 (包括學習新技術) | 4 | 6 |
Design Spec | 生成設計文檔 | 1 | 1 |
Design Review | 設計復審 | 1 | 0.5 |
Coding Standard | 代碼規范 (為目前的開發制定合適的規范) | 1 | 0.5 |
Design | 具體設計 | 1 | 1 |
Coding | 具體編碼 | 15 | 10 |
Code Review | 代碼復審 | 1 | 1 |
Test | 測試(自我測試,修改代碼,提交修改) | 2 | 1 |
Reporting | 報告 | 3 | 5 |
Test Repor | 測試報告 | 1 | 1 |
Size Measurement | 計算工作量 | 1 | 1 |
Postmortem & Process Improvement Plan | 事后總結, 並提出過程改進計划 | 1 | 3 |
合計 | 32 | 29 |
設計、實現
第一眼看到題目發現竟然是數獨題,當時想這和大一大二有什么區別怎么做來做去還是算法題,在認真讀完博客后發現這可不僅僅只是像以往一般只需寫出算法然后控制台能夠輸出真確答案,往oj上一提交ac后就能兩腿一伸不再管的算法題啊!
代碼分兩個模塊分別為數獨算法模塊(Sudoku)、文件讀取模塊(FileIO)。
Sudoku模塊
求解數獨第一眼我看到是蒙蔽的,畢竟咱就是個算法渣渣,所以通過在網絡上搜索一番后恍然大悟,原來利用回溯法便能輕松解決!
backTrace()
/**
* 回溯算法
*
* @param i 行號
* @param j 列號
* @param outSudokus 輸出鏈表
*/
public void backTrace(int i, int j, LinkedList<int[][]> outSudokus) {
if (i == rank-1 && j == rank) {
int[][] array = new int[9][9];
for(int ai = 0; ai < rank; ai++) {
for(int aj = 0; aj < rank; aj++) {
array[ai][aj] = matrix[ai][aj];
}
}
outSudokus.add(array);
return;
}
//已經到了列末尾了,還沒到行尾,就換行
if (j == rank) {
i++;
j = 0;
}
//如果i行j列是空格,那么才進入給空格填值的邏輯
if (matrix[i][j] == 0) {
for (int k = 1; k <= rank; k++) {
//判斷給i行j列放1-9中的任意一個數是否能滿足規則
if (check(i, j, k)) {
//將該值賦給該空格,然后進入下一個空格
matrix[i][j] = k;
backTrace(i, j + 1, outSudokus);
//初始化該空格
matrix[i][j] = 0;
}
}
} else {
//如果該位置已經有值了,就進入下一個空格進行計算
backTrace(i, j + 1, outSudokus);
}
}
這段代碼就是利用了回溯法求解數獨的核心,其原理就是通過dfs檢測到每一個為零的格子后對每一個格子進行填數,填充過程為:對當前所在格從19依次填充,若檢測函數檢測到此數能夠填充便進行填充,隨后進入下一格知道某一格出現19都無法填充的情況便開始回溯將之前走過最近的路徑都歸為0后重新進行判斷。所以,一條路走到黑然后能走遍所有路徑的backTrace()支持多解的情況
chcek()
/**
* 判斷給某行某列賦值是否符合規則
*
* @param row 被賦值的行號
* @param line 被賦值的列號
* @param number 賦的值
* @return
*/
public boolean check(int row, int line, int number) {
//判斷該行該列是否有重復數字
for (int i = 0; i < rank; i++) {
if (matrix[row][i] == number || matrix[i][line] == number) {
return false;
}
}
//當盤面為4,6,8,9時需要對小宮格進行判斷
int tempRow = 0;
int tempLine = 0;
switch(rank) {
case 4:
tempRow = row / 2;
tempLine = line / 2;
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 2; j++) {
if(matrix[tempRow * 2 + i][tempLine * 2 + j] == number) {
return false;
}
}
}
break;
case 6:
tempRow = row / 2;
tempLine = line / 3;
for(int i = 0; i < 2; i++) {
for(int j = 0; j < 3; j++) {
if(matrix[tempRow * 2 + i][tempLine * 3 + j] == number) {
return false;
}
}
}
break;
case 8:
tempRow = row / 4;
tempLine = line / 2;
for(int i = 0; i < 4; i++) {
for(int j = 0; j < 2; j++) {
if(matrix[tempRow * 4 + i][tempLine * 2 + j] == number) {
return false;
}
}
}
break;
case 9:
tempRow = row / 3;
tempLine = line / 3;
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (matrix[tempRow * 3 + i][tempLine * 3 + j] == number) {
return false;
}
}
}
break;
default:
break;
}
return true;
}
如果說backTrace()是Sudoku的靈魂,那么check()便是backTrace()的伴侶,沒有check()的backTrace()就是徒有其表,只會到處瞎轉。check()的作用其實也很簡單,就是檢測當前數字是否能夠填進當前格子檢測原理為判斷將填入數字是否在當前行、列及小宮格中存在。
或許你會對backTrace()中的參數感到迷惑,不急我們下文詳解
FileIO模塊
好吧我承認算法渣渣的我輸入輸出流也不怎么會,不會怎么辦?去學啊!於是又開始了我的學習+魔改+查api之路,終於是實現了讀取txt文件及將內容輸出到txt中代碼,接着又傳來一個噩耗“文件里有多個盤”,???黑人問號,原來我之前沒看清楚一個txt文件中是有多個盤的!好吧,繼續改,改改。。。最終終於有了如下代碼
readFile()
/**
* 讀入TXT文件
* 返回一個整型數組包含里面所有數字
*/
public int[] readFile( String pathName) {
int[] array = new int[5000];
int count = 0;
try {
FileReader reader = new FileReader(pathName);
BufferedReader br = new BufferedReader(reader);
int number = 0;
while ((number = br.read()) != -1) {
if((number-48) >= 0) {
array[count++] = number - 48;
}
}
br.close();
} catch (IOException e) {
e.printStackTrace();
}
return array;
}
這個方法就是讀入文件中的所有數字然后返回一個整型數組。
writeFile()
/**
* 寫入TXT文件
*
*/
public void writeFile(String pathName, LinkedList<int[][]> sudokus, int rank) {
try {
File writeName = new File(pathName);
writeName.createNewFile();
FileWriter writer = new FileWriter(writeName);
BufferedWriter out = new BufferedWriter(writer);
for(int k = 0; k < sudokus.size(); k++) {
int[][] array = new int[9][9];
array = sudokus.get(k);
for(int i = 0; i < rank; i++) {
for(int j = 0; j < rank; j++) {
out.write(array[i][j] + 48);
out.write(" ");
}
out.write("\n");
}
out.write("\n");
}
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
寫到這個方法或許你就懂了為什么上面backTrace()會有一個鏈表參數了吧,這個鏈表就是用來儲存所有解的容器!然后在writeFile()中將所有的解打印出來!
或許你的問題又來了,readFile()返回的是一個一維數組我們該怎么操作呢? 這時候我們的splitArray()方法便應運而出啦!
splitArray()
/**
* 將整型readerFile讀出的數組分解為不同盤面
* 用LinkedList進行存儲二位數組
* 返回一個LinkedList
* @param suCnt面數
* @param rank階數
*/
public LinkedList<int[][]> splitArray(int[] array, int rank, int suCnt) {
LinkedList<int[][]> sudokus = new LinkedList<int[][]>();
int count = 0;
for(int k = 0; k < suCnt; k++) {
int[][] temp = new int[9][9];
for(int i = 0; i < rank; i++) {
for(int j = 0; j < rank; j++) {
temp[i][j] = array[count++];
}
}
sudokus.add(temp);
}
return sudokus;
}
測試
三階
四階
五階
六階
七階
八階
九階
性能分析
性能分析主要使用jprofiler但是分析完的結果我是一臉茫然的
TO THE END
這次作業其實算法不算太難但是各種新東西確實讓我學到了很多,比如熟悉了git的使用,熟悉了java的基本語法,學會了去查api等等,總的來說搜獲還是蠻大的!