灵感
在b站看到了这位师兄的讲解,基本框架说得很清楚了。在此主要说明一些细节以及算法回溯的地方。
点击此处跳转 -> b站八皇后算法讲解
八皇后问题
在8*8的国际象棋上拜访八个皇后,使其不能互相攻击。即:任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。
思路
思考方向
因为要用递归回溯解决问题。那么有几个思考方向:
- 递归终点是什么?
- 递归计算公式是什么?
- 在什么时候产生回溯?
难点
- 递归终点并不像斐波那契或者杨辉三角那样明确:第一个或是最后一个。
- 数据类型不是简单的基本数据类型。计算公式也不明确。
明确点
- 能够知道的是,我们的行为是:放置皇后
- 棋盘相当于一个地图,可以用二维数组,也可以用一维数组。
- 一维数组的下标代表行数,一维数组的元素值代表列数。
- 即:j 棋子坐标:
(j , arr[j])
- 比如
arr[3] = 2
,那么(3,2)
即第四行第三列。放了一个棋子。
- 有八个皇后,可以一个个放置。
- 八皇后规则:
arr[i] == arr[j]
Math.abs(j - i) == Math.abs(arr[j] - arr[i])
- 每个皇后有八个位置可以放(从第一列到第八列)-> 8轮外循环
for (arr[j] = 0; arr[j] < 8; arr[j]++)
- 先试探着放下去,然后和之前已经放置好的棋子做判断:
- 放置的是 j 皇后(第 j+1 个皇后),则前面有 j 个皇后已经放好了。需要和这 j 个皇后进行比较 -> j 轮内循环
for (i = 0; i < j; i++)
- 如果违反八皇后规则,则跳出内循环(不再比较),继续外循环,即 j 皇后右移一位。
- 如果进行 j 轮内循环后都没有违反八皇后规则,则
arr[j]
确定下来,寻找下一个皇后 -> 重复345步 (递归)
if(i == j) put(arr,j+1)
PS:这是个经典的判断中途是否有跳出循环的做法,就是增长的变量(i)是否等于边界值(j)
- 放置的是 j 皇后(第 j+1 个皇后),则前面有 j 个皇后已经放好了。需要和这 j 个皇后进行比较 -> j 轮内循环
回溯
理论理解
我们知道,递归调用时,会中断当前执行的程序,进入调用的方法。这个方法调用完成时,会返回调用这个方法的地方。
-
那么,就只需要看两点:
- 在哪里调用了方法(递归)
- 我们在 j 轮内循环完成后,调用了方法。
if(i == j) put(arr,j+1)
- 我们在 j 轮内循环完成后,调用了方法。
- 调用方法的地方,后面还有没有语句需要执行。如果有,就会产生回溯。
- 从
put(arr,j+1)
往后看,发现我们后面还有一部分外循环没有执行。 - 程序总是在前面 j 个皇后满足规则后就中断,调用方法找后面的皇后。
- 从
- 在哪里调用了方法(递归)
-
具体什么时候产生回溯:在调用的方法执行完毕时,会产生回溯
- 可以看到,外循环执行完了,一个方法也就结束了 。
- 外循环执行完毕 => j 皇后从第一列到第八列都没有找到能不违反规则的位置。此时 j 皇后的列数自增为8
图文理解
模块分析
输出模块(print)
int count = 1;
public void print(int[] arr,int count){
int i;
System.out.println("第" + count + "种摆法:");
for (i = 0; i < 8; i++) {
System.out.print(arr[i] + " ");
}
System.out.println("");
}
主要是count,需要定义在方法外面。
否则每次递归调用的时候,会导致count总是被初始化,出现始终为1等异常情况。
放置模块(put)
public void put(int[] arr,int j) { //放置第j+1个皇后
if (j == 8) { //没有第九个棋子,所以j增加到8时则说明八皇后已经放置完毕,可以输出
print(arr,count++);
} else {
int i;
for (arr[j] = 0; arr[j] < 8; arr[j]++) {
for (i = 0; i < j; i++) { //固定后面的放置(j)的棋子,判断前面已经放好的棋子(i)是否与它(j)冲突
if (arr[i] == arr[j] || Math.abs(j - i) == Math.abs(arr[j] - arr[i])) {
break; //产生冲突,不能放在这里
}
}//没有和前面任何一个棋子冲突,则放置该棋子,判断下一个棋子
if(i == j) put(arr,j+1);
}//遍历所有列都不能满足条件:自动退出->回溯到调用方法的地方。
//也就是上一行的put(arr,j+1),会继续执行外层for循环。即:将前一个j的列数增加
}
}
- 输出模块没有在main调用,是因为j == 8的时候,一种摆法就已经生成了(理应输出)。而put没有返回值,所以main是接收不到的。
- 所以main直接调用put就可以得到结果。
main调用put,put自行递归,put再调用print
主类主方法(main)
public class EightQueen {
public static void main(String[] args){
int[] arr = new int[8];
int j = 0;
Tool tool = new Tool();
tool.put(arr,j);
}
}
代码实现
public class EightQueen {
public static void main(String[] args){
int[] arr = new int[8];
int j = 0;
Tool tool = new Tool();
tool.put(arr,j);
}
}
class Tool {
int count = 1;
public void print(int[] arr,int count){
int i;
System.out.println("第" + count + "种摆法:");
for (i = 0; i < 8; i++) {
System.out.print(arr[i] + " ");
}
System.out.println("");
}
public void put(int[] arr,int j) { //放置第j+1个皇后
if (j == 8) { //没有第九个棋子,所以j增加到8时则说明八皇后已经放置完毕,可以输出
print(arr,count++);
} else {
int i;
for (arr[j] = 0; arr[j] < 8; arr[j]++) { //j皇后在它的行进行遍历判断 (也是回溯后执行的语句)
for (i = 0; i < j; i++) { //固定后面的放置(j)的棋子,判断前面已经放好的棋子(i)是否与它(j)冲突
if (arr[i] == arr[j] || Math.abs(j - i) == Math.abs(arr[j] - arr[i])) {
break; //产生冲突,不能放在这里
}
}//没有和前面任何一个棋子冲突,则放置该棋子,判断下一个棋子
if(i == j) put(arr,j+1);
}//遍历所有列都不能满足条件:自动退出->回溯到调用方法的地方。
//也就是上一行的put(arr,j+1),会继续执行外层for循环。即:将前一个j的列数增加
}
}
}