数独问题流程图_数独-分析、设计、求解思路
需求分析
需求
運行一個命令行程序,程序能:
1. 生成不重復的數獨終局至文件。
2. 讀取文件內的數獨問題,求解并將結果輸出到文件。
數據建模
將數獨分成9個宮進行求解,ER表示如下,因為數獨左上角第一塊確定,所以將其看作數獨的屬性。
功能建模
數據源:用戶
數據終點:文件
主要數據流:生成指令、求解指令、數獨(待求解),以及終局
主要支持文件:待求解數獨文件
主要處理過程:生成終局、求解數獨
0層圖
對系統進行求精,劃分系統的子系統。
行為建模
解題思路
在最開始拿到題目的時候,想起上學期算法課程學過的回溯算法,決定用會回溯算法進行實現,所以翻看了算法設計的課本和ppt,對回溯算法重新進行了學習。在生成n個不同局面和求解給定數獨的思路大致一致,但有在細節處的處理有些不同。首先是生成n個不同的局面,按行進行循環,有一個二維數組保存每個位置的可能數字,從可能數字中隨機選一個放在該位置,若該位置找不到可能的位置,則回到上一個,修改上一個位置的解。不斷進行回溯,直到所有的位置都合法。若是對給定局面進行求解,則將空白位置用數組記錄下來,只對空白位置進行回溯即可。
但在該回溯算法中,搜索時盲目的,效率較低,最差的實現對每個空方格試探所有可能的數字,有大量的時間浪費。并且如果需要生成多個終局時,每一個終局都是通過回溯實現,且生成不同的終局由Random()進行數字選擇實現,但實際上Random生成偽隨機數,在一定時間會生成相同隨機數,無法保證在生成終局數目足夠大時,不會產生兩個相同的終局。
因為算法效率有很大的改進空間,以及無法保證在n足夠大時,所有終局都相異,所以在對代碼重構的過程中,引入了排列組合的算法,對原有進行優化。將數獨分成9個3X3的小方塊,每個小方塊是一個宮。
參考維基百科提供的一個生成終局思路,由第一宮生成其余八個宮。對第一宮中的塊進行排列組合即可,因為生成終局時,要求左上角的第一個數為:(學號后兩位相加)%9+1,(1+3)%9+1=5,所以左上角為5,且不允許改變。即在第一宮內只有八個塊可以移動,有8!=40320種情況,并且在每一宮內(除了第一宮)進行列列變換有3!×3!×3!×3!=1296,第二宮和第三宮可以交換,第四宮和第七宮可以交換有2!×2!=4,總共可以生成209,018,880個終局(大于1,000,000),此方案可行。
對于各種全排列,使用遞歸的方式實現。
設計實現過程
???編譯器:Intellij IDEA 2019.2
???語言:java
???運行環境:JDK 1.8
活動圖
順序圖
下面是生成終局的順序圖
下面是求解數獨的順序圖
類圖
???共有三個類,Main用于判斷輸入是否合法、將終局寫入文件、判斷輸入命令是否合法,Generate類用于生成終局,Solve類用于求解數獨。
具體代碼說明
Generate中主要函數:
?generateSudoku 外部調用的接口,傳入一個n(n<=1,000,000),即可以生成n個終局。
createSeed 將第一宮作為種子,進行交換操作,得到不同的種子。
createMap 通過種子的變換,得到該種子生成的第一個終局。
changeIndex 對于第一個終局進行行列變換,生成不同的終局。
writeToOutput 將生成的int[ ] [ ]的終局,按照輸出格式要求,存放在一個char[]中 。
changeIndex流程圖如下
具體實現代碼如下所示,在goalNum=1時,不用進行變換,在>1開始,先交換index[16]和index[17],即先在第三大列內進行列變換,可以有6中不同的終局,接著在第二大列內進行交換,依次類推,到行進行交換,可以快速產生大量不相同的終局。
/**
* @Title: changeIndex
* @Description: 對宮內的行列進行全排列
* @param a
* @param start
* @param end
* @return void
* @throws
*/
public void changeIndex( int start, int end) {
int i;
//分段進行全排列
if (start == end) {
if (end == 5) {
changeIndex( 6, 8);
}else if(end==8){
changeIndex( 12, 14);
}else if(end==14){
changeIndex( 15, 17);
}
else {
writeToOutput();
}
}
else {
for (i = start; i <= end; i++) {
swap(index, start, i);
changeIndex( start + 1, end);
if (nowNum == goalNum) break;
swap(index, start, i);
}
}
}
Solve中主要函數
findSolution外部調用的接口,傳入一個txt的絕對路徑,即可對該txt中的數獨進行求解。
dealFile將txt中的所有數獨,存在一個int[]中,并得到要求解的數獨數目。
initCriterion將Criterion(表示行列宮沒有用過的數)初始化,并將數獨中出現過的數在Criterion進行標記。
chooseCriterion遞歸對數獨進行求解。
chooseCriterion流程圖
chooseCriteron和dealWithCriterion是求解數獨的主要兩個函數。chooseCriterion中state表示當前是否能找到可行解,找到返回true,沒找返回false。先循環1-9九個數,找到一個所在行、列、宮都沒有使用過的數放入,然后調用dealWithCriterion函數,若后面的位置沒有待求解位置,則返回true,此時state為true,結束求解。若后面仍有待求解位置(a,b),則dealWithCriterion函數返回chooseCriterion(a, b)進行求解,若可以找到可行解,則前面所求解保留,若找不到,則修改前面找到的可行解。本質是利用回溯算法進行求解。
起初兩個函數寫在一起,但在代碼質量檢查時,該函數復雜度太高,所以拆分成兩個函數
/**
* @Title: chooseCriterion
* @Description: 回溯選擇合適的數
* @param row
* @param col
* @return boolean
* @throws
*/
private boolean chooseCriterion(int row, int col) {
int value;
boolean state = false;
for (int k = 0; k < 9; k++) {
if(!state) {
value = k + 1;
if (!fill(row, col, value)) {
continue;
}
map[row][col] = value;
usedNum(row, col, value);
state = dealWithCriterion(row, col);
if (!state) {
releaseNum(row, col, value);
map[row][col] = 0;
}
}
}
return state;
}
/**
* @Title: dealWithCriterion
* @Description: 查找是否有空位要求解
* @param row
* @param col
* @return boolean
* @throws
*/
public boolean dealWithCriterion( int row, int col) {
//沒有空位
boolean state = false;
for (; row < 9; row++) {
for (; col < 9; col++) {
if (map[row][col] == 0) {
state = true;
break;
}
}
if (state)
break;
col = 0;
}
//沒有空位
if (!state) {
return true;
}
return chooseCriterion(row, col);
}
Main中主要函數
stringToFile傳入一個char[],將其寫入可執行文件同級文件夾中的layout.txt(沒有會創建)。
isNumeric,whetherSolve,whetherGenerate都是判斷輸入是否合法。
總結
以上是生活随笔為你收集整理的数独问题流程图_数独-分析、设计、求解思路的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 西北大学计算机科学排名,西北大学计算机科
- 下一篇: 记第一次组装台式电脑的小经历