问题描述
在N×N格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。八皇后如下图所示。
思路分析
八皇后问题是一道经典的DFS回溯问题,一开始先将第0行第0列的位置放入皇后1(使用习惯,有大佬习惯以1开始也一个道理),那么在第0行所有的位置和第0列所有的位置以及其对角线上都不可能再有皇后。
我们一开始选择(0,0)点(即第一行第一个位置)安置皇后1,则下一个皇后(皇后2)一定不在这一行我们就可以直接从(1,0)点(第二行第一个位置)位置开始穷举。当然,第二行第一个位置和第二行第二个位置肯定都是不可以的了,因为他们不是和第一个皇后在一列就是和第一个皇后在同一个对角线。
要做这道题一定要明确两个问题。
- 何时进行输出?
- 如何进行标记?
对于问题一,还是比较简单的,在进行dfs时传递一个参数表示当前行,当当前行数等于N的时候就进行输出(因为我是以0开始的)。
对于问题二,这就是一个数学问题了,如下表所示,标出坐标:
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | (0,0) | (0,1) | (0,2) | (0,3) |
1 | (1,0) | (1,1) | (1,2) | (1,3) |
2 | (2,0) | (2,1) | (2,2) | (2,3) |
3 | (3,0) | (3,1) | (3,2) | (3,3) |
我们以红色坐标(1,2)为例,则可以发现与它同一列的坐标都是以2结尾的(绿色部分);主对角线上(左上到右下)的纵坐标与横坐标的差都是固定值1(橙色部分);副对角线上的纵坐标与横坐标的和是固定值3(紫色部分);由此我们可以得出由此推出一般结论也是如此。
我们可以设计一个存储结构来判定每一列、每一对角线上是否还可以放置皇后。可以使用3个一维数组,也可以使用一个三行2*N列的二维数组,为什么是2*N我们一会儿再说。
下面我们开始解题。
设题中N的值为n;
pos[p]=q表示在第p行第q列放置皇后;
mark[i][j]:为布尔类型的标记数组,默认false,当i=0且值为true时,表示第j列已被标记,无法放置皇后,
当i=1且值为true时,表示纵坐标与横坐标之和为定值j的坐标已被标记,即副对角线,
当i=2且值为true时,表示纵坐标与横坐标之差为定值j-n的坐标已被标记,即主对角线;
读到这里,大家应该会又产生两个问题:
- 刚才遗留的二维数组的第二维为什么是N*2?
- 为什么mark数组当i=2时,纵坐标与横坐标之差要加上n?
对于问题一,若是4皇后问题,则最大的横纵坐标值和为3 + 3 = 6,为了不浪费空间我们选择N*2;
对于问题二,我们之前探究过,主对角线上纵坐标与横坐标之差是定值,但可能是负数,而我们知道,数组的下标是不能为负数的,所以我们将其加上n,一个定值加上一个定值,既保证了它仍是定值,也保证了它是正数。同样以4皇后为例,最坏的情况是坐标(3,0),0 - 3 = -3,再加上4等于1,为正数(这里感谢陈梓轩大佬)。
代码实现
我们这里输出每一种可能的情况与总的情况数量,测试代码如下
/**
* 全局变量如下
*/
int n = 0;
int tot = 0;
int[] pos;
boolean[][] mark;
/**
* cur为当前行,开始传入的是0
*/
public void dfs(int cur) {
//当满足输出条件时输出,X表示皇后位置
if(cur == n) {
for(int i = 0; i < n; i++) {
for(int j = 0; j < n; j++) {
if(pos[i] == j) {
System.out.print("X");
}else {
System.out.print("O");
}
}
System.out.println();
}
System.out.println();
this.tot++;
}else {
for(int i = 0; i < n; i++) {
//判断当前坐标是否满足条件(不是已放置在棋盘上皇后的列、副对角线、主对角线)
if(!mark[0][i] && !mark[1][cur+i] && !mark[2][cur-i+n]) {
pos[cur] = i;
mark[0][i] = true;
mark[1][cur+i] = true;
mark[2][cur-i+n] = true;
dfs(cur+1);
//递归结束后回溯,将条件改回去
mark[0][i] = false;
mark[1][cur+i] = false;
mark[2][cur-i+n] = false;
}
}
}
}
@Test
public void nQueens() {
this.n = 8;
pos = new int[n];
mark = new boolean[3][n*2];
dfs(0);
System.out.println(tot);
}
总结
处理对角线是重点,其他就是基本的DFS思路。