在8*8的国际象棋棋盘上,皇后是威力较大的棋子,它能够攻击到与本身同行、同列以及同一斜线上的棋子,以下图,全部橙色格子上的棋子,均可能会被皇后攻击:java
而八皇后问题就是在8*8的棋盘上,找到合适的位置放置8个皇后,让它们不会相互攻击,并且须要找出这样的放法共有多少种。算法
回溯法就是当咱们肯定了一个问题的解空间的结构后,从根节点出发,以深度优先的方式去遍历解空间,找到合适的解。因此用此方法分析八皇后问题以下:数组
将棋盘看做0-7的平面直角坐标系,八皇后问题的解就是寻找八个点的坐标(i,j)。先抛开具体问题的约束来看,咱们要寻找的第一个坐标有64中可能,当第一个坐标肯定后,第二个坐标也有64中可能,第三个一样如此......所以,全部可能的解造成了一个64叉树(类比二叉树的说法),这就是有可能的解(64^8种可能),解空间结构是64叉树形状的。jvm
基于此解空间的结构,才能以深度优先的方式去遍历解空间,并寻找合适的解。工具
当咱们结合问题对解的约束来看,八皇后问题的解就是这个64叉树上某些从根节点到叶子节点的路径上的坐标。具体约束就是皇后的攻击规则(任意两点不能在同一直线或斜线上)。ui
也就是将皇后一个个摆上去,当咱们摆到第n个皇后时,若发现按照约束条件,任何位置都不能摆放,那就说明第n-1个皇后在当前位置不能获得正确的解,因此须要从新肯定第n-1个皇后的位置。若第n-1个皇后再无其余位置可摆放,则须要从新肯定第n-2个皇后的位置......spa
这就是回溯遍历解空间,在算法实现时,可使用递归或迭代进行回溯遍历,分别被称为递归回溯和迭代回溯。code
算法使用名为queen的二维int数组表示棋盘,数组的索引表示0-7的坐标,值为0表示空白,值为1表示皇后的摆放位置。递归
因为任意一行不能有两个皇后,因此每一行必须摆放一个,所以程序在每一行依次尝试:索引
package com.yawn.queen; /** * @author yawn */ public class QueenTest { private static final int SIZE = 8; private static int[][] queen = new int[SIZE][SIZE]; public static void main(String[] args) { for (int i = 0; i < SIZE; i++) { // 每一行都从0位置开始尝试放置 place(i, 0); } printQueen("结果为"); } /** * 第i行的放置 */ private static void place(int i, int start) { // 是否放置成功 boolean placed = false; for(int j = start; j < SIZE; j++) { if (check(i, j)) { queen[i][j] = 1; placed = true; printQueen("第 " + i + " 行放置"); break; } } if (!placed) { int index = seek(i - 1); // 去掉已经放置的皇后 queen[i-1][index] = 0; printQueen("回溯到第 " + (i-1) + " 行,从 " + (index+1) + " 开始"); place(i-1, index+1); place(i, 0); } } /// 如下为工具方法,内容单一,浅显易懂 /** * 输出棋盘 */ private static void printQueen(String action) { System.out.println("---> " + action); for (int[] cells : queen) { for (int cell : cells) { System.out.print(cell + "\t"); } System.out.println(); } } /** * 寻找第i行放置皇后的位置 */ private static int seek(int i) { for (int j = 0; j < SIZE; j++) { if (queen[i][j] == 1) { return j; } } return -1; } /** * 检查queen[i][j] 是否能够放置皇后 */ private static boolean check(int i, int j) { for(int m = 0; m < SIZE; m++) { for(int n = 0; n < SIZE; n++) { if (queen[m][n]==1 && (i == m || j == n || Math.abs(i - m) == Math.abs(j - n))) { // queen[m][n]==1 :(m,n)位置有皇后 // i == m :同行 // j == n :同列 // Math.abs(i - m) == Math.abs(j - n) :同一斜线(两个位置连线的斜率绝对值为1) // 若是知足if条件,则(i,j)不可放置皇后 return false; } } } return true; } }
求解过程当中的输出片断以下:
---> 第 5 行放置 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ---> 回溯到第 5 行,从 3 开始 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 ---> 第 5 行放置 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
咱们能够看出:第5行放置在了(5,2)位置,而后尝试放置第六行,结果第6行没法放置,因此回溯到第5行,从(5,3)位置继续尝试放置,并在(5,3)放置成功。
以上代码为咱们找到了问题的一个解,但咱们想知道一共有多少解存在,这就须要咱们稍微修改代码,具体以下:
在以上代码中,若找到一个可行的解以后,程序就会执行结束。但咱们若要找到全部的解,就须要在找到可行解后,继续让程序尝试下一个解便可。具体作法就是当最后一行均可以放置好皇后时,咱们只记录这个解,而后再让程序尝试当前位置的下一个位置,而不退出程序。
当遍历完全部可能的解以后,就能够中止程序。因为在递归调用过程当中,会产生很深的方法栈,致使栈满报错,因此棋盘不宜设置太大:
八皇后问题解的个数具体实现以下:
package com.yawn.queen; /** * @author yawn */ public class QueenTest2 { private static final int SIZE = 12; private static int count = 0; private static int[][] queen = new int[SIZE][SIZE]; public static void main(String[] args) { for (int i = 0; i < SIZE; i++) { place(i, 0); } } /** * 第i行的放置 */ private static void place(int i, int start) { if (i==0 && start== SIZE) { System.out.println("结果集已经遍历完毕,共找到结果数为:" + count); System.exit(0); } // 第i行是否放置成功 boolean rowPlaced = false; // 遍历第i行每一个位置是否能够放置皇后 for(int j = start; j < SIZE; j++) { if (check(i, j)) { queen[i][j] = 1; rowPlaced = true; // 最后一行放置成功,则获得一个结果,打印结果后从下一位置继续回溯遍历结果集 if (i== SIZE -1) { count++; printQueen("第" + count + "个结果,从(" + (SIZE-1) + "," + (j+1) + ")继续回溯寻找..."); // 去掉已经放置的皇后 queen[i][j] = 0; place(i, j+1); } break; } } // 若是第i行没有放置成功,则回溯到第i-1行(从新放置第i-1行) if (!rowPlaced) { int index = seek(i - 1); reset(i-1, index); // 回溯放置第i-1行 place(i-1, index+1); // 直到第i-1行放置成功,再开始放置第i行(最终将全部结果集遍历完毕后,只能使用exit(0)退出程序) place(i, 0); } } /// 如下为工具方法,内容单一,浅显易懂 /** * 输出棋盘 */ private static void printQueen(String string) { System.out.println("---> " + string); for (int[] cells : queen) { for (int cell : cells) { System.out.print(cell + "\t"); } System.out.println(); } } /** * 去掉(i,j)位置已经放置的皇后 */ private static void reset(int i, int j) { queen[i][j] = 0; } /** * 寻找第i行放置皇后的位置 */ private static int seek(int i) { for (int j = 0; j < SIZE; j++) { if (queen[i][j] == 1) { return j; } } return -1; } /** * 检查queen[i][j] 是否能够放置皇后 */ private static boolean check(int i, int j) { for(int m = 0; m < SIZE; m++) { for(int n = 0; n < SIZE; n++) { if (queen[m][n]==1) { if (i == m || j == n || Math.abs(i - m) == Math.abs(j - n)) { return false; } } } } return true; } }
---> 第91个结果,从(7,4)继续回溯寻找... 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 0 0 0 0 ---> 第92个结果,从(7,5)继续回溯寻找... 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 结果集已经遍历完毕,共找到结果数为:92
最终棋盘规格为8*8时,共有92个解。
原文参考做者的博客: