利用递归和回溯解决八皇后问题
Posted 庸人冲
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了利用递归和回溯解决八皇后问题相关的知识,希望对你有一定的参考价值。
前言
本文解题思路来自于b站up懒猫老师的视频: 懒猫老师-C语言-递归函数-八皇后问题(搜索,回溯)
这个老师讲的特别细,有兴趣的可以去看看,相信一定能获得比本文更大的收获。
八皇后问题
在8×8格的国际象棋上摆放8个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法?
高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。 —— 百度百科
解题思路
1️⃣ 每一行不能重复出现,可以每次只在同一行上放置一个皇后,则避免了行冲突的问题。每一行的上皇后的放置位置可以从第1列~第8列,共8种选择。例如:从第一行开始放置皇后,那么该皇后从第1行第1列到第1行第8列都可以放置。
2️⃣ 每一列不能重复出现,可以在当前行的某一列合适位置放置皇后,并标记该列为已被占领,则下一行的皇后不可以放置在该列上,例如: 第一行第一列放置皇后,则第一列不能再放置其它皇后。
3️⃣ 每一对角线不能重复出现,对角线有主对角线(左上至右下)和副对角线(左下至右上)两个区域,当把本行皇后放置在合适列上后,标记这两个对角线区域,不能使其它皇后放置在这一区域中。
标记对角线的方式为:
① 主对角线,将棋盘每一行的行下标分别减去每一列的列下标,得到的值在每一个主对角线的位置上都是相等的。
可以定义一个boolean型数组d1,用来纪录棋盘中主对角线是否被占用,用上图中每一个对角线的值作为索引,因为存在负数因此整体+7得到0~14的值,刚好可以作为该数组的索引,数组长度为15。
假设当前放置皇后的位置为(0,2)
,则d1[0-2+7]
即d1[5]
的值被置为false。当放置下一行的皇后时,例如在(1,3)
位置,则判断数组d1[1-3+7]
位置上的值是否为true,为true表示可以放置,此时应该为false,因此这个位置不能放置。
② 副对角线,将棋盘每一行的行下标分别加上每一列的列下标,得到的值在每一个副对角线的位置上都是相等的。
同样可以定义一个d2
数组用来纪录棋盘中副对角线是否被占用,长度同样是15,并且值已经是0~14
所以无须修改。待放入皇后位置的行下标+列下标既是对应在d2
数组中的下标值。
通过上面分析,可以定义4个数组:
- 数组place为int型数组,用来纪律每一行皇后的位置,数组长度为8。假如第一行皇后放置再第三列的位置,则
place[0] = 2
。 - 数组flag为boolean型数组,用来纪录列的占用情况,数组长度也为8,默认值为true。假如第一列放置了一个皇后,则
flag[0] = false
。 - 数组d1为boolean型数组,用来纪录棋盘中主对角线是否被占用,长度为15,默认值为true。
- 数组d2为boolean型数组,用来纪律棋盘中副对角线是否被占用,长度为15,默认值为true。
public class Queen{
// 数据结构
private int[] place = new int[8]; // 表示棋盘的一行
private boolean[] flag = new boolean[8]; // 纪录列的占用情况
private boolean[] d1 = new boolean[15]; // 纪录主对角线的占用情况
private boolean[] d2 = new boolean[15]; // 纪录副对角线的占用情况
}
4️⃣ 定义数组后,可以从第1行的第1列开始放置第一个皇后,接着放置下一行的皇后,如果出找到合适位置则放置,当找到第8行合适放置的位置时,纪录本次8个皇后放置的情况,并将第8个皇后的纪录情况置为true,回到第上一行,继续查找,直到遍历完所有情况退出。
当遍历到第n行时,第1列和第8列都找不到合适的位置时,则也需要回溯到上一行,将上一行皇后的位置后移到本行其它的合适位置,继续查找。因此,需要用到使用递归来实现回溯的功能。具体代码如下:
public class Queen{
private int[] place = new int[8]; // 表示棋盘的一行
private boolean[] flag = new boolean[8]; // 纪录列的占用情况
private boolean[] d1 = new boolean[15]; // 纪录主对角线的占用情况
private boolean[] d2 = new boolean[15]; // 纪录副对角线的占用情况
private int count; // 用于纪录成功放置8皇后的次数
public Queen() {
// 初始化数组的值
Arrays.fill(flag,true);
Arrays.fill(d1,true);
Arrays.fill(d2,true);
}
public void putQueen(int row){ // row表示当前行
for(int col = 0; col < 8; col++){ // 从第row的第1列向第8列遍历
if(flag[col] && d1[row-col+7] && d2[row+col]){
// 放置皇后
place[row] = col;
// 标记占用
flag[col] = false; // col列被占用
d1[row-col+7] = false; // 对应主对角线被占用
d2[row+col] = false; // 对应副对角线被占用
// 当row < 7 时
if(row < 7){ // 递归调用
putQueen(row+1); // 继续放置下一行皇后
} else { // 当row == 7 时, 递归终止
print(); // 输出本次皇后的摆放位置
}
// 回溯,当代码执行到此处有2种情况:
// 1. 从putQueen()递归方法中返回, 并且在后续递归中未能找到合适位置, 则需要将row行,col列的所有标记清除, 并继续遍历row行col+1列。
// 2. 已经成功放置8个皇后, 即进入else语句, 同样也需要将row行,col列的所有标记清除,并返回至上一级方法调用处, 如果上一行皇后的位置不在最后一列,则会继续往后查找其它情况。如果上一行皇后在最后列, 则会继续返回上一行重复操作。
// 清除标记
flag[col] = true;
d1[row-col+7] = true;
d2[row+col] = true;
}
}
}
// 输出本次皇后摆放位置
public void print(){
// 纪录次数
System.out.println("第" + (++count) + "次八皇后的排列方式: ");
// 利用place数组中存放的信息, 还原为一个二维数组
// 'Q'表示皇后, '.' 表示空
char[][] chessBoard = new char[8][8];
for(int i = 0; i < 8; i++){
Arrays.fill(chessBoard[i],'.');
// 将第i行的第place[i]列置为Q
chessBoard[i][place[i]] = 'Q';
}
// 遍历
for(char[] chars : chessBoard){
for(char chess : chars){
System.out.print(chess + "\\t");
}
System.out.println();
}
}
@Test
public void testQueen(){
putQueen(0); // 从第1行开始
}
}
输出结果: (有兴趣的可以验证一下)
第1次八皇后的排列方式:
Q . . . . . . .
. . . . Q . . .
. . . . . . . Q
. . . . . Q . .
. . Q . . . . .
. . . . . . Q .
. Q . . . . . .
. . . Q . . . .
第2次八皇后的排列方式:
Q . . . . . . .
. . . . . Q . .
. . . . . . . Q
. . Q . . . . .
. . . . . . Q .
. . . Q . . . .
. Q . . . . . .
. . . . Q . . .
第3次八皇后的排列方式:
Q . . . . . . .
. . . . . . Q .
. . . Q . . . .
. . . . . Q . .
. . . . . . . Q
. Q . . . . . .
. . . . Q . . .
. . Q . . . . .
第4次八皇后的排列方式:
Q . . . . . . .
. . . . . . Q .
. . . . Q . . .
. . . . . . . Q
. Q . . . . . .
. . . Q . . . .
. . . . . Q . .
. . Q . . . . .
第5次八皇后的排列方式:
. Q . . . . . .
. . . Q . . . .
. . . . . Q . .
. . . . . . . Q
. . Q . . . . .
Q . . . . . . .
. . . . . . Q .
. . . . Q . . .
第6次八皇后的排列方式:
. Q . . . . . .
. . . . Q . . .
. . . . . . Q .
Q . . . . . . .
. . Q . . . . .
. . . . . . . Q
. . . . . Q . .
. . . Q . . . .
第7次八皇后的排列方式:
. Q . . . . . .
. . . . Q . . .
. . . . . . Q .
. . . Q . . . .
Q . . . . . . .
. . . . . . . Q
. . . . . Q . .
. . Q . . . . .
第8次八皇后的排列方式:
. Q . . . . . .
. . . . . Q . .
Q . . . . . . .
. . . . . . Q .
. . . Q . . . .
. . . . . . . Q
. . Q . . . . .
. . . . Q . . .
第9次八皇后的排列方式:
. Q . . . . . .
. . . . . Q . .
. . . . . . . Q
. . Q . . . . .
Q . . . . . . .
. . . Q . . . .
. . . . . . Q .
. . . . Q . . .
第10次八皇后的排列方式:
. Q . . . . . .
. . . . . . Q .
. . Q . . . . .
. . . . . Q . .
. . . . . . . Q
. . . . Q . . .
Q . . . . . . .
. . . Q . . . .
第11次八皇后的排列方式:
. Q . . . . . .
. . . . . . Q .
. . . . Q . . .
. . . . . . . Q
Q . . . . . . .
. . . Q . . . .
. . . . . Q . .
. . Q . . . . .
第12次八皇后的排列方式:
. Q . . . . . .
. . . . . . . Q
. . . . . Q . .
Q . . . . . . .
. . Q . . . . .
. . . . Q . . .
. . . . . . Q .
. . . Q . . . .
第13次八皇后的排列方式:
. . Q . . . . .
Q . . . . . . .
. . . . . . Q .
. . . . Q . . .
. . . . . . . Q
. Q . . . . . .
. . . Q . . . .
. . . . . Q . .
第14次八皇后的排列方式:
. . Q . . . . .
. . . . Q . . .
. Q . . . . . .
. . . . . . . Q
Q . . . . . . .
. . . . . . Q .
. . . Q . . . .
. . . . . Q . .
第15次八皇后的排列方式:
. . Q . . . . .
. . . . Q . . .
. Q . . . . . .
. . . . . . . Q
. . . . . Q . .
. . . Q . . . .
. . . . . . Q .
Q . . . . . . .
第16次八皇后的排列方式:
. . Q . . . . .
. . 迷宫回溯和八皇后问题