参考资料:
八皇后问题总结:
N皇后问题,N格棋盘,对于第N个皇后而言,第N个皇后放在第N行。重点在于如何处理回溯的问题。回溯可能有但不限有如下情形
- 本身发现可以放置皇后的第N个,这时,只需要回溯到前一个N-1的行数,然后位置往后重新找一个新的位置就可以了。
- 如果这个回溯的点(N-M)不是最初回溯的点,即已经对棋盘的数据进行了占用(对二维数组做了赋值处理),那么就需要注意了
- 取消因为(n-m)在放置时,对棋盘数据的赋予的值,还原数据到(n-m-1)的情形
- 在获取新的位置后,需要及时的更新回溯中的数据,否则在后续获取新坐标会出现问题,因为,你无法知道m是多少!
代码如下(简单实现,没有优化):
package backtracking;
import util.CollectionHelper;
/**
* @author rongbin.xie
* @version 1.0.0
* @date 2020/7/9
* @description N皇后问题: https://juejin.im/post/5accdb236fb9a028bb195562
* @copyright COPYRIGHT © 2014 - 2020 VOYAGE ONE GROUP INC. ALL RIGHTS RESERVED.
**/
public class NQueue {
private final static int QUEUE_SIZE = 8;
private final static int[][] CHESS_BOARD = new int[QUEUE_SIZE][QUEUE_SIZE];
/**
* 记录回溯皇后过程的索引
*/
private final static int[] QUEUE_LOCATIONS = new int[QUEUE_SIZE];
public static void main(String[] args) {
locateQueue(1);
CollectionHelper.printArray(QUEUE_LOCATIONS);
}
public static void locateQueue(int queue) {
if (queue > QUEUE_SIZE || queue < 1) return;
int curLocation = QUEUE_LOCATIONS[queue - 1];
if (curLocation > 0) {
// back track and clear value to get the right next location
setValue(queue, curLocation, true);
}
int newLocation = findLocation(queue);
// 很重要,因为可能有的情形下,会回溯到N>1个前,所以要及时的更新回溯的数据
QUEUE_LOCATIONS[queue - 1] = 0;
if (newLocation > 0) {
// set value by new location
setValue(queue, newLocation, false);
QUEUE_LOCATIONS[queue - 1] = newLocation;
locateQueue(queue + 1);
} else {
//back track
locateQueue(queue - 1);
}
}
public static int findLocation(int queue) {
int[] queueRaw = CHESS_BOARD[queue - 1];
int curQueueLocation = QUEUE_LOCATIONS[queue - 1];
int idx = curQueueLocation == 0 ? 1 : curQueueLocation + 1;
for (; idx <= queueRaw.length; idx++) {
if (queueRaw[idx - 1] == 0) {
return idx;
}
}
return -1;
}
/**
* 棋盘赋值操作
*
* @param raw 行的索引,从1开始,其实也是皇后的索引代号
* @param col 列的索引,从1开始
* @param isClear 是否清除,否则就是占用
*/
public static void setValue(int raw, int col, boolean isClear) {
// set raw element as occupied-> value
for (int idx = 1; idx <= QUEUE_SIZE; idx++) {
assign(raw, idx, isClear, raw);
}
// set col element as occupied -> value
for (int idx = 1; idx <= QUEUE_SIZE; idx++) {
assign(idx, col, isClear, raw);
}
// 斜角函数是可以被优化的,只需要找到左上角和左下角就可以了,然后做斜率为1|-1的操作 + 边界的判断就好了
// set the oblique line element -> value
int back = Math.max(raw, col);
while (back > 0) {
if (col - back > 0 && raw - back > 0) {
assign(raw - back, col - back, isClear, raw);
}
if (col - back > 0 && raw + back <= QUEUE_SIZE) {
assign(raw + back, col - back, isClear, raw);
}
back--;
}
//forward,向右
int forward = QUEUE_SIZE - Math.min(raw, col);
while (forward > 0) {
if (col + forward <= QUEUE_SIZE && raw - forward > 0) {
assign(raw - forward, col + forward, isClear, raw);
}
if (col + forward <= QUEUE_SIZE && raw + forward <= QUEUE_SIZE) {
assign(raw + forward, col + forward, isClear, raw);
}
forward--;
}
}
public static void assign(int raw, int col, boolean isClear, int expectQueue) {
//删除因为expectQueue而占用的棋格
if (isClear && CHESS_BOARD[raw - 1][col - 1] == expectQueue) CHESS_BOARD[raw - 1][col - 1] = 0;
if (!isClear && CHESS_BOARD[raw - 1][col - 1] == 0) CHESS_BOARD[raw - 1][col - 1] = expectQueue;
}
}