这里我们用N皇后问题来进一步学习回溯算法。
如图,这是一个八皇后问题。如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。
算法思路:
首先我们分析一下问题的解,我们每取出一个皇后,放入一行,共有八种不同的放法,然后再放第二个皇后,同样如果不考虑规则,还是有八种放法。于是我们可以用一个八叉树来描述这个过程。从根节点开始,树每增加一层,便是多放一个皇后,直到第8层(根节点为0层),最后得到一个完全八叉树。这就是问题的解空间(状态空间树)。
接下来我们先对状态空间树进行修剪,然后才是使用深度优先搜索法进行遍历。
按照问题的题意,可以利用下述约束方程修剪状态空间树。
x[i] ≠ x[j] 1≤i≤8,1≤j≤8, i≠j ; (1)
| x[i] - x[j] | ≠ | i - j | 1≤i≤8,1≤j≤8, i≠j ; (2)
其中x[i] 表示第 i 行皇后的列位置。式(1)保证第 i 行的皇后和第 j 行的皇后不会在同一列;式(2)保证了两个皇后行号之差的绝对值不会等于列号之差的绝对值,因此它们不会在斜率为±1的同一斜线上。这两个关系式还保证i 和j 的取值范围应该为1到8。
使用上述约束方程对空间树进行修剪后,就可以使用DFS进行搜索了。
(网上看到个通俗易懂的说法,放上来帮助理解)
0. 将皇后编号1,2,3,4,5,6,7,8,并且排号为i的皇后,放在第i行
1. 将1号皇后放在第1行(1号皇后是肯定不会冲突的)
2. 1号皇后放好之后,放2号皇后(3号及以后的类似),从第2行第1列开始检测,不冲突就可以落子; 当本行没有合法的位置时,说明上一行皇后放的位置不好,则撤销上一行皇后的位置,并重新摆放(所谓回溯)
3. 如果8个皇后放好之后,按照上文逻辑该放8 + 1 号皇后了,但是已经放完了,所以将摆放结果打印。再重新摆放第8行(很多人不理解这里为什么还回溯)
回溯发生的位置:
1. 皇后i的摆放位置,使得皇后i+1怎么放都不行,即这条路走不通了,回溯(重新摆放皇后i的位置,使得i+1可以摆放,就好像迷宫遇到墙了,要往回走)
2. 8个皇后都摆放完毕,打印出了摆放结果,回溯(就好像迷宫找到了一件宝物(一共需要集齐n件),你找到之后还要回头找另外几件,直到你集齐或者把所有的路都走了(DFS))。
这里贴上Java代码实现。
public class WolfQueen {
/**
* 一共有多少个皇后(此时设置为8皇后在8X8棋盘,可以修改此值来设置N皇后问题)
*/
int max = 8;
/**
* 该数组保存结果,第一个皇后摆在array[0]列,第二个摆在array[1]列
*/
int[] array = new int[max];
public static void main(String[] args) {
new WolfQueen().check(0);
}
/**
* n代表当前是第几个皇后
* @param n
* 皇后n在array[n]列
*/
private void check(int n) {
//终止条件是最后一行已经摆完,由于每摆一步都会校验是否有冲突,所以只要最后一行摆完,说明已经得到了一个正确解
if (n == max) {
print();
return;
}
//从第一列开始放值,然后判断是否和本行本列本斜线有冲突,如果OK,就进入下一行的逻辑
for (int i = 0; i < max; i++) {
array[n] = i;
if (judge(n)) {
check(n + 1);
}
}
}
private boolean judge(int n) {
for (int i = 0; i < n; i++) {
if (array[i] == array[n] || Math.abs(n - i) == Math.abs(array[n] - array[i])) {
return false;
}
}
return true;
}
private void print() {
for (int i = 0; i < array.length; i++) {
System.out.print(array[i] + 1 + " ");
}
System.out.println();
}
}