八皇后问题也算是比较经典的回溯算法的经典案例。题干描述如下:
在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法
对此首先我们使用array[][]来构建一个棋盘,然后尝试落子,此时算法如下:
/** * 寻找皇后节点 * @param row * @param size */ public static void findQueen(int row, int size) { // 如果皇后行已达到最后一行,结果数量自增,并未输出当前棋盘情况 if (row == size) { resultCount++; print(size); return; } // 递归回溯 for (int column = 0; column < size; column++) { // 检查当前节点是否可以放皇后 if (check(row, column, size)) { // 使用皇后占位 array[row][column] = 1; // 查找下一列的可占位节点 findQueen(row + 1, size); // 继续尝试同一行其他列占位前,清空临时占位 array[row][column] = 0; } } }
其中check方法实现如下:
/** * 判断节点是否合适 * * @param row 行 * @param column 列 * @param size 棋盘尺寸 * @return */ public static boolean check(int row, int column, int size) { // 遍历 for (int rowTemp = row - 1; rowTemp >= 0; rowTemp--) { // 验证纵向 if (array[rowTemp][column] == 1) { return false; } int offset = row - rowTemp; int columnLeft = column - offset, columnRight = column + offset; // 验证左向 if (columnLeft >= 0 && array[rowTemp][columnLeft] == 1) { return false; } // 验证右向 if (columnRight < size && array[rowTemp][columnRight] == 1) { return false; } } return true; }
完整算法如下:
public class EightTest { public static int[] array;//棋盘,放皇后 public static int resultCount = 0;//存储方案结果数量 public static void main(String[] args) { Stopwatch stopwatch = Stopwatch.createStarted(); int size = 14; array = new int[size]; findQueen(0, size); System.out.println(size + "皇后问题共有:" + resultCount + "种可能,耗时:"+stopwatch.stop().toString()); } /** * 寻找皇后节点 * * @param row * @param size */ public static void findQueen(int row, int size) { // 如果皇后行已达到最后一行,结果数量自增,并未输出当前棋盘情况 if (row == size) { resultCount++; print(size); return; } // 递归回溯 for (int column = 0; column < size; column++) { // 检查当前节点是否可以放皇后 if (check(row, column, size)) { // 使用皇后占位 array[row] = column; // 查找下一列的可占位节点 findQueen(row + 1, size); // 继续尝试同一行其他列占位前,清空临时占位 array[row] = 0; } } } /** * 判断节点是否合适 * * @param row 行 * @param column 列 * @param size 棋盘尺寸 * @return */ public static boolean check(int row, int column, int size) { // 遍历 for (int rowTemp = row - 1; rowTemp >= 0; rowTemp--) { // 验证纵向 if (array[rowTemp] == column) { return false; } int offset = row - rowTemp; int columnLeft = column - offset, columnRight = column + offset; // 验证左向 if (columnLeft >= 0 && array[rowTemp] == columnLeft) { return false; } // 验证右向 if (columnRight < size && array[rowTemp] == columnRight) { return false; } } return true; } public static void print(int size) {//打印结果 System.out.println("方案" + resultCount + ":"); for (int i = 0; i < size; i++) { for (int m = 0; m < size; m++) { System.out.print((array[i] == m ? 1 : 0) + " "); } System.out.println(); } System.out.println(); } }
继续优化时间复杂度,我们仔细观察输出的棋盘信息,举个例子:
方案92: 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
来考虑是否能把每一列当做一个二进制数来处理,这样我们判断第N列时,其实对于向上判断,只需要进行位运算即可。具体代码如下:
public class EightTest { public static Integer size = 15; // 棋盘,放皇后 public static int[] array = new int[size]; // 映射 public static int[] arrayMapping = new int[size]; // 映射 public static int[][] arrayMappingDeep = new int[size][size]; // 记录每一列已经被占用的值——2进制 public static int a = 0; // 存储方案结果数量 public static int resultCount = 0; public static void main(String[] args) { Stopwatch stopwatch = Stopwatch.createStarted(); arrayMapping[0] = 0b1; for (int i = 1; i < size; i++) { arrayMapping[i] = arrayMapping[i - 1] << 1; } for (int i = 0; i < size; i++) { for (int j = 0; j < size; j++) { arrayMappingDeep[i][j] = arrayMapping[i] | (i + j >= size ? 0 : arrayMapping[i] << j) | arrayMapping[i] >> j; } } findQueen(0, size); System.out.println(size + "皇后问题共有:" + resultCount + "种可能,耗时:" + stopwatch.stop().toString()); } /** * 寻找皇后节点 * * @param row * @param size */ public static void findQueen(int row, int size) { // 如果皇后行已达到最后一行,结果数量自增,并未输出当前棋盘情况 if (row == size) { resultCount++; print(size); return; } // 递归回溯 for (int column = 0; column < size; column++) { a = 0; for (int i = 0; i < row; i++) { a = a | arrayMappingDeep[array[i]][row - i]; } // 检查当前节点是否可以放皇后 if (check(column)) { // 使用皇后占位 array[row] = column; // 查找下一列的可占位节点 findQueen(row + 1, size); // 继续尝试同一行其他列占位前,清空临时占位 array[row] = 0; } } } /** * 判断节点是否合适 * * @param column 列 * @return */ public static boolean check(int column) { return (a & arrayMapping[column]) == 0; } public static void print(int size) {//打印结果 System.out.println("方案" + resultCount + ":"); for (int i = 0; i < size; i++) { for (int m = 0; m < size; m++) { System.out.print((array[i] == m ? 1 : 0) + " "); } System.out.println(); } System.out.println(); } }
本地执行没跑出明细差别,leetcode上前者写法7ms,后者4ms。感觉如果要刷leetcode,需要修改输出格式增加缓存来拼一下时间。
原文地址:https://www.cnblogs.com/fbw-gxy/p/12584308.html
时间: 2024-10-12 07:54:07