1.問題描述
在國際象棋中,皇后可以橫,豎,斜三種走法. (比中國象棋的車還厲害些)
現有n*n的棋盤 要放n個皇后,且使他們相互無法攻擊,問有多少种放法.(即棋盤上任意橫,豎斜線都只有一個皇后)
2.大概思路:
1.用n*n一維數組表示結果:0代表未放子,1表示放子. 根據下標自己邏輯解析爲n*n二維
2.判斷一行的每個格子是否可以落子,如果可以則落子後遞歸到下一行繼續判斷.直到第n+1行,即算出結果可以打印出來.
3.注意的點:如果落錯子記得回退,
落子後如果下一行所有格子都不能放則說明上一處落子錯誤需要回退.(也算是回溯算法)
public void queen(int[] arr, int n, int row) {
if (row == n) {
printQueen(arr, n);//求解成功,打印結果
} else {
for (int i = 0; i < n; i++) { // 循環當行 每個格子是否可以落子
if (canPlace(arr, n, row * n + i)) { // 判斷是否可落子
arr[row * n + i] = 1; // 落子
queen(arr, n, row + 1); // 遞歸到下一行
arr[row * n + i] = 0; // 回退,說明此處落子後面某行可能無法落子
}
}
}
}
判斷是否可以落子
/**
*判斷是否可以落子
*/
public canPlace(int[] arr, int n, int i){
int row = i/n;
int col = i%n;
for(int j=0;j<row;j++ ){// 從第0行看是否有子
if(a[j*n+col]>0) return false;
if(col-(row-j)>=0 && a[j*n+col-(row-j)]>0) return false;
if(col+(row-j)<n && a[j*n+col+(row-j)]>0) return false;
}
for(int j=0;j<col;j++){
if(a[j+row*n]) return false; // 第row行不能落子
}
return ture;
}
據說有個更簡單的判斷: 假設(row,col) 處有落子,判斷(i,j)是否可以落子可用公式:
col =j (是否在一列) 和 |row-i|=|col-j| (是否在斜線上)
代碼表示:
if (col == j || Math.abs(row - i) == Math.abs(col - j)) return false;
3.java實現
package arithmetic;
/**
* @author jiangfeng 2019/8/4 17:59
* 八皇后問題
*/
public class EightQueen {
static int count =0;
public static void main(String[] args) {
queen(8);
}
/**
* n皇后問題的遞歸解法.
* todo 回溯算法求解
* @param n
*/
public static void queen(int n) {
int[] arr = new int[n * n];
queen_(0, arr, n);
System.out.println("有解數:" + count);
}
/**
* 八皇后問題遞歸求解器
* @param row 當前遞歸的行數
* @param arr 八皇后棋盤數組 n*n一維,根據下標自己邏輯解析爲n*n二維
* @param n 棋盤的大小
*/
private static void queen_(int row, int[] arr, int n) {
if (row == n) {
printResult(arr);
System.out.println();
count++;
} else {
for (int i = 0; i < n; i++) { //試探row行的 每一列是否可放
if (canPlace(arr, i + row*n, n)) {
arr[i + row*n] = 1;
queen_(row + 1, arr, n);//遞歸到下一行去
arr[i + row*n] = 0; // 說明此處不能放子
}
}
}
}
/**
* 檢測是否橫豎斜均無棋子
* @param arr
* @param i 第i個棋子是否可放
* @return
*/
private static boolean canPlace(int[] arr, int i, int n) {
// 第i個棋子的 左上,正上,右上不能有棋子
int row = i / n;
int col = i % n;
for (int j = 0; j < row; j++) { // 第row行不用考慮
if (arr[j * n + col] > 0) { // 正上不能有子
return false;
}
if (col - row + j >= 0 && arr[j * n + col - row + j] > 0) { // 左斜上方不能有子
return false;
}
if (col + row - j < n && arr[j * n + col + row - j] > 0) { // 右斜上方不能有子
return false;
}
}
for (int j = 0; j < col; j++) {
if (arr[row * n + j] > 0) { // 第row行不能落子
return false;
}
}
return true;
}
/**
* 用一維數組表示結果:0代表未放子,1表示放子
* 打印結果
* @param arr
*/
private static void printResult(int[] arr){
int n = ((Double) Math.sqrt(arr.length)).intValue();// 開平方
for (int i = 0; i < arr.length; i++) {
if ((i + 1) % n == 0) {
System.out.println(arr[i]);
} else {
System.out.print(arr[i] + " ");
}
}
}
}
結果: