n_queens_II
题意: 给定n个皇后和一个n*n的棋盘,找到有多少种相容的放置方法。
条件: 相容的意思是皇后相互之间不能攻击,根据国际象棋规定,皇后可以攻击它所在位置的行、列和斜对角线中所有的棋子,所以要能够实现相容,每一个皇后与前面的所有皇后不能在同一行、同一列、同一对角线。
举例
下图是8皇后问题的一个可行解。
分析: 解决n皇后问题,用到的方法即为回溯。回溯是五大常用算法之一,其余四个分别为:分治、贪心、动态规划和分支限界。回溯法首先需要定义一颗解空间树(其实如何得到解空间树本身也是一个需要讨论的问题); 其次会有一定的约束条件,根据约束条件可以进行剪枝,删除无用的; 遍历解空间树采用深度优先策略,直到叶子节点也遍历过,则从根到叶子节点的路径即为一个可行解。找到一组可行解或者不满足约束条件时,都要进行回溯,即返回到上一个状态继续遍历。当遍历过整个解空间树后算法终止。
对于n皇后问题,我们一般按行来放置皇后,即一行只能有一个皇后。那么可以有 nn 种放置方法,即3皇后问题就可以用27种不同的放置方法,那么对应的解空间树也就有这么多种状态。那有这么多种状态不就是个np难的问题了?其实不然,因为有约束条件!n皇后问题约束条件即为不能在一列或者斜对角线,不满足则剪枝,由约束条件可以剪去绝大多数的情况,从而极大的提升算法效率。
n皇后是个非常经典的问题,读者需要了解回溯的概念和两种实现方式(递归和迭代),在这里我给出一种递归的思路。
代码
//将棋盘看成一个二维数组
public int total = 0; //全局变量,表示总共有多少种放置方法
public int totalNQueens(int n) {
if(n <= 0) {
return 0;
}else if(n == 1){
return 1;
}else{
for(int i = 0; i < n; i++){
Map<Integer, Integer> map = new HashMap<>();
map.put(0, i); //将二维数组的坐标放到map中,横坐标为key,纵坐标为value
totalNQueen(n, 1, map);
}
return total;
}
}
//递归实现回溯
public int totalNQueen(int n, int row, Map<Integer, Integer> map){ //row表示层数
if(row >= n) return total + 1; //如果遍历过解空间树的叶子节点,说明该方法可行,将结果加1
else{
for(int i = 0; i < n; i++){
boolean satisfied = true;
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
int key = entry.getKey();
int value = entry.getValue();
if(value == i || Math.abs(i - value) == (row - key)){ //判断该节点是否满足约束
satisfied = false; //不满足约束直接回溯
break;
}
}
if(satisfied){ //满足约束继续找下一层
map.put(row, i);
total = totalNQueen(n, row + 1, map);
map.remove(row);
}
}
return total;
}
}
再提供一个执行效率更高的版本,其实思路都是一样的,不过时间和空间复杂度高一些
public class Solution {
int solutions;
int[] points; //index is m, value is n, -1 means unknown
public int totalNQueens(int n) {
points = new int[n];
dfs(0,n);
return solutions;
}
//row 进行dfs的行数 n=n
public boolean dfs(int row,int n) {
if(row == n) {
solutions++;
return true;
}
for(int i = 0;i < n;i++) {
if(!check(row,i)) continue;
points[row] = i;
dfs(row+1,n);
}
return false;
}
boolean check(int row,int i) {
for(int p = 0;p < row;p++) {
if(points[p] == i)
return false;
if(row-p == points[p]-i)
return false;
if(p-row == points[p]-i)
return false;
}
return true;
}
}
n_queens
题意: 该问题要求在II的基础上输出,放置的地方用’Q’,空的地方用’.’表示。
举例: 4皇后问题有2个不同解,如下
[
[“.Q..”, // Solution 1
“…Q”,
“Q…”,
“..Q.”],
[“..Q.”, // Solution 2
“Q…”,
“…Q”,
“.Q..”]
]
分析: 该题只需要在II的基础上稍作修改即可,大体思路还是一致的,只不过在满足条件时不是计算个数,而是要将可行解加入到一个ArrayList中,最后返回该ArrayList即可。
代码
public ArrayList<String[]> solveNQueens(int n) {
ArrayList<String[]> result = new ArrayList<>(); //返回的结果
String[] strs = new String[n]; //初始化String的数组,起始都是“.”
char[] chs = new char[n];
for(int i = 0; i < n; i++){
chs[i] = '.';
}
String str = String.valueOf(chs);
for(int i = 0; i < n; i++){
strs[i] = str;
}
Map<Integer, Integer> map = new HashMap<>();
result = solveNQueens(n, 0, map, result, strs);
return result;
}
public ArrayList<String[]> solveNQueens(int n, int row, Map<Integer, Integer> map, ArrayList<String[]> result, String[] strs){
if(row >= n){
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
int key = entry.getKey();
int value = entry.getValue();
char[] chs = strs[key].toCharArray();
chs[value] = 'Q';
strs[key] = String.valueOf(chs);
}
String[] temps = new String[n];
for(int i = 0; i < temps.length; i++){
temps[i] = strs[i];
}
result.add(temps);
for(int i = 0; i < n; i++){
strs[i] = strs[i].replace("Q", ".");
}
return result; //如果遍历过解空间树的叶子节点,说明该方法可行
}
else{
for(int i = 0; i < n; i++){
boolean satisfied = true;
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
int key = entry.getKey();
int value = entry.getValue();
if(value == i || Math.abs(i - value) == (row - key)){ //判断该节点是否满足约束
satisfied = false; //不满足约束直接回溯
break;
}
}
if(satisfied){ //满足约束继续找下一层
map.put(row, i);
result = solveNQueens(n, row + 1, map, result, strs);
map.remove(row);
}
}
return result;
}
}