八皇后问题
问题介绍
八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n1×n1,而皇后个数也变成n2。而且仅当 n2 ≥ 1 或 n1 ≥ 4 时问题有解。
分析
1)向二维棋盘落子,为0的位置表示没有棋子,不为0的位置表示有棋子
2)先落子后判断还是先判断后落子?先判断后落子。先落子后判断又要重置
3)需要遍历所有结果。因此得到正确解单独处理,然后需要回溯。
4)行是跳动的,列是遍历的。我们下棋,先下第一行,然后直接到第二行,第二行还是遍历,找到合适位置,然后到第N-1行;
当第N行不满足,需要直接回退第N-1,而不用继续N+1。比如行的轨迹可能是0-1-2-3-4-5-6-5-6-5-4-5-6-7;
而列是需要完全遍历每一个位置,当所有位置不满足,才会回退,上一行继续遍历剩下列,知道每一行的所有列都遍历
5)判断冲突。当前位置跟其余位置列是否冲突,斜线是否冲突。不用检查行,因为我们只会在每行放一个皇后。检查也只需要往回检查。
使用二维棋盘模拟下棋解法:
package com.zby; /** * @author zby * @title QueueQuestionOrignal * @date 2019年6月13日 * @description */ public class QueueQuestionOrignal { private static int solutions; public static void main(String[] args) { resolveNQueue(8); resolveNQueue(9); resolveNQueue(10); resolveNQueue(11); resolveNQueue(12); resolveNQueue(13); resolveNQueue(14); resolveNQueue(15); } /** * N皇后问题 * * @param queueNum */ public static void resolveNQueue(int queueNum) { Long start = System.currentTimeMillis(); solutions = 0; int[][] chessboard = new int[queueNum][queueNum]; putQueue(chessboard, 0); System.out.printf("%d皇后有%d种解法,耗时%dms\n", queueNum, solutions, System.currentTimeMillis() - start); } /** * 我们只需要递增行,列必须全部遍历 * * @param chessboard 棋盘 * @param row 行 */ public static void putQueue(int[][] chessboard, int row) { if (row == chessboard.length) { // printchessboard(chessboard); solutions++; return; } for (int clos = 0; clos < chessboard.length; clos++) { if (!isConflict(chessboard, row, clos)) { // 存放的是当前列号+1,实际上只要不是0都可以 chessboard[row][clos] = clos + 1; putQueue(chessboard, row + 1); chessboard[row][clos] = 0; } } } /** * 只需要往回检查列,左斜线,右斜线 * * @param chessboard * @param row * @param clos * @return */ public static boolean isConflict(int[][] chessboard, int row, int clos) { int step = 1; while (row - step >= 0) { // 检查列 if (chessboard[row - step][clos] != 0) { return true; } // 检查左斜线 if (clos - step >= 0 && chessboard[row - step][clos - step] != 0) { return true; } // 检查右斜线 if (clos + step < chessboard.length && chessboard[row - step][clos + step] != 0) { return true; } step++; } return false; } /** * 打印棋盘 * * @param chessboard */ public static void printchessboard(int[][] chessboard) { for (int[] row : chessboard) { for (int chess : row) { System.out.print(chess + " "); } System.out.println(); } System.out.println("***************"); } }
结果:
8皇后有92种解法,耗时2ms 9皇后有352种解法,耗时2ms 10皇后有724种解法,耗时8ms 11皇后有2680种解法,耗时35ms 12皇后有14200种解法,耗时198ms 13皇后有73712种解法,耗时1228ms 14皇后有365596种解法,耗时8045ms 15皇后有2279184种解法,耗时55463ms
优化:
八皇后第一种解法 1 0 0 0 0 0 0 0 0 0 0 0 5 0 0 0 0 0 0 0 0 0 0 8 0 0 0 0 0 6 0 0 0 0 3 0 0 0 0 0 0 0 0 0 0 0 7 0 0 2 0 0 0 0 0 0 0 0 0 4 0 0 0 0 1) 观察8皇后的结果,每一行只有一个皇后,可以把结果简化为{1,5,8,6,3,7,2,4} 2)二维数组的作用是保存结果,那么使用一维数组完全满足了 3)皇后位置放的是所在列号+1,那么通过一维数组就可以方便知道每个皇后放在什么地方,第一个皇后放在第0行第0列,第二个放在第1行第4列,等等 3)皇后位置不是用的1,而是第几个皇后,那么判断斜线有了另一种简化方式。abs(当前列号+1-上列的值)==两个皇后行的距离,则冲突
使用一维数组解法:
/** * @author zby * @title QueueQuestionUpgrade * @date 2019年6月13日 * @description */ public class QueueQuestionUpgrade { private static int solutions; public static void main(String[] args) { resolveNQueue(8); resolveNQueue(9); resolveNQueue(10); resolveNQueue(11); resolveNQueue(12); resolveNQueue(13); resolveNQueue(14); resolveNQueue(15); } /** * N皇后问题 * * @param queueNum */ public static void resolveNQueue(int queueNum) { Long start = System.currentTimeMillis(); solutions = 0; int[] chessboard = new int[queueNum]; putQueue(chessboard, 0); System.out.printf("%d皇后有%d种解法,耗时%dms\n", queueNum, solutions, System.currentTimeMillis() - start); } /** * 我们只需要递增行,列必须全部遍历 * * @param chessboard 棋盘 * @param row 行 */ public static void putQueue(int[] chessboard, int row) { if (row == chessboard.length) { // printchessboard(chessboard); solutions++; return; } for (int clos = 0; clos < chessboard.length; clos++) { if (!isConflict(chessboard, row, clos)) { chessboard[row] = clos + 1; putQueue(chessboard, row + 1); chessboard[row] = 0; } } } /** * 只需要往回检查列,左斜线,右斜线 * * @param chessboard * @param row * @param clos * @return */ public static boolean isConflict(int[] chessboard, int row, int clos) { for (int i = 1; i <= row; i++) { if (chessboard[row - i] == clos + 1) { return true; } if (Math.abs(clos + 1 - chessboard[row - i]) == i) { return true; } } return false; } /** * 打印棋盘 * * @param chessboard */ public static void printchessboard(int[] chessboard) { for (int chess : chessboard) { System.out.print(chess + " "); } System.out.println(); System.out.println("***************"); } }
结果:
8皇后有92种解法,耗时1ms 9皇后有352种解法,耗时1ms 10皇后有724种解法,耗时7ms 11皇后有2680种解法,耗时32ms 12皇后有14200种解法,耗时180ms 13皇后有73712种解法,耗时1063ms 14皇后有365596种解法,耗时6899ms 15皇后有2279184种解法,耗时47099ms
结论:
使用一维数组实际上在递归的时间复杂度没有差别,但是判断冲突效率明显提高,因此耗时减少五分之一左右