N皇后问题【java】【回溯法】
N皇后问题概述
要想解决n皇后问题,首先要明白什么是n皇后问题。
在本篇文章中,借助 leetcode 51.N皇后问题 进行解析。
N皇后问题题目:
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
由上述题目的描述,简而言之,就是:
在一个棋盘上,一个皇后(棋子)的 同一列、同一行、以皇后为中心的两条对角线 都不能有任何棋子,求所有可能的情况,这就是N皇后问题。
N皇后问题java代码解析
由于我使用了回溯法解决了N皇后问题,所以本篇文章详细讲述如何用回溯法解决n皇后问题。
本文仅仅代表作者的思路,可能有大佬的思路更好一些,欢迎大家发表想法。
在阅读本文之前,建议大家首先了解回溯法,本文会尽量让大家了解回溯法的解题技巧。
首先,N皇后问题的回溯代码大致可以分为调用函数(这里就不使用java方法的概念了,感觉函数更顺口一点)、回溯算法、判断选择三个方法。
调用函数
顾名思义,调用函数就是调用回溯算法解决问题的函数,该函数篇幅很短,主要用于定义路径、调用回溯函数、返回结果三个作用。
class Solution {
//res表示result,保存最后的结果
List<List<String>> res = new ArrayList<>();
//调用函数
public List<List<String>> solveNQueens(int n) {
//记录正确的结果
ArrayList<StringBuilder> track = new ArrayList<>();
//回溯算法
backtrack(n, 0, track);
//返回结果
return res;
}
}
解释两点:
- 返回类型为List<List<String>>,是因为返回值是一个集合,集合中的每个元素都是一种正确的解法,而每一种解法都是一个List<String>类型,如:
[“.Q…”,“…Q”,“Q…”,“…Q.”],这就是一个棋盘,一个正确的结果。 - 记录正确的结果时,我使用了List<StringBuilder>而不是List<String>,是因为将 ‘.’ 修改为 ‘Q’ 时,StringBuilder 更简单一点。
回溯算法
/**
* 回溯算法
* @param n N皇后问题的 N
* @param row n*n 棋盘上的第几行
* @param track 路径(结果)
*/
public void backtrack(int n, int row, List<StringBuilder> track) {
//触发结束条件
if(track.size() == n) {
List<String> list = chg(track);
res.add(list);
return;
}
StringBuilder sb = new StringBuilder();
for(int i = 0;i < n;i++) {
sb.append(".");
}
for(int i = 0;i < n;i++) {
//排除不合法的选择
if(!isValid(n, track, row, i)) {
continue;
}
//做选择
sb.setCharAt(i, 'Q');
track.add(sb);
//进入下一层决策树
backtrack(n, row + 1, track);
//取消选择
sb.setCharAt(i, '.');
track.remove(track.size() - 1);
}
}
分析:
//触发结束条件
if(track.size() == n) {
List<String> list = chg(track);
res.add(list);
return;
}
这是回溯算法必须要有的,如果没有条件,那么回溯算法将会一直递归;n皇后问题的条件是:
当 n * n 大的棋盘上放置了 n 个皇后时,这种情况正确,递归结束并加入最终结果中。(chg函数是将 List<StringBuilder> 类型转换为 List<String> 类型)
//sb 是棋盘中的某一行。
StringBuilder sb = new StringBuilder();
//进行初始化(“.”表示没有放棋子)
for(int i = 0;i < n;i++) {
sb.append(".");
}
//从第 0 列开始,第 n 列结束
for(int i = 0;i < n;i++) {
//排除不合法的选择
if(!isValid(n, track, row, i)) {
continue;
}
//尝试(尝试将第 row 行的第 i 列放置皇后)
sb.setCharAt(i, 'Q');
track.add(sb);
//进入下一层决策树(进入下一行继续尝试)
backtrack(n, row + 1, track);
//取消选择(将皇后取消,并删除该行)
sb.setCharAt(i, '.');
track.remove(track.size() - 1);
}
判断选择
public static boolean isValid(int n, List<StringBuilder> track, int row, int col) {
//检查列中是否有皇后互相冲突
for(int i = 0;i < row;i++) {
//如果上方有‘Q’,不能放置,返回结果false。
if(track.get(i).charAt(col) == 'Q') {
return false;
}
}
//检查右上方是否有皇后互相冲突
for(int i = row - 1, j = col + 1;i >= 0 && j < n;i--, j++) {
if(track.get(i).charAt(j) == 'Q') {
return false;
}
}
//检查左上方是否有皇后互相冲突
for(int i = row - 1, j = col - 1;i >= 0 && j >= 0;i--, j--) {
if(track.get(i).charAt(j) == 'Q') {
return false;
}
}
return true;
}
//将 List<StringBuilder> 转换为 List<String>,可能多此一举,看看就好
public static List<String> chg(List<StringBuilder> track) {
List<String> list = new ArrayList<>();
for(int i = 0;i < track.size();i++) {
list.add(new String(track.get(i)));
}
return list;
}
到这里,n皇后的 java 代码部分就结束了。回溯题目大同小异,这里放一下 backtrack 的大致框架:
public static void backtrack(int n, List<StringBuilder> track) {
//触发结束条件(结束递归)
if(......) {
//向结果中添加一种解决方案
res.add(list);
//返回
return;
}
for(int i = 0;i < n;i++) {
//排除不合法的选择
if(...) {
continue;
}
//做选择
track.add(...);
//进入下一层决策树
backtrack(n, track);
//取消选择
track.remove(track.size() - 1);
}
}
大致框架就这样子,但是要根据实际情况灵活变通。
至此,n 皇后问题就解决啦,感谢观看!