1.问题描述
)
根据国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。八皇后问题研究如何将八个皇后放置在8×8的棋盘上,并且使皇后彼此之间不能相互攻击。寻找一共有多少种摆法,输出每种结果的具体摆法。
2.解决思路
为了简便介绍,我们将8皇后问题简述为4皇后。
)
根据题意我们首先想到的是遍历所有可能(深度优先搜索DFS),假设每行放一个棋子,共有4×4×4×4种可能,其状态空间树如下图所示,但这样时间复杂度会很大。
)
我们发现在遍历的过程中,有些搜索是没有必要的。例如在4皇后中,当第一行选择第一个位置时,第二行当选择第1个位置时我们发现不满足题目要求,即该节点无需再向状态空间树的更深层搜索,因为已经不满足题目的要求,因此需要剪枝。
)
每次遍历完当前节点时,需要回溯。
)
回溯法(非递归)的通用框架:
)
3.实现代码
3.1非递归法
N皇后问题的回溯算法:
)
限制函数Bound Function:PLACE
用于判断当前选择点与前面已经确定的皇后位置是否满足要求:不在同一斜线及同一行列。
)
import java.util.Arrays;
public class Queen8questionM2 {
static int num = 0; // 结果数量
static final int N = 8; // 解决8皇后问题
static int[] result = new int[N+1]; // 其中一个解的序列,补充:result[1] = 2 表示第1行选择第2个位置
// 主方法
public static void main(String[] args) {
NQUEENS(N);
System.out.println(num); // 输出结果总数量
}
public static void NQUEENS(int n){
int k = 1; //从第1行开始
result[k] = 0; // 清空该行的选择
while (k>0){
result[k] = result[k] + 1;
// 在 k 这一行中,找一个位置
while ((result[k]<=N)&&(!PLACE(k))){ // 遍历所有位置,直到可以与之前的位置共存(即满足题目要求)
result[k] ++; //寻找该行下一个位置
}
// 判断找到 k 这一列的合法位置result[k] 是否越界
if(result[k]<=N){ // 如果没越界
if(k == N){ // 如果以及到了最后一行
num ++;// 结果数量+1
System.out.println("第"+num+"个解,每行的位置序列为"+ Arrays.toString(result));;// 输出当前结果
}else {
k = k + 1; // 进入下一行,继续寻找
result[k] = 0; // 清空该行的选择,准备从下一行的第一个开始
}
}else{ // 越界
result[k] = 0; //清空该行的选择(此行可有可无,因为每次k=k+1时都顺便清空选择了)
k = k - 1; // 回溯
}
}
}
/**
* 判断当前k行的选择节点result[k]的与k行之前行选择的位置是否满足要求
* @param k
* @return
*/
private static boolean PLACE(int k) {
for (int i = 1;i<k;i++){
// 判断result[k]的正上方没有节点,并且不在一条斜线
if ((result[k]==result[i])||(Math.abs(k - i)==Math.abs(result[k]-result[i]))){
return false;
}
}
return true;
}
}
输出结果:
3.2 递归法
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* @Author wzpeng
* @Date 2021-12-17 下午3:27
* @Description 八皇后问题(递归回溯法)
*/
public class Queen8question {
static int num = 0; // 结果数量
static final int N = 8; // 解决8皇后问题
static int[] result = new int[N+1]; // 其中一个解的序列
public static void main(String[] args) {
queen(1);// 从第1行开始找
}
public static void queen(int i){
if(i > 8){ // 1-8 这8行已经找完
num++;// 找到一个解,
// 输出该解的序列(每行摆放位置)
System.out.println("第"+num+"个解,每行的位置序列为"+ Arrays.toString(result));
return;
}
for(int j=1;j <= N;j++){// 遍历每一行的8个位置(数字1-8)
// 选择j位置时
int k;
for(k = 1;k < i;k++){// 循环判断和之前每一行是否冲突
// 判断是否在同一列 以及 是否位于同一斜线
if (result[k]==j||Math.abs(k - i)==Math.abs(result[k]-j)){
break;// 剪枝
}
}
// 和之前的点都没有冲突时
if(k==i){
result[i] = j; // 记下该点的位置
queen(i+1); // 寻找下一层
}
}
}
}
输出结果:
4. 力扣51 N皇后
完整IDE代码,包含主函数
//按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。
//
// n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
//
// 给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
//
//
//
// 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 'Q' 和 '.' 分别代表了皇后和空位。
//
//
//
//
//
// 示例 1:
//
//
//输入:n = 4
//输出:[[".Q..","...Q","Q...","..Q."],["..Q.","Q...","...Q",".Q.."]]
//解释:如上图所示,4 皇后问题存在两个不同的解法。
//
//
// 示例 2:
//
//
//输入:n = 1
//输出:[["Q"]]
//
//
//
//
// 提示:
//
//
// 1 <= n <= 9
//
//
// Related Topics 数组 回溯 👍 1627 👎 0
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* N 皇后
* @author WZP
* @date 2023-02-04 16:24:07
*/
public class P51_NQueens{
public static void main(String[] args) {
//测试代码
Solution solution = new P51_NQueens().new Solution();
solution.solveNQueens(8);
}
//力扣代码
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
int num = 0;
public List<List<String>> solveNQueens(int n) {
int[] result = new int[n+1];
List<List<String>> finalresult = new ArrayList<>();
int k = 1; //从第1行开始
result[k] = 0; // 清空该行的选择
while (k>0){
result[k] = result[k] + 1;
// 在 k 这一行中,找一个位置
while ((result[k]<=n)&&(!PLACE(k,result))){ // 遍历所有位置,直到可以与之前的位置共存(即满足题目要求)
result[k] ++; //寻找该行下一个位置
}
// 判断找到 k 这一列的合法位置result[k] 是否越界
if(result[k]<=n){ // 如果没越界
if(k == n){ // 如果以及到了最后一行
num ++;// 结果数量+1
System.out.println("第"+num+"个解,每行的位置序列为"+ Arrays.toString(result));;// 输出当前结果
finalresult.add(generateString(result));
}else {
k = k + 1; // 进入下一行,继续寻找
result[k] = 0; // 清空该行的选择,准备从下一行的第一个开始
}
}else{ // 越界
result[k] = 0; //清空该行的选择(此行可有可无,因为每次k=k+1时都顺便清空选择了)
k = k - 1; // 回溯
}
}
return finalresult;
}
/**
* 将其中一个解的答案转换为List<String>
* @param queens
* @return List<String>
*/
public List<String> generateString(int[] queens){
// 注意,queens中的下标从1开始
List<String> result=new ArrayList<>();
for(int i =1;i < queens.length;i++){
char[] chars=new char[queens.length-1];
Arrays.fill(chars,'.');
chars[queens[i]-1]='Q';
result.add(String.valueOf(chars));
}
return result;
}
/**
* 判断当前k行的选择节点result[k]的与k行之前行选择的位置是否满足要求
* @param k
* @return boolean
*/
public boolean PLACE(int k,int[] result) {
for (int i = 1;i<k;i++){
// 判断result[k]的正上方没有节点,并且不在一条斜线
if ((result[k]==result[i])||(Math.abs(k - i)==Math.abs(result[k]-result[i]))){
return false;
}
}
return true;
}
}
//leetcode submit region end(Prohibit modification and deletion)
}
5. 力扣51 N皇后(递归,建议记忆)
递归法相对于非递归更便于理解,建议加强对回溯递归法框架的练习与记忆。
框架
int main(){
finalresultlist = []
backtrack(路径,1/选择列表)
}
void backtrack(路径,深度/选择列表)
if( (深度)满足结束条件/选择列表为空)
finalresultlist.add(路径)
return
for 选择 in 选择列表:
做选择(并判断当前选择是否满足情况)
backtrack(路径, 深度+1 /新选择列表)
撤销选择
}
提交代码
本地可运行的完整代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
* N 皇后
* @author WZP
* @date 2023-03-21
*/
public class P51_NQueens{
public static void main(String[] args) {
//测试代码
Solution solution = new P51_NQueens().new Solution();
solution.solveNQueens(4);
}
//力扣代码
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
List<List<String>> finalresult = new ArrayList<>();
int N;
public List<List<String>> solveNQueens(int n) {
int[] result = new int[n+1]; // 注意下标从1开始,result[n]表示第n行存在的位置是第result[n]个
N = n; // N皇后,保存N的值
backtrack(result,1); // 从第一行开始找
return finalresult;
}
public void backtrack(int[] result,int n){
// 满足结束条件
if( n > N ){
finalresult.add(generateString(result)); // 将数组结果转换为满足题目要求的string结果
return;
}
// 遍历所有选择
for(int i = 1; i <= N; i++){
// 做选择,排除不合法选择
result[n] = i;
if(!PLACE(n,result)) {
continue;// 排除
}
// 进入下一层决策树
backtrack(result,n + 1);
// 撤销选择
result[n] = 0;
}
}
/**
* 将其中一个解的答案转换为List<String>
* @param queens
* @return List<String>
*/
public List<String> generateString(int[] queens){
// 注意,queens中的下标从1开始
List<String> result=new ArrayList<>();
for(int i =1;i < queens.length;i++){
char[] chars=new char[queens.length-1];
Arrays.fill(chars,'.');
chars[queens[i]-1]='Q';
result.add(String.valueOf(chars));
}
return result;
}
/**
* 判断当前k行的选择节点result[k]的与k行之前行选择的位置是否满足要求
* @param k
* @return boolean
*/
public boolean PLACE(int k,int[] result) {
for (int i = 1;i<k;i++){
// 判断result[k]的正上方没有节点,并且不在一条斜线
if ((result[k]==result[i])||(Math.abs(k - i)==Math.abs(result[k]-result[i]))){
return false;
}
}
return true;
}
}
//leetcode submit region end(Prohibit modification and deletion)
}