问题描述:
八皇后问题是一个以国际象棋为背景的问题:如何能够在8×8的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n×n,而皇后个数也变成n。当且仅当n = 1或n ≥ 4时问题有解[1]。
来源:八皇后问题
这里的求解是利用递归来处理的。(暴力解决法,效率很低)
大体思路如下:
依次摆放每个皇后(后面以棋子代替皇后字样),因为不能是同一行,同一列,同一斜线,所以,至少可以肯定,一行只能摆放一个棋子;
然后按照如下步骤进行摆放:
- 第
n
个棋子摆放在第n
行上面(比如第1
个棋子就摆放在第一行),但是不知道要摆放在第几列,这时候使用笨方法,每个棋子都是从第1
列开始摆放,摆放上去之后判断这个棋子与前面的n-1
个棋子中的每一个是不是在同一列,同一斜线上面。(这里不再判断是否是同一行,因为在放置的时候,都是采用第n
个棋子就摆放在第n
行上面,所以一定都不在同一行上面) - 如果上面的判断结果为真(不在同一列,同一斜线上面),则递归调用去摆放第
n+1
个棋子; - 如果上面的判断结果为假,就将当前的棋子移动到下一列。(每一列的可选范围就是
[0,N)
) - 递归的退出条件就是 此时的
n > N
.(比如八皇后,这个N
就是8
了);
最终8个棋子会全部放完;但是注意,8个棋子放完这个函数并没有结束。按照上面到步骤1,此时会走到最外层的函数里面执行 把 n
摆放到下一列的操作; 然后等把这个棋子摆放到最后一列,并且里面的递归都结束之后,这个函数才结束。
摆放棋子的逻辑(递归实现):
public void placePiece(int[] map, int numberN, int[] counts) {
if (numberN == map.length) {
counts[0]++; // right count
System.out.println(formatMap(map));
System.out.println(graphMap(map));
return;
}
for (int i = 0; i < map.length; ++i) {
// try place in here.
map[numberN] = i;
counts[1]++; // try count
if (checkConflict(map, numberN)) {
// can place in here
// then try place the next piece
placePiece(map, numberN + 1, counts);
} /*else {
// can not place in here, nothing to do, just continue;
System.out.printf("conflict in (%d,%d)\n", numberN, i);
}*/
}
}
下面给出完整实现:
class Queues {
public static void main(String[] args) {
int queues = 8;
int[] array = new int[queues];
System.out.println("before: @");
Queues que = new Queues();
int[] counts = new int[2];
que.placePiece(array, 0, counts);
System.out.printf("after: counts # rightCount= %d, tryCount= %d\n", counts[0], counts[1]);
}
/**
* place queue.
*
* @param map 巧妙利用数组索引及数组元素值的组合来表示棋盘上面的任意位置。
* 比如a[0]=0; 表示是 (0,0); a[0]=1; 表示是 (0,1); a[7]=7; 表示的是(7,7) 的位置。
* @param numberN 表示的棋盘上面的第几个皇后,也是第几行, 而 map[numberN] 表示的第几列;
* @param counts 用于统计数量;
*/
public void placePiece(int[] map, int numberN, int[] counts) {
if (numberN == map.length) {
counts[0]++; // right count
System.out.println(formatMap(map));
System.out.println(graphMap(map));
return;
}
for (int i = 0; i < map.length; ++i) {
// try place in here.
map[numberN] = i;
counts[1]++; // try count
if (checkConflict(map, numberN)) {
// can place in here
// then try place the next piece
placePiece(map, numberN + 1, counts);
} /*else {
// can not place in here, nothing to do, just continue;
System.out.printf("conflict in (%d,%d)\n", numberN, i);
}*/
}
}
/**
* check conflict.
* 检查当前放置的棋子是否与之前的冲突(同一行,同一列,同一斜线);
* numberN 表示是当前要检查的皇后; 那么只需要判断该棋子与之前放置的有没有冲突即可; 与之前放置的都不冲突即为不冲突;
* 同时, numberN 表示的棋盘上面的第几行, 而 map[numberN] 表示的第几列;
*
* @param map 巧妙利用数组索引及数组元素值的组合来表示棋盘上面的任意位置。
* 比如a[0]=0; 表示是 (0,0); a[0]=1; 表示是 (0,1); a[7]=7; 表示的是(7,7) 的位置。
* @param numberN the serial number of queue. [0, map.length)
* @return true if it has no conflict; otherwise it's conflicted;
*/
private boolean checkConflict(int[] map, int numberN) {
boolean conflict = false;
for (int i = 0; i < numberN; ++i) {
if (map[numberN] == map[i] || Math.abs(numberN - i) == Math.abs(map[numberN] - map[i])) {
conflict = true;
break;
}
}
return !conflict;
}
private String formatMap(int[] map) {
StringBuilder sb = new StringBuilder("queues: [ ");
for (int i = 0; i < map.length; ++i) {
sb.append(String.format("(%d,%d), ", i, map[i]));
}
int last;
if ((last = sb.lastIndexOf(", ")) != -1) {
sb.delete(last, last + 2);
}
sb.append(" ]");
return sb.toString().replaceAll(" {2}", "");
}
private String graphMap(int[] map) {
StringBuilder sb = new StringBuilder("queues graph:\n{");
for (int column : map) {
sb.append("\t");
for (int j = 0; j < map.length; ++j) {
if (j != column) {
sb.append("# ");
} else {
sb.append("X ");
}
}
sb.append("\n");
}
sb.append("}");
return sb.toString();
}
}
输出如下:
before: @
queues: [ (0,0), (1,4), (2,7), (3,5), (4,2), (5,6), (6,1), (7,3) ]
queues graph:
{ X # # # # # # #
# # # # X # # #
# # # # # # # X
# # # # # X # #
# # X # # # # #
# # # # # # X #
# X # # # # # #
# # # X # # # #
}
.... 省略中间到打印
queues: [ (0,7), (1,3), (2,0), (3,2), (4,5), (5,1), (6,6), (7,4) ]
queues graph:
{ # # # # # # # X
# # # X # # # #
X # # # # # # #
# # X # # # # #
# # # # # X # #
# X # # # # # #
# # # # # # X #
# # # # X # # #
}
after: counts # rightCount= 92, tryCount= 15720
这个二维棋盘的打印是不是就很灵性~