递归——回溯算法
八皇后问题介绍
八皇后问题,是一个古老而著名的问题,是回溯算法的经典案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8x8国际象棋棋盘上拜访8个皇后,使其不能够相互攻击,即:任意两个皇后都不能够处于同一行,同一列,同一斜线上,问共有多少种摆法?
解题思路:
1、第一个皇后先放在第一列上
2、第二个皇后在放在第二行的第一列上,然后判断是否符合条件,如果不符合,放在第二列,第三列。。。 如果符合,那么继续
3、继续第三个皇后,还是从第一列开始,然后第二列。。。当到最后第8个皇后也放在一个不冲突的位时,证明得到了一个正确的解
4、当有一个正确的解后,在栈退回到上一个栈时,回溯开始,最后就会将第一个皇后在第一列中所有解全部得到
5、然后在回头继续将第一个皇后放到第一行的第二列上,继续上面操作
注:我们通过一个一维数组就可以来解出八皇后的所有解,数组下标值可以认为是行,每个下标值对应的数据是在这一行中,皇后所在的列。因为数组下标为0,所以我们将行和列都从0开始
写三个方法:
1、用来放置皇后的方法
2、用来判断当前放置的皇后与已经放置的皇后是否冲突
3、打印方法
代码:
package cn.littleworm;
/*
八皇后问题:
任意两个皇后不能够出现在同一行,同一列,同意斜线上
解题思路:
1、第一个皇后先放在第一列上
2、第二个皇后在放在第二行的第一列上,然后判断是否符合条件,如果不符合,放在第二列,第三列。。。
如果符合,那么继续
3、继续第三个皇后,还是从第一列开始,然后第二列。。。当到最后第8个皇后也放在一个不冲突的位时
,证明得到了一个正确的解
4、当有一个正确的解后,在栈退回到上一个栈时,回溯开始,最后就会将第一个皇后在第一列中所有解全部得到
5、然后在回头继续将第一个皇后放到第一行的第二列上,继续上面操作
*/
public class Queue {
//定义一共有多少的皇后
int max = 8;
//定义一个数组,来存放每次皇后放置的位置
int[] array = new int[max];
//定义计数器,记录共有多少中解法
static int count = 0;
static int judgetnum = 0;
//能够将数组中的数据打印
private void print(){
//只要触发了该方法,就将计数器加一
count++;
for (int i = 0; i < array.length; i++) {
System.out.print(array[i]+" ");
}
System.out.println();
}
//判读放入的皇后与前面已经放入的皇后是否冲突:n表示第n个皇后
private boolean judget(int n){
/*
皇后冲突的条件是“:任意两个皇后,不能够出现在同一行,同一列,同意斜线
所以:
同一列:array[n] == array[i]
同意斜线:Math.abs(n-i) == Math.abs(array[n]-array[i])
*/
for (int i=0;i<n;i++){
if (array[n]==array[i]||Math.abs(n-i)==Math.abs(array[n]-array[i])){
return false; //证明出现冲突
}
}
return true;
}
//放置第n个皇后
public void put(int n){
//如果已经将前八个皇后都防止好了
if (n==max){
//将数组中的数据打印
print();
return;
}
//依次放皇后,从第一列开始
for (int i = 0; i < max; i++) {
//先放在第一列
array[n] = i;
//判断是否冲突,如果不冲突,递归调用
if (judget(n)){
put(n+1);
}
//如果冲突的话就会继续将皇后向下一列放
}
}
public static void main(String[] args) {
Queue queue = new Queue();
queue.put(0); //从第1行第一列开始放置
System.out.printf("共有%d种摆法",count);
}
}
结果:
一共有92种解法,我们可以在小游戏中试验一下是否正确:
我们可以从92中结果中随便找到一种结果,进行实验,结果总是正确的。
难点
在代码中有两个难点,一个是如何判断是否冲突,一个是如何理解回溯
判断是否冲突:
问题规定:任意两个皇后不能出现在同一行,同一列,同意斜线上。
因为我们是使用的一维数组,所以不需要考虑能不能出现在同一行上,只需要考虑会不会出现在同一列上和同意斜线上
列冲突
棋盘最大列数是8,所以我们只需要考虑当前第n个皇后与前面第n-1个皇后是否冲突交就行了。因为数组中存放的数据就是皇后所在的列数,所以使用一个循环,遍历比较当前array[n]的数据与前array[i]的数据是否相同就可。若相同则冲突
斜线冲突
不难发现,当两个皇后出现在同一斜线时:它们两个的行的差的绝对值与列的差的绝对值是一样的,即Math.abs(n-i) == Math.abs(array[n]-array[i])
理解回溯
//放置第n个皇后
public void put(int n){
//如果已经将前八个皇后都防止好了
if (n==max){
//将数组中的数据打印
print();
return;
}
//依次放皇后,从第一列开始
for (int i = 0; i < max; i++) {
//先放在第一列
array[n] = i;
//判断是否冲突,如果不冲突,递归调用
if (judget(n)){
put(n+1);
}
//如果冲突的话就会继续将皇后向下一列放
}
}
回溯在放置皇后的方法中使用到,当我们放入第一个皇后,是放在第一行第一列,这时判断是否冲突?不冲突,那么调用if判断语句中的put(n+1)方法(递归调用)。这时的参数就是2了,即第二个皇后,同样先放到第二行的第一列,在判断,发现冲突,根据循环,将皇后移动到第二列,判断,冲突,继续后移,再判断,冲突,继续后移,不冲突,然后调用if判读语句中的put(n+1)语句(递归调用)…当最后调用put(n+1)的时候发现参数已经大于8,所以将数组中的数据打印出来,并调用reutrn语句,弹出当前这个方法栈,进入上一级的方法栈,也就是进入第8行,循环继续执行,发现一直冲突,当这个方法栈的循环指向完了,在跳到上一级方法栈,就是第7行,然后在第7行寻找其他符合条件的位置。。。
来源:CSDN
作者:Juwenile
链接:https://blog.csdn.net/weixin_43288447/article/details/104092232