N皇后问题解决思路
问题:n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击(即同行同列且同一斜边不能出现2个皇后)。
前提:n>3 (否则无解)
思路:已知n<=3无解,那就选最简单的4*4表格
如果第一个格子内放一个皇后那么第一行,第一列,全部都不能再放皇后且与1.1斜边对应的2,2 3,3 4,4都不能放皇后
再2,3再放一个皇后
此时无解在试2,4放一个皇后
显然3,2和4,3在同一斜边上所以第一行放第一列是无解的,所以在第一行第二列上放
第二行只能在2,4的位置放,之后就可以直接看出答案。如下图即
在方格为4的表格里,前一半列数(2)的解为1个那么,水平翻转就是剩下的解,如下图
此时我们的解题思路大概就出来了。
需要一个位置记录黑名单即 摆放过皇后的位置,其同行同列 同一斜边都不能再放。
需要一个记录皇后位置集,
需要一个计数器。
如果我们记录皇后位置的集合用一个N个数字的数组 第一个数字表示第一行第二个数字表示第二行那么。黑名单就可以只记录列和斜边
我们发现一个位置的斜边左上和右下相减是同一个数字,右上和左下相加数值相等,那么我门再用一个列的数组和两个斜边的数组记录黑名单。
贴代码
public class Queen {
private int[] column; //黑名单1,列已占用
private int[] rup; //黑名单2,左上右下
private int[] lup; //黑名单1,
private int[] queen; //所在对应列的位置
private int num; //计数器
public Queen() {
column = new int[8+1];
rup = new int[(2*8)+1];
lup = new int[(2*8)+1];
for (int i = 1; i <= 8; i++)
column[i] = 0;
for (int i = 1; i <= (2*8); i++)
rup[i] = lup[i] = 0; //初始定义全部无皇后
queen = new int[8+1];
}
public void backtrack(int i) {
if (i > 8) {
showAnswer();
} else {
for (int j = 1; j <= 8; j++) {
if ((column[j] == 0) && (rup[i+j] == 0) && (lup[i-j+8] == 0)) {
//若无皇后
queen[i] = j; //设定为占用
column[j] = rup[i+j] = lup[i-j+8] = 1;
backtrack(i+1); //循环调用
column[j] = rup[i+j] = lup[i-j+8] = 0;
}
}
}
}
protected void showAnswer() {
num++;
System.out.println("\n解答" + num);
for (int y = 1; y <= 8; y++) {
for (int x = 1; x <= 8; x++) {
if(queen[y]==x) {
System.out.print("Q");
} else {
System.out.print(".");
}
}
System.out.println();
}
}
public static void main(String[] args) {
Queen queen = new Queen();
queen.backtrack(1);
}
}
代码解读
如上所说,定义一个列,一个坐上右下(相减是同一个数)和右上左下(相加是同一个数)的黑名单
代码开头初始化几个数组的值,8皇后问题,数组长度都+1是为了后面从1开始,下面的循环都是从1开始,所以这个不必纠结。
然后开始执行主体,主体其实是一个递归,主体后面讲,我们先看出口1,出口1里面是一个解答答案的过程其实就是连个for循环循环输出而已,这里其实已经是答案弄好了。这里也很简单。
但是答案怎么来,看条件,每次i>8说明什么?i是什么?
i可以理解为行数,i大于8说明递归的时候i在第八行 有合适的位置让皇后放,也就是已经有一种解了 。
出口2,是for循环里面1-8的位置都不符合放皇后的要求。循环直接循环完。这一种方案是无解的,说明上一层位置不能选这个位置,所以上一层的黑名单位置去除掉,
(理论上这里也应该将queen[i]=0将,queen数组i的位置上置空。但是后面也会重复工作也会重复赋值,所以可以省去这个工作。)
此时的递归的方法走完,跳出来,执行上一层的黑名单置空。然后在上一层下一个位置j+1在进行判断
看递归上两行代码,
i行如果第j个列满足条件可以放皇后条件即使这个坐标的列没有被加入黑名单,坐上右下的黑名单和右上左下的黑名单也没有它。(条件:)
这个递归在for循环里,其实实际上就是两个for循环循环套用。
温故而知新
这个问题是用回溯法解决的(穷举法暴力破解)。举一反三,以后遇到需要用回溯法解决问题的时候,代码就可以写成这样。用递归解决。递归之前写占位操作(以后进行判断)递归之后写清除操作