八皇后问题的背景:1848年,国际西洋棋手贝瑟尔提出在 8x8 的国际象棋上摆放八个皇后,任意两个皇后不能处于同一行、同一列或者同一斜线上,问一共有多少种摆法?
思路分析:
- 第1个皇后先放在第1行第1列,
- 第2个皇后放在第2行第1列,然后判断是否满足条件,不满足就继续放在第2行第3列、第2行第4列,直到在第2行的8列中找到合适的列位置,
- 第3~8个皇后与第2个皇后做法一样,
- 当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,第1个皇后放到第1行第1列的所有解全部得到,
- 继续将第1个皇后放第1行第2列,循环执行2、3、4步骤;
简而言之,第一个皇后位置固定,找到剩下所有满足条件的皇后排列的可能情况。
我们要着重理解这句话:当得到一个正确解时,在栈回退到上一个栈时,就会开始回溯,第1个皇后放到第1行第1列的所有解全部得到。
以下图为例:
- 当得到1个正确解时:第1个皇后放在第1行的第1列,第1行共有8列,共有8种放法,每一种对应方法对应其他皇后的结果不同,
- 在栈回退到上一个栈时,就会开始回溯:回溯就是判断我们之前放置的皇后是否正确,放置完第3个皇后,遍历判断和前两个皇后的关系正确与否,
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就“回溯”返回,尝试别的路径。
代码实现:
棋盘的表示方法:使用一维数组来表示,array[i]的i:放第i+1个皇后,array[i] = thcol:第i+1个皇后放在第thcol列;
int max = 8; // 八个皇后
int[] array = new int[max];
static int totalCount = 0;
第2个皇后放在第2行第1列,然后判断是否满足不在同一行、同一列、同一斜线,不满足就继续放在第2行第3列、第2行第4列,直到在第2行的8列中找到合适的列位置 ;
// 当放第n个皇后时,判断该皇后和前面已经放置的皇后是否在同一列或者斜线
// true表示不在同一列,不在同一斜线
public boolean judge(int queue) {
for (int row = 0; row < queue; row++) {
if (array[row] == array[queue] || Math.abs(queue - row) == Math.abs(array[queue] - array[row])) {
return false;
}
}
return true;
}
使用递归,找到所有的解法,第queue皇后满足条件,就查找第queue+1个皇后满足条件的情况,直到8个皇后不同位置的不同方法都找到
public void check(int queue) {
if (queue == max) { // queue一共是8个
show();
totalCount++;
return;
}
for (int col = 0; col < max; col++) { // 从第0列到第7列
array[queue] = col; // 先把皇后放在第1列,queue不仅代表第几个皇后,也代表第几行
if (judge(queue)) { //皇后不在同一列,同一斜线
check(queue + 1); // 接着放queue+1个皇后
}
}
}
全部代码:
package cn.cwj.recursion;
public class Queue8 {
int max = 8; // 最多放置皇后的数量
// 一维数组表示棋盘,array[i]的i:放第i+1个皇后,array[i] = thcol:第i+1个皇后放在第thcol列
int[] array = new int[max];
static int totalCount = 0;
public void check(int queue) {
if (queue == max) { // queue一共是8个
show();
totalCount++;
return;
}
for (int col = 0; col < max; col++) { // 从第0列到第7列
array[queue] = col; // 先把皇后放在第1列,queue不仅代表第几个皇后,也代表第几行
if (judge(queue)) { //皇后不在同一列,同一斜线
check(queue + 1); // 接着放queue+1个皇后
}
}
}
// 当放第n个皇后时,判断该皇后和前面已经放置的皇后是否在同一列或者斜线
// true表示不在同一列,不在同一斜线
public boolean judge(int queue) {
for (int row = 0; row < queue; row++) {
/*
皇后在同一列,同一斜线的判断条件:
1).array[row] == array[thQueue] 表示第row行的列值和第queue行的第queue个皇后是否在同一列上
1.1)row从0开始,表示 在棋盘上 放置当前的 第queue个皇后 会和之前的 已经放置的皇后 依次比较,同列返回false
2).Math.abs(n-row) == Math.abs(array[n]-array[i])),表示皇后在同一斜线
2.1)n-i是行相减,
2.2)array[n]-array[i]是列相减,把棋盘看成正方形,斜线是对角线,构成等边直角三角形,用坐标计算思想即可
2.3)比如,当前行(row,array[row]),当前皇后(queue,array[queue])转成坐标系
*/
if (array[row] == array[queue] || Math.abs(queue - row) == Math.abs(array[queue] - array[row])) {
return false;
}
}
return true;
}
// 显示放置皇后的位置
public void show() {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + "\t");
}
System.out.println();
}
public static void main(String[] args) {
Queue8 queue8 = new Queue8();
queue8.check(0);// 从第1个皇后开始放
System.out.println("八皇后问题共有"+totalCount+"解法");
}
}
截取了部分结果图:可以根据打印的结果到小游戏中搜索“死亡八皇后”进行验证