一、问题描述:
二、解题思路:
三、代码与解析:
前提你已经懂得递归了
第一阶段:
分析:懂得解题思路,第一个感觉写出的代码
空间上:开了两个size*size二维数组,一个用来保存棋盘,一个用来保存其中的一个解法;以及多个标识数组
时间上:完全递归
import java.util.Arrays;
import java.util.Scanner;
public class Main {
private static int[][] chessBoard;// 原棋盘数组
private static int[][] resultChessBoard;// 保存一个N皇后解法的数组
private static int solutionCount = 0;
private static int size = 0;
private static boolean[] rowFlag;// 标识某一行是否已经放下了皇后
private static boolean[] columnFlag;// 标识某一列是否已经放下了皇后
private static boolean[] leftToRightFlag;// 标识某一斜行(方向:\)是否已经放下了皇后
private static boolean[] rightToLeftFlag;// 标识某一斜行(方向:/)是否已经放下了皇后
private static boolean isPrintResult = true;
public static void main(String args[]) {
Scanner cin = new Scanner(System.in);
size = cin.nextInt();
chessBoard = new int[size][size];
resultChessBoard = new int[size][size];
rowFlag = new boolean[size];
columnFlag = new boolean[size];
leftToRightFlag = new boolean[2 * size];
rightToLeftFlag = new boolean[2 * size];
nQueens();
printResult();
}
private static void nQueens() {
backtrace(0);
}
/**
* 回溯搜索到第i行
*
* @param i
*/
private static void backtrace(int i) {
if (i >= size) {
solutionCount++;
// 找到一个解之后,将棋盘数组拷贝一份,用于最后的输出
if (isPrintResult) {
for (int k = 0; k < size; k++) {
// 使用浅拷贝
resultChessBoard[k] = Arrays.copyOfRange(chessBoard[k], 0, size);
}
isPrintResult = false;
}
return;
}
for (int j = 0; j < size; j++) {
if (isValid(i, j)) {
// 将皇后放到该位置
chessBoard[i][j] = 1;
updateFlag(i, j, true);
// 进入下一行搜索
backtrace(i + 1);
// 递归出来后,说明这种放置方法不对,取消 将皇后放到该位置
updateFlag(i, j, false);
chessBoard[i][j] = 0;
}
}
}
/**
* 打印解法个数 并输出一个解法的皇后的位置
*/
private static void printResult() {
System.out.println(solutionCount);// 输出解法数量
for (int i = 0; i < size; i++)
for (int j = 0; j < size; j++) {
if (resultChessBoard[i][j] == 1) {
System.out.println((i + 1) + " " + (j + 1));
}
}
}
/**
* 更新皇后站位标识
*
* @param i
* @param j
* @param value
*/
private static void updateFlag(int i, int j, boolean value) {
rowFlag[i] = value;
columnFlag[j] = value;
leftToRightFlag[i - j + size - 1] = value;
rightToLeftFlag[j + i] = value;
}
/**
* 行和列的标识比较简单。有一点技巧的是斜行的标识。 <br>
* 方向(↙):i+j的值是相等的 <br>
* 方向(↘):i-j的值是相等的,由于相减会出现负数,所以这组标识的下标表示为:i-j+size-1.这样就可以了
*
* @param i
* @param j
* @return
*/
private static boolean isValid(int i, int j) {
return !rowFlag[i] && !columnFlag[j] && !leftToRightFlag[i - j + size - 1] && !rightToLeftFlag[j + i];
}
}
第二阶段:
分析:你会发现上面的代码其实在空间的开销是挺大的。既然我们已经用了四个数组去标识,而且我们是逐行遍历的,为什么还需要去用一 个size*size的数组呢?看上面的代码也可以知道,我们根本没有用的二维数组的什么性质。而且保存位置也没必要用一个size*size 的数组,不就是想保存坐标么,[size][2]就行了;这样空间上就省一些了
空间上:一个长度为size的数组表示棋盘,一个size*2的数组保存坐标;标识数组同上
时间上:完全递归
import java.util.Scanner;
/**
*
* 使用一维数组
*/
public class Main{
private static int[] chessBoard;// 一维数组即可
private static int[][] queenPosition;// 保存一个N皇后解法的坐标
private static int solutionCount = 0;
private static int size = 0;
private static boolean[] rowFlag;// 标识某一行是否已经放下了皇后
private static boolean[] columnFlag;// 标识某一列是否已经放下了皇后
private static boolean[] leftToRightFlag;// 标识某一斜行(方向:\)是否已经放下了皇后
private static boolean[] rightToLeftFlag;// 标识某一斜行(方向:/)是否已经放下了皇后
private static boolean isPrintResult = true;// 因为只输出一个答案,所以标识一下
public static void main(String args[]) {
Scanner cin = new Scanner(System.in);
size = cin.nextInt();
chessBoard = new int[size];
queenPosition = new int[size][2];
rowFlag = new boolean[size];
columnFlag = new boolean[size];
leftToRightFlag = new boolean[2 * size];
rightToLeftFlag = new boolean[2 * size];
nQueens();
if (solutionCount > 0)
printResult();
}
private static void nQueens() {
backtrace(0);
}
/**
* 回溯法
*
* @param i
*/
private static void backtrace(int i) {
if (i >= size) {
solutionCount++;
isPrintResult = false;
return;
}
for (int j = 0; j < size; j++) {
if (isValid(i, j)) {
// 将皇后放到该位置
chessBoard[j] = 1;
if (isPrintResult) {
queenPosition[i][0] = i + 1;
queenPosition[i][1] = j + 1;
}
updateFlag(i, j, true);
// 进入下一行搜索
backtrace(i + 1);
// 递归出来后,说明这种放置方法不对,取消 将皇后放到该位置
updateFlag(i, j, false);
if (isPrintResult) {
queenPosition[i][0] = 0;
queenPosition[i][1] = 0;
}
chessBoard[j] = 0;
}
}
}
private static void printResult() {
System.out.println(solutionCount);// 解法个数
for (int i = 0; i < size; i++) {
for (int j = 0; j < 2; j++) {
System.out.print(queenPosition[i][j] + " ");
}
System.out.println();
}
}
/**
* 更新皇后站位标识
*
* @param i
* @param j
* @param value
*/
private static void updateFlag(int i, int j, boolean value) {
rowFlag[i] = value;
columnFlag[j] = value;
leftToRightFlag[i - j + size - 1] = value;
rightToLeftFlag[j + i] = value;
}
/**
* 行和列的标识比较简单。有一点技巧的是斜行的标识。 <br>
* 方向(↙):i+j的值是相等的 <br>
* 方向(↘):i-j的值是相等的,由于相减会出现负数,所以这组标识的下标表示为:i-j+size-1.这样就可以了
*
* @param i
* @param j
* @return
*/
private static boolean isValid(int i, int j) {
return !rowFlag[i] && !columnFlag[j] && !leftToRightFlag[i - j + size - 1] && !rightToLeftFlag[j + i];
}
}
第三阶段:
分析:因为棋盘的大小是size*size,可以知道是对称的,所以我们可以考虑对称性,而不用完全递归
空间上:同第二阶段
时间上:考虑对称性,不用完全递归,搜索次数减少将近一半
import java.util.Scanner;
/**
*
* 使用一维数组 并且 使用 对称分析(只对第一行的对称性进行分析)
*/
public class Main {
private static int[] chessBoard;
private static int[][] queenPosition;
private static int solutionCount = 0;
private static int size = 0;
private static int topIndex = 0;// 标识第一行已经遍历到第几个格子了,用于对称的分析
private static boolean[] rowFlag;// 标识某一行是否已经放下了皇后
private static boolean[] columnFlag;// 标识某一列是否已经放下了皇后
private static boolean[] leftToRightFlag;// 标识某一斜行(方向:\)是否已经放下了皇后
private static boolean[] rightToLeftFlag;// 标识某一斜行(方向:/)是否已经放下了皇后
private static boolean isPrintResult = true;// 只要输出一组答案而已
public static void main(String args[]) {
Scanner cin = new Scanner(System.in);
size = cin.nextInt();
chessBoard = new int[size];
queenPosition = new int[size][2];
rowFlag = new boolean[size];
columnFlag = new boolean[size];
leftToRightFlag = new boolean[2 * size];
rightToLeftFlag = new boolean[2 * size];
nQueens();
if (solutionCount > 0)
printResult();
}
private static void nQueens() {
backtrace(0);
}
/**
* 回溯搜索到第i行
*
* @param i
*/
private static void backtrace(int i) {
if (i >= size) {
// 如果size只有1的话,很显然+1,如果第一行遍历的位置刚好处于中间,则就不应该+2,否则会重复
if (size == 1 || (size % 2 != 0 && topIndex == ((size + 1) / 2) - 1)) {
solutionCount++;
} else {
solutionCount += 2;// 所有对称情况+2
}
isPrintResult = false;
return;
}
int max = (i == 0 ? ((size + 1) / 2) : size);
for (int j = 0; j < max; j++) {
if (isValid(i, j)) {
if (i == 0)
topIndex = j;// 记录第一行已经遍历到第几列了
// 将皇后放到该位置
chessBoard[j] = 1;
if (isPrintResult) {
queenPosition[i][0] = i + 1;
queenPosition[i][1] = j + 1;
}
updateFlag(i, j, true);
// 进入下一行搜索
backtrace(i + 1);
// 递归出来后,说明这种放置方法不对,取消 将皇后放到该位置
updateFlag(i, j, false);
if (isPrintResult) {
queenPosition[i][0] = 0;
queenPosition[i][1] = 0;
}
chessBoard[j] = 0;
}
}
}
/**
* 打印解法个数 并输出一个解法的皇后的位置
*/
private static void printResult() {
System.out.println(solutionCount);// 输出解法个数
for (int i = 0; i < size; i++) {
for (int j = 0; j < 2; j++) {
System.out.print(queenPosition[i][j] + " ");
}
System.out.println();
}
}
/**
* 更新皇后站位标识
*
* @param i
* @param j
* @param value
*/
private static void updateFlag(int i, int j, boolean value) {
rowFlag[i] = value;
columnFlag[j] = value;
leftToRightFlag[i - j + size - 1] = value;
rightToLeftFlag[j + i] = value;
}
/**
* 行和列的标识比较简单。有一点技巧的是斜行的标识。 <br>
* 方向(↙):i+j的值是相等的 <br>
* 方向(↘):i-j的值是相等的,由于相减会出现负数,所以这组标识的下标表示为:i-j+size-1.这样就可以了
*
* @param i
* @param j
* @return
*/
private static boolean isValid(int i, int j) {
return !rowFlag[i] && !columnFlag[j] && !leftToRightFlag[i - j + size - 1] && !rightToLeftFlag[j + i];
}
}
OK!!我是一个算法菜鸟,今天心血来潮,去做了ACM的题,刚好做到这题,其实以前上课的时候已经做过了,但是今天还是耐下性子来,写了写,写完一个总感觉可以优化优化,于是就写了这三陀代码。其实也谈不上什么突破性的优化,请大牛不要鄙视。如有错误,请大家指正;如有更好的解法,求分享~~