八皇后问题:
八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。
如上图所示,有一点需要注意,八个皇后不仅不在同一行和同一列,而且还不能在同一条对角线上(当然,如果某两个皇后在同一行或者同一列,那么它们就一定不在同一对角线了),这样一下子要问题的难度提升了。
分析思路:摆放八个皇后,必然是每行每列只有一个皇后,那么我们就从第0行开始,一直摆放到第7行即可,每摆放一个皇后就检查是否符合问题要求。这个过程和递归的思路一致。
在摆放第0行的时候,一共有8列,所以就有8种可能摆放,用一个for循环来遍历它。现在完成了第0行的摆放,然后进入下一行,这里要注意一点,因为题目要求,皇后不能在同一条对角线上,所以第1行的摆放方法并不是简单的7种,那是几种呢?这和第0行皇后的摆放位置有关系。为了递归程序更简洁,我们这里依然假设第1列有8中摆放,然后再对这每一种摆放进行检查,对不满足要求的摆放方式,终止这一路递归即可。顺着这个思路,依次递归下去,直到最后一行就可以了。
代码:
/*
* N皇后问题
* leetcode链接:https://leetcode.com/problems/n-queens/description/
* 暴力破解,自顶向下,循环递归,试图优化
*/
package leetcode;
import java.util.ArrayList;
public class N_Queens_51 {
public static void main(String[] args) {
int N = 8;
ArrayList<String[]> res = solveNQueens(N);
//遍历ArrayList
int num = 0; //可能的总的摆放数目
for(String[] temp : res) {
num++;
System.out.println("第" + num + "种摆放方法");
for(int i = 0; i < temp.length; i++) {
System.out.println(temp[i]);
}
}
}
private static ArrayList<String[]> solveNQueens(int N) {
ArrayList<String[]> res = new ArrayList<String[]>(); //保存最终结果
if(N == 0)
return null;
solveNQueens(N, 0, new int[N], res);
return res;
}
private static void solveNQueens(int N, int row,
int[] columnForRow, ArrayList<String[]> res) {
if(row == N) { //递归终止条件,对皇后的位置进行记录
String[] item = new String[N];
for(int i = 0; i < N; i++) {
StringBuilder strRow = new StringBuilder();
for(int j = 0; j < N;j ++) {
if(columnForRow[i] == j) strRow.append("& ");
else strRow.append("* ");
}
item[i] = strRow.toString();
}
res.add(item);
return;
}
for(int i = 0; i < N; i++) {
columnForRow[row] = i;
if(check(row,columnForRow)) {
solveNQueens(N, row+1, columnForRow, res);
}
}
}
private static boolean check(int row, int[] columnForRow) { //判断列是否重复,或者在同一对角线上
for(int i=0;i<row;i++) {
if(columnForRow[row]==columnForRow[i] || Math.abs(columnForRow[row]-columnForRow[i])==row-i)
return false;
}
return true;
}
}
这是一个可以独立运行的Java类,主函数中定义了皇后的个数N,使用类型为String[]的链表ArrayList去存储最终的摆放结果,String[]是长度为8的字符串数组,每个元素存储的是类似这样的字符串:“ * * * & * * * * ”,&表示当前行皇后的摆放位置。for打印最终结果。
solveNQueens()是两个重载函数,这样写是为了耦合更松,同时也方便递归函数的编写。solveNQueens(int N)接受参数,然后调用递归主体函数:solveNQueens(int N, int row, int[] columnForRow, ArrayList(这里泛型显示原因没写))
递归主体中,开始的这个if函数是递归的结束条件,内部两个fou循环是为了把最终结果以字符串数组的形式存储到ArrayList中。
for(int i = 0; i < N; i++) {
columnForRow[row] = i;
if(check(row,columnForRow)) {
solveNQueens(N, row+1, columnForRow, res);
}
}
这是递归的最重要部分,针对每一行的8个列,循环8次,每一次拜访后检查(check)与之前几行的皇后位置是否矛盾,check函数:
private static boolean check(int row, int[] columnForRow) { //判断列是否重复,或者在同一对角线上
for(int i=0;i<row;i++) {
if(columnForRow[row]==columnForRow[i] || Math.abs(columnForRow[row]-columnForRow[i])==row-i)
return false;
}
return true;
}
columnForRow[row]==columnForRow[i]判断列是否重复,Math.abs(columnForRow[row]-columnForRow[i])==row-i判断是否在同一个对角线上。
观察代码,会发现这就是一个循环和递归的组合,在程序设计中,顺序结构、选择结构、循环结枸是最基本的,但是递归用的也很多。顺序结构与递归的组合容易理解,但是选择(if-else)、循环(for /while)与递归的组合比较难以理解,比如本例就是一个典型的循环和递归的组合:递归不是在一条线路上一步步走下去,而是多条递归顺序开展,这样做,递归的次数显然是指数增长的,有兴趣的可以给N取不同的值,观察程序执行情况。
此题解决思想核心:暴力破解,循环 * 递归,试图优化
程序执行结果:
第91种摆放方法
* * * * * * * &
* * & * * * * *
& * * * * * * *
* * * * * & * *
* & * * * * * *
* * * * & * * *
* * * * * * & *
* * * & * * * *
第92种摆放方法
* * * * * * * &
* * * & * * * *
& * * * * * * *
* * & * * * * *
* * * * * & * *
* & * * * * * *
* * * * * * & *
* * * * & * * *