用Java實現N*N的標準數獨及對角線數獨解題
阿新 • • 發佈:2019-02-13
<span style="font-size: x-large;"><strong>1、引言</strong></span>
前一段時間迷上了數獨遊戲(不知道數獨的同學請自行百度,或點這裡瞭解),就有了想程式設計實現解題的想法。一直拖到最近,終於抽空使用Java實現了3*3標準數獨的解題,並在其基礎上抽象出了N*N的標準數獨及對角線數獨的解題。現和眾位分享相關的程式碼和過程。
特別說明:這裡的N*N標準數獨,指的是N=n*n(n為正整數),即4*4、9*9、16*16、25*25……(n=1沒意義)
2、解題思路
數獨的解題方法有很多種,有興趣的同學可以自行百度或
我使用的是最簡單的,也是比較容易實現的基礎摒除法。
在每個空格上,都遞迴嘗試1~N的每個值的可能情況,校驗每個值是否符合基礎摒除法,不符合則嘗試下一個,直至嘗試N個值結束。
引用
基礎摒除法就是利用1~N的值在每一行、每一列、每一個N宮格都只能出現一次的規則進行解題的方法。基礎摒除法可以分為行摒除、列摒除、N宮格摒除。
3、實現過程
3.1 幾個定義
1、N宮格中,“空格”的定義,採用空字串(”“)或0表示;
2、N宮格中,行或列的索引定義,為編碼方便採用0~(N-1)來表示,如:第4行第5列的格子對應的行列索引為:row=3和col=4;
3、N宮格中,填空內容的定義,採用長度為N的一維陣列表示,如:
n=2,即N=4
Java程式碼
- // 4*4填空內容
- String[] dataArray = new String[] {
- ”1”, “2”, “3”, “4”
- });
// 4*4填空內容
String[] dataArray = new String[] {
"1", "2", "3", "4"
});
n=3,即N=9
- // 9*9填空內容
- String[] dataArray = new String[] {
- ”1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”
- });
// 9*9填空內容
String[] dataArray = new String[] {
"1", "2", "3", "4", "5", "6", "7", "8", "9"
});
n=4,即N=16
Java程式碼
- // 16*16填空內容
- String[] dataArray = new String[] {
- ”1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”, “A”, “B”, “C”, “D”, “E”, “F”, “G”
- });
// 16*16填空內容
String[] dataArray = new String[] {
"1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F", "G"
});
當然,N宮格的填空內容,不侷限於1~N的數值,你可以使用任何非“空格”的且互不一樣的字串,如:
Java程式碼
- // 4*4填空內容
- String[] dataArray = new String[] {
- ”張三”, “李四”, “王五”, “趙六”
- });
// 4*4填空內容
String[] dataArray = new String[] {
"張三", "李四", "王五", "趙六"
});
4、N宮格中,N*N的數獨題目或填空結果定義,採用長度為N*N的二維陣列表示,如:
Java程式碼
- // 9*9數獨題目
- String[][] resultArray = new String[][] {
- {
- ”0”, “2”, “0”, “0”, “0”, “0”, “0”, “0”, “0”
- }, {
- ”5”, “0”, “6”, “0”, “0”, “0”, “3”, “0”, “9”
- }, {
- ”0”, “8”, “0”, “5”, “0”, “2”, “0”, “6”, “0”
- }, {
- ”0”, “0”, “5”, “0”, “7”, “0”, “1”, “0”, “0”
- }, {
- ”0”, “0”, “0”, “2”, “0”, “8”, “0”, “0”, “0”
- }, {
- ”0”, “0”, “4”, “0”, “1”, “0”, “8”, “0”, “0”
- }, {
- ”0”, “5”, “0”, “8”, “0”, “7”, “0”, “3”, “0”
- }, {
- ”7”, “0”, “2”, “0”, “0”, “0”, “4”, “0”, “5”
- }, {
- ”0”, “4”, “0”, “0”, “0”, “0”, “0”, “7”, “0”
- }
- };
// 9*9數獨題目
String[][] resultArray = new String[][] {
{
"0", "2", "0", "0", "0", "0", "0", "0", "0"
}, {
"5", "0", "6", "0", "0", "0", "3", "0", "9"
}, {
"0", "8", "0", "5", "0", "2", "0", "6", "0"
}, {
"0", "0", "5", "0", "7", "0", "1", "0", "0"
}, {
"0", "0", "0", "2", "0", "8", "0", "0", "0"
}, {
"0", "0", "4", "0", "1", "0", "8", "0", "0"
}, {
"0", "5", "0", "8", "0", "7", "0", "3", "0"
}, {
"7", "0", "2", "0", "0", "0", "4", "0", "5"
}, {
"0", "4", "0", "0", "0", "0", "0", "7", "0"
}
};
5、N宮格中,每個格子的索引定義,採用從0開始的整數來表示,那麼N*N格子索引範圍為:0~(N*N-1),如:9*9的索引範圍為0~80,那麼,格子索引和行列索引可以進行相關的換算;
3.2 基礎摒除法的Java實現
1、行摒除
Java程式碼
- /
- 行校驗
- @param resultArray
- @param row
- @param value
- @return
- /
- privatestaticboolean checkRow(final String[][] resultArray, int row, String value) {
- int arrayLen = resultArray.length;
- for (int i = 0; i < arrayLen; i++) {
- if (value.equals(resultArray[row][i])) {
- returnfalse;
- }
- }
- returntrue;
- }
/
* 行校驗
* @param resultArray
* @param row
* @param value
* @return
*/
private static boolean checkRow(final String[][] resultArray, int row, String value) {
int arrayLen = resultArray.length;
for (int i = 0; i < arrayLen; i++) {
if (value.equals(resultArray[row][i])) {
return false;
}
}
return true;
}
2、列摒除
Java程式碼
- /
- 列校驗
- @param resultArray
- @param col
- @param value
- @return
- /
- privatestaticboolean checkColumn(final String[][] resultArray, int col, String value) {
- int arrayLen = resultArray.length;
- for (int i = 0; i < arrayLen; i++) {
- if (value.equals(resultArray[i][col])) {
- returnfalse;
- }
- }
- returntrue;
- }
/
* 列校驗
* @param resultArray
* @param col
* @param value
* @return
*/
private static boolean checkColumn(final String[][] resultArray, int col, String value) {
int arrayLen = resultArray.length;
for (int i = 0; i < arrayLen; i++) {
if (value.equals(resultArray[i][col])) {
return false;
}
}
return true;
}
3、N宮摒除
這裡實現比較難的一點在於,根據給定行、列計算其所在宮的行列開始值
Java程式碼
- /
- 宮校驗
- @param resultArray
- @param row
- @param col
- @param value
- @return
- /
- privatestaticboolean checkBlock(final String[][] resultArray, int row, int col, String value) {
- int arrayLen = resultArray.length;
- int blockLen = (int) Math.sqrt(arrayLen);
- int blockRowIndex = (int) row / blockLen;
- int blockColIndex = (int) col / blockLen;
- int blockRowStart = blockLen blockRowIndex;
- int blockColStart = blockLen * blockColIndex;
- for (int i = 0; i < blockLen; i++) {
- int rowIndex = blockRowStart + i;
- for (int j = 0; j < blockLen; j++) {
- int colIndex = blockColStart + j;
- if (value.equals(resultArray[rowIndex][colIndex])) {
- returnfalse;
- }
- }
- }
- returntrue;
- }
/
* 宮校驗
* @param resultArray
* @param row
* @param col
* @param value
* @return
*/
private static boolean checkBlock(final String[][] resultArray, int row, int col, String value) {
int arrayLen = resultArray.length;
int blockLen = (int) Math.sqrt(arrayLen);
int blockRowIndex = (int) row / blockLen;
int blockColIndex = (int) col / blockLen;
int blockRowStart = blockLen * blockRowIndex;
int blockColStart = blockLen * blockColIndex;
for (int i = 0; i < blockLen; i++) {
int rowIndex = blockRowStart + i;
for (int j = 0; j < blockLen; j++) {
int colIndex = blockColStart + j;
if (value.equals(resultArray[rowIndex][colIndex])) {
return false;
}
}
}
return true;
}
4、對角線摒除(左上至右下)
- /
- 對角線校驗(左上至右下)
- @param resultArray
- @param value
- @return
- */
- privatestaticboolean checkLeftTop2RightBottom(final String[][] resultArray, int row, int col, String value) {
- if (row == col) {
- int arrayLen = resultArray.length;
- for (int i = 0; i < arrayLen; i++) {
- if (value.equals(resultArray[i][i])) {
- returnfalse;
- }
- }
- }
- returntrue;
- }
/
* 對角線校驗(左上至右下)
* @param resultArray
* @param value
* @return
*/
private static boolean checkLeftTop2RightBottom(final String[][] resultArray, int row, int col, String value) {
if (row == col) {
int arrayLen = resultArray.length;
for (int i = 0; i < arrayLen; i++) {
if (value.equals(resultArray[i][i])) {
return false;
}
}
}
return true;
}
5、對角線摒除(左下至右上)
Java程式碼
- /
- 對角線校驗(左下至右上)
- @param resultArray
- @param value
- @return
- */
- privatestaticboolean checkLeftBottom2RightTop(final String[][] resultArray, int row, int col, String value) {
- int arrayLen = resultArray.length;
- if ((row + col) == (arrayLen - 1)) {
- for (int i = 0; i < arrayLen; i++) {
- if (value.equals(resultArray[arrayLen - i - 1][i])) {
- returnfalse;
- }
- }
- }
- returntrue;
- }
/
* 對角線校驗(左下至右上)
* @param resultArray
* @param value
* @return
*/
private static boolean checkLeftBottom2RightTop(final String[][] resultArray, int row, int col, String value) {
int arrayLen = resultArray.length;
if ((row + col) == (arrayLen - 1)) {
for (int i = 0; i < arrayLen; i++) {
if (value.equals(resultArray[arrayLen - i - 1][i])) {
return false;
}
}
}
return true;
}
6、基礎摒除法
Java程式碼
- /
- 執行所有校驗
- @param resultArray
- @param row
- @param col
- @param value
- @param checkCross
- @return
- /
- privatestaticboolean checkAll(final String[][] resultArray, int row, int col, String value, boolean checkCross) {
- // 行校驗
- if (!checkRow(resultArray, row, value)) {
- returnfalse;
- }
- // 列校驗
- if (!checkColumn(resultArray, col, value)) {
- returnfalse;
- }
- // 宮校驗
- if (!checkBlock(resultArray, row, col, value)) {
- returnfalse;
- }
- // 對角線校驗
- if (checkCross) {
- // 對角線校驗(左上至右下)
- if (!checkLeftTop2RightBottom(resultArray, row, col, value)) {
- returnfalse;
- }
- // 對角線校驗(左下至右上)
- if (!checkLeftBottom2RightTop(resultArray, row, col, value)) {
- returnfalse;
- }
- }
- returntrue;
- }
/
* 執行所有校驗
* @param resultArray
* @param row
* @param col
* @param value
* @param checkCross
* @return
*/
private static boolean checkAll(final String[][] resultArray, int row, int col, String value, boolean checkCross) {
// 行校驗
if (!checkRow(resultArray, row, value)) {
return false;
}
// 列校驗
if (!checkColumn(resultArray, col, value)) {
return false;
}
// 宮校驗
if (!checkBlock(resultArray, row, col, value)) {
return false;
}
// 對角線校驗
if (checkCross) {
// 對角線校驗(左上至右下)
if (!checkLeftTop2RightBottom(resultArray, row, col, value)) {
return false;
}
// 對角線校驗(左下至右上)
if (!checkLeftBottom2RightTop(resultArray, row, col, value)) {
return false;
}
}
return true;
}
3.3 解題實現
解題採用遞迴的方式進行,直至解題完成並打印出結果,相關實現程式碼如下
1、校驗是否已填好的實現如下:
- /
- 校驗是否已經填好
- @param value
- @return
- /
- privatestaticboolean isUnselect(String value) {
- return“”.equals(value) || “0”.equals(value);
- }
/
* 校驗是否已經填好
* @param value
* @return
*/
private static boolean isUnselect(String value) {
return "".equals(value) || "0".equals(value);
}
2、遞迴過程的填空結果傳遞,需要複製二維陣列,其實現如下:
Java程式碼
- /
- 複製陣列
- @param array
- @return
- /
- privatestatic String[][] copyArray(final String[][] array) {
- int rowCount = array.length;
- int colCount = array[0].length;
- String[][] copy = new String[rowCount][colCount];
- for (int i = 0; i < rowCount; i++) {
- for (int j = 0; j < colCount; j++) {
- copy[i][j] = array[i][j];
- }
- }
- return copy;
- }
/
* 複製陣列
* @param array
* @return
*/
private static String[][] copyArray(final String[][] array) {
int rowCount = array.length;
int colCount = array[0].length;
String[][] copy = new String[rowCount][colCount];
for (int i = 0; i < rowCount; i++) {
for (int j = 0; j < colCount; j++) {
copy[i][j] = array[i][j];
}
}
return copy;
}
3、列印解題結果的實現程式碼如下:
Java程式碼
- /
- 輸出結果
- @param resultArray
- */
- privatestaticvoid printResult(final String[][] resultArray) {
- System.out.println(”\n——————————–”);
- int arrayLen = resultArray.length;
- for (int i = 0; i < arrayLen; i++) {
- System.out.println(Arrays.asList(resultArray[i]));
- }
- }
/
* 輸出結果
* @param resultArray
*/
private static void printResult(final String[][] resultArray) {
System.out.println("\n--------------------------------");
int arrayLen = resultArray.length;
for (int i = 0; i < arrayLen; i++) {
System.out.println(Arrays.asList(resultArray[i]));
}
}
4、遞迴解題演算法(核心)
Java程式碼
- /
- 數獨解題
- @param dataArray 待選列表
- @param resultArray 前面(resultIndex-1)個的填空結果
- @param resultIndex 選擇索引,從0開始
- @param checkCross 是否是對角線數獨
- /
- privatestaticvoid sudoSelect(String[] dataArray, final String[][] resultArray, int resultIndex, boolean checkCross) {
- int resultLen = resultArray.length;
- if (resultIndex >= (int) Math.pow(resultLen, 2)) {
- // 全部填完時,輸出排列結果
- printResult(resultArray);
- return;
- }
- int row = (int) resultIndex / resultLen;
- int col = resultIndex % resultLen;
- if (isUnselect(resultArray[row][col])) {
- // 逐個嘗試,遞迴選擇下一個
- for (int i = 0; i < dataArray.length; i++) {
- if (checkAll(resultArray, row, col, dataArray[i], checkCross)) {
- // 排列結果不存在該項,才可選擇
- String[][] resultCopy = copyArray(resultArray);
- resultCopy[row][col] = dataArray[i];
- sudoSelect(dataArray, resultCopy, resultIndex + 1, checkCross);
- }
- }
- } else {
- // 遞迴選擇下一個
- String[][] resultCopy = copyArray(resultArray);
- sudoSelect(dataArray, resultCopy, resultIndex + 1, checkCross);
- }
- }
/
* 數獨解題
* @param dataArray 待選列表
* @param resultArray 前面(resultIndex-1)個的填空結果
* @param resultIndex 選擇索引,從0開始
* @param checkCross 是否是對角線數獨
*/
private static void sudoSelect(String[] dataArray, final String[][] resultArray, int resultIndex, boolean checkCross) {
int resultLen = resultArray.length;
if (resultIndex >= (int) Math.pow(resultLen, 2)) {
// 全部填完時,輸出排列結果
printResult(resultArray);
return;
}
int row = (int) resultIndex / resultLen;
int col = resultIndex % resultLen;
if (isUnselect(resultArray[row][col])) {
// 逐個嘗試,遞迴選擇下一個
for (int i = 0; i < dataArray.length; i++) {
if (checkAll(resultArray, row, col, dataArray[i], checkCross)) {
// 排列結果不存在該項,才可選擇
String[][] resultCopy = copyArray(resultArray);
resultCopy[row][col] = dataArray[i];
sudoSelect(dataArray, resultCopy, resultIndex + 1, checkCross);
}
}
} else {
// 遞迴選擇下一個
String[][] resultCopy = copyArray(resultArray);
sudoSelect(dataArray, resultCopy, resultIndex + 1, checkCross);
}
}
3.4 其它
1、根據待選陣列,初始化生成二維結果陣列;
- /
- 初始化結果陣列
- @param dataArray 待選列表
- @return
- /
- publicstatic String[][] initResultArray(String[] dataArray) {
- int arrayLen = dataArray.length;
- String[][] resultArray = new String[arrayLen][arrayLen];
- for (int i = 0; i < arrayLen; i++) {
- for (int j = 0; j < arrayLen; j++) {
- resultArray[i][j] = ”0”;
- }
- }
- return resultArray;
- }
/
* 初始化結果陣列
* @param dataArray 待選列表
* @return
*/
public static String[][] initResultArray(String[] dataArray) {
int arrayLen = dataArray.length;
String[][] resultArray = new String[arrayLen][arrayLen];
for (int i = 0; i < arrayLen; i++) {
for (int j = 0; j < arrayLen; j++) {
resultArray[i][j] = "0";
}
}
return resultArray;
}
2、根據N*N長度的字串,初始化生成二維結果陣列;
Java程式碼
- /
- 初始化結果陣列
- @param resultString 結果字串
- @return
- /
- publicstatic String[][] initResultArray(String resultString) {
- int arrayLen = (int) Math.sqrt(resultString.length());
- String[][] resultArray = new String[arrayLen][arrayLen];
- for (int i = 0; i < arrayLen; i++) {
- for (int j = 0; j < arrayLen; j++) {
- resultArray[i][j] = ”“ + resultString.charAt(i * arrayLen + j);
- }
- }
- return resultArray;
- }
/
* 初始化結果陣列
* @param resultString 結果字串
* @return
*/
public static String[][] initResultArray(String resultString) {
int arrayLen = (int) Math.sqrt(resultString.length());
String[][] resultArray = new String[arrayLen][arrayLen];
for (int i = 0; i < arrayLen; i++) {
for (int j = 0; j < arrayLen; j++) {
resultArray[i][j] = "" + resultString.charAt(i * arrayLen + j);
}
}
return resultArray;
}
4、測試
4.1 測試程式碼
1、為測試方便,進行了幾個封裝
Java程式碼
- /
- 9*9數獨給定已選字串求解
- @param resultString 數獨題目
- /
- publicstaticvoid sudoSelect(String resultString) {
- String[][] resultArray = initResultArray(resultString);
- sudoSelect(new String[] {
- ”1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”
- }, resultArray);
- }
- /
- N*N數獨給定結果陣列求解
- @param dataArray 待選列表
- @param resultArray 已選結果陣列
- /
- publicstaticvoid sudoSelect(String[] dataArray, final String[][] resultArray) {
- sudoSelect(dataArray, resultArray, false);
- }
- /
- 排列選擇(從列表中選擇n個排列)
- @param dataArray 待選列表
- @param resultArray 已選結果
- @param checkCross 是否校驗對角線
- /
- publicstaticvoid sudoSelect(String[] dataArray, final String[][] resultArray, boolean checkCross) {
- sudoSelect(dataArray, resultArray, 0, checkCross);
- }
/
* 9*9數獨給定已選字串求解
* @param resultString 數獨題目
*/
public static void sudoSelect(String resultString) {
String[][] resultArray = initResultArray(resultString);
sudoSelect(new String[] {
“1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”
}, resultArray);
}
/**
* N*N數獨給定結果陣列求解
* @param dataArray 待選列表
* @param resultArray 已選結果陣列
*/
public static void sudoSelect(String[] dataArray, final String[][] resultArray) {
sudoSelect(dataArray, resultArray, false);
}
/**
* 排列選擇(從列表中選擇n個排列)
* @param dataArray 待選列表
* @param resultArray 已選結果
* @param checkCross 是否校驗對角線
*/
public static void sudoSelect(String[] dataArray, final String[][] resultArray, boolean checkCross) {
sudoSelect(dataArray, resultArray, 0, checkCross);
}
2、測試入口
- publicstaticvoid main(String[] args) {
- // 求解給定數獨所有可能
- sudoSelect(new String[] {
- ”1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”
- }, new String[][] {
- {
- ”9”, “1”, “2”, “0”, “0”, “7”, “0”, “5”, “0”
- }, {
- ”0”, “0”, “3”, “0”, “5”, “9”, “0”, “2”, “1”
- }, {
- ”0”, “0”, “5”, “4”, “1”, “2”, “0”, “0”, “9”
- }, {
- ”0”, “8”, “0”, “0”, “4”, “5”, “9”, “0”, “2”
- }, {
- ”0”, “0”, “0”, “0”, “7”, “0”, “5”, “0”, “0”
- }, {
- ”5”, “0”, “4”, “0”, “6”, “0”, “0”, “1”, “0”
- }, {
- ”0”, “0”, “0”, “5”, “0”, “6”, “0”, “0”, “0”
- }, {
- ”2”, “5”, “0”, “7”, “0”, “0”, “8”, “0”, “0”
- }, {
- ”0”, “3”, “0”, “0”, “0”, “0”, “0”, “9”, “5”
- }
- });
- // 求解給定數獨所有可能
- // #9806 002300609000000075100060000504100008060050040800007102000030001250000000907004200
- // #9807 010000000000294000008300709180002040050000080030800096401003800000471000000000020
- // #9808 100200905000080000400600023010005060000060000050400030840001007000070000507002001
- // #9809 300500090400000500002310000053080010000090000060050370000021800001000004080007006
- // #9810 010500000090073000804020000400000100780060029002000005000030207000480060000006090
- sudoSelect(”002300609000000075100060000504100008060050040800007102000030001250000000907004200”);
- sudoSelect(”010000000000294000008300709180002040050000080030800096401003800000471000000000020”);
- sudoSelect(”100200905000080000400600023010005060000060000050400030840001007000070000507002001”);
- sudoSelect(”300500090400000500002310000053080010000090000060050370000021800001000004080007006”);
- sudoSelect(”010500000090073000804020000400000100780060029002000005000030207000480060000006090”);
- }
public static void main(String[] args) {
// 求解給定數獨所有可能
sudoSelect(new String[] {
“1”, “2”, “3”, “4”, “5”, “6”, “7”, “8”, “9”
}, new String[][] {
{
“9”, “1”, “2”, “0”, “0”, “7”, “0”, “5”, “0”
}, {
“0”, “0”, “3”, “0”, “5”, “9”, “0”, “2”, “1”
}, {
“0”, “0”, “5”, “4”, “1”, “2”, “0”, “0”, “9”
}, {
“0”, “8”, “0”, “0”, “4”, “5”, “9”, “0”, “2”
}, {
“0”, “0”, “0”, “0”, “7”, “0”, “5”, “0”, “0”
}, {
“5”, “0”, “4”, “0”, “6”, “0”, “0”, “1”, “0”
}, {
“0”, “0”, “0”, “5”, “0”, “6”, “0”, “0”, “0”
}, {
“2”, “5”, “0”, “7”, “0”, “0”, “8”, “0”, “0”
}, {
“0”, “3”, “0”, “0”, “0”, “0”, “0”, “9”, “5”
}
});
// 求解給定數獨所有可能
// http://tieba.baidu.com/p/4813549830
// #9806 002300609000000075100060000504100008060050040800007102000030001250000000907004200
// #9807 01000000000029400000830