八皇后问题是由19世纪数学家“搞死先生”(高斯先生)提出的,具体的问题是这样的:
在国际象棋的棋盘中(有8×8格)摆放8个皇后,这八个皇后不能相互攻击到(皇后的攻击方向很广:横着,竖着,斜着都能攻击),即8个皇后不能处于同行、同列、同一正反对角线上,这样就不能相互攻击到了。那么,这样的皇后占位的方法,一共有多少种呢?每一种是怎么样的呢?
解题思路:
要在棋盘中放置8个皇后,可以一个一个皇后放置到棋盘里,在放皇后的过程中,棋盘中被该皇后所能攻击到的位置要作出标记,以提示之后将要放置的皇后不能在标记的位置中放置,在没有被标记的地方,下一个皇后是可以放置的。
假如8个皇后仍未全部放置到棋盘中,而棋盘却被全部标记满,则表示前面的皇后放置位置不能满足要求,最好的解决办法就是将在他之前的那一个皇后位置改变。
以此类推,直到将所有的皇后都能放入到棋盘中为止。
根据解题思路,若不能放置皇后,则返回到它的前面一个皇后中进行修改位置。这就是一个回溯的过程,所以要解决八皇后摆放的问题,也就是可以用回溯算法来解决问题。
对于要回溯问题的解决方法,可以用迭代法和递归法来实现。在这里,我就用递归的方法来讨论这个八皇后问题的解决方法。
思考解题难点:
皇后占位后,所攻击的范围如何标记?
这个标记的方法是一个难点,也是一个解题的突破口,有的朋友认为,可以用一个二维数组进行标记,刚开始二维数组存放全为0,皇后占位后,皇后的位置标记为2,将该皇后所能攻击到的范围在此二维数组中用1来表示(0表示未被标记,1和2表示被标记而不能占位)。
这中方法是可行的,也是可以实现的,但还有一种更好的解决方法,这种方法可以不用二维数组,只用一维数组进行标记即可,那么我在这里就只介绍用一维数组进行标记的方法:
将8皇后放置在8×8的棋盘中,所以每一行,每一列都最多只能有一个皇后;
因此算法执行过程中,可以用一个递归过程的传参进行每一行的摆放问题;
因为皇后的攻击范围是同行、同列、同一正反对角线的;
所以用3个一维数组进行标记即可。(一个列标记数组,一个正对角线标记数组,一个反对角线标记数组)
为什么不用二维数组?
在这拿列的标记数组举例子,因为在标记的过程中,如果整列需要标记,那么将这一列都标记为1,若没被占位,用0标记即可,所以不需要用到二维数组浪费空间。
刚开始数组填充全为0,而后如果有占位就用1来表示。
那么列标记与正、反对角线标记中的数组下标有什么关联呢?
用i表示行,用j表示列,那么,正对角线、反对角线与i、j的关系为:正对角线的下标正好是i+j,反对角线的关系正好是i-j+size-1。而且正,反对角线的数组长度应该为2*棋盘边长。(size是棋盘的边长以及皇后的数目)
那么由此可以写代码了:
package queen; public class Queen { private final int size;// 定义棋盘大小(顺便用来表示皇后的数目) private int[] location;// 用来皇后所在列的位置 private int[] colsOccupied;// 占领的列 private int[] cross1Occupied;// 占领的正对角线 private int[] cross2Occupied;// 占领的反对角线 private static int count;// 计算方法有多少种 private static final int STATUS_OCCUPIED = 1; private static final int STATUS_OCCUPY_CANCELED = 0; // 初始化棋盘 public Queen(int size) { this.size = size; location = new int[size]; colsOccupied = new int[size]; cross1Occupied = new int[2 * size]; cross2Occupied = new int[2 * size]; } // 判断这个位置有木有被标记 private boolean isOccupied(int i, int j) { boolean a; a = (colsOccupied[j] == STATUS_OCCUPIED) || (cross1Occupied[i - j + size - 1] == STATUS_OCCUPIED) || (cross2Occupied[i + j] == STATUS_OCCUPIED); return a; } // 建立占领状态 private void setStatus(int i, int j, int flag) { colsOccupied[j] = flag; cross1Occupied[i - j + size - 1] = flag; cross2Occupied[i + j] = flag; } // 第一种显示皇后占位方法 private void printLocation1() { System.out.println("第" + count + "种摆放位置"); for (int i = 0; i < size; i++) { System.out.println("行:" + i + "列:" + location[i]); } } // 第二种显示皇后占位方法 private void printLocation2() { System.out.println("第" + count + "种摆放方式"); int[][] printLocation = new int[size][size]; for (int i = 0; i < size; i++) { printLocation[i][location[i]] = STATUS_OCCUPIED; } for (int[] i : printLocation) { for (int j : i) { System.out.print(j + " "); } System.out.println(); } } // 在第i行摆放皇后 public void place(int i) { for (int j = 0; j < size; j++) { if (!isOccupied(i, j)) { location[i] = j;// 摆放皇后 setStatus(i, j, STATUS_OCCUPIED); if (i < size - 1) { place(i + 1); } else { count++; printLocation1();// 用第一种显示方法显示 printLocation2();// 用第二种显示方法显示 } setStatus(i, j, STATUS_OCCUPY_CANCELED); } } } public void start() { place(0);// 从第0行开始摆放皇后 } public static void main(String[] args) { new Queen(8).start(); } }