1
引言
这次来聊聊回溯算法的n皇后问题。第一次接触n皇后问题时,还是在5年前的一天,那时候根本看不懂,草草跳过.现在面临换工作,终于还是得硬着头皮去啃...
2
知识回顾
该问题描述的是在一个n×n的棋盘上放置n个皇后,要求任何两个皇后都不能位于同一行、同一列或同一斜线上。
解决n皇后问题的目标是找出所有可能的不同布局方式,即找到满足条件的所有解。由于随着n值增大,问题的复杂性急剧增加,通常使用回溯算法这样的搜索算法来穷举所有可能的配置,逐步尝试并验证每一步放置是否有效,无效则回溯至前一步继续搜索其它可能性.
3
解决思路
确定易于搜索的解空间结构;
设置初始状态及终止条件;
以深度优先的方式搜索解空间,并且在搜索过程中使用剪枝函数避免无效搜索。
回溯算法就是暴力尝试法,一条路一条路去试.
4
具体实现(打印所有结果)
定义数据结构
public static List<List<String>> solveNQueens(int n) {
// 定义一个n*n的棋盘,设置默认值为.
char[][] chessboard = new char[n][n];
for (char[] c : chessboard) {
Arrays.fill(c, '.');
}
backTrack(n, 0, chessboard);
return res;
}
回溯算法主要逻辑
public static void backTrack(int n, int row, char[][] chessboard) {
// row从0开始,当row能到达n时,说明布置的所有皇后均满足情况
if (row == n) {
res.add(Array2List(chessboard));
return;
}
for (int col = 0; col < n; ++col) {
if (isValid(row, col, n, chessboard)) {
chessboard[row][col] = 'Q';
backTrack(n, row + 1, chessboard);
chessboard[row][col] = '.';
}
}
}
判断当前位置皇后是否合法
public static boolean isValid(int row, int col, int n, char[][] chessboard) {
// 检查列
//
for (int i = 0; i < row; ++i) { // 相当于剪枝
if (chessboard[i][col] == 'Q') {
return false;
}
}
// 检查45度对角线
for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
// 检查135度对角线
for (int i = row - 1, j = col + 1; i >= 0 && j <= n - 1; i--, j++) {
if (chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
将二维数据转化为集合
public static List<String> Array2List(char[][] chessboard) {
List<String> list = new ArrayList<>();
for (char[] c : chessboard) {
list.add(String.copyValueOf(c));
}
return list;
}
测试样例
public static void main(String[] args) {
List<List<String>> lists = solveNQueens(4);
for (List<String> list : lists) {
System.out.println("--------------");
for (String s : list) {
System.out.println(s);
}
}
}
5
具体实现(打印结果总数)
public class Class01_NQueens {
public static int num1(int n){
if(n<1){
return 0;
}
int []record=new int[n];
return process1(0,record,n);
}
/**
*
* @param i 目前来到了第i行
* @param record 表示之前的行,存储皇后的位置,一定不共行,不共列,不共斜线
* @param n 是n*n的棋盘
* @return 返回最终的摆法数
*/
private static int process1(int i, int[] record, int n) {
// n是终止行,棋盘的下一行,棋盘的第一行是从数组索引0开始的
if(i==n){
return 1;
}
int res=0;
for (int j = 0; j < n; j++) {
if(isValid(record,i,j)){
record[i]=j;
res+=process1(i+1,record,n);
}
}
return res;
}
// i 行 j 列 record 所有的结果
private static boolean isValid(int[] record, int i, int j) {
for (int k = 0; k < i; k++) {
if(j==record[k]|| Math.abs(record[k]-j)==Math.abs(i-k)){
return false;
}
}
return true;
}
测试样例:
public static void main(String[] args) {
System.out.println(num1(4));
}
// 输出结果为 2
5
笔记扩展
什么是剪枝函数
剪枝函数是用来削减不必要的搜索或计算资源消耗,同时保持或尽可能接近最优解决方案的有效工具,简单的理解就是边界条件,约束函数.
感觉不错的话,就请点赞支持一下吧!
微信公众号|大数据进阶小铺