之前写过一篇也是关于N皇后的博客使用回溯法解决八皇后问题,不过当初使用的是二维数组存储。不仅是空间开销还是子集规模都非常大。
这里大致说下二维数组的子集规模计算方法
例如是4x4。如果按照二维数组存放,设二维数组为x[4][4]
x[0][0]有1(摆放)和0(不摆放)两种可能,对应的x[0][1]也有0和1的可能。所以可以用下面的树(排列树)来表示计算开销的规模,一共会有16层。所以解空间树规模(最底层)为2(n^2) = 216 = 65536
下面介绍仅使用一维数组就能解决此问题的方式。
设数组为x[4]。这代表什么意思呢?例如x[0] = 1,x[1] = 3,x[2] = 0,x[3] = 2
x[0] = 1就代表皇后摆放在第一行的第2个位置。
x[1] = 3就代表皇后摆放在第二行的第4个位置。
如下:
0 1 0 0
0 0 0 1
1 0 0 0
0 0 1 0
子集规模树(子集树),一共有4层,解空间树规模(最底层)为nn = 44 = 256,相比较于前面的65536已经是非常小了,如果n在大些,后者基本都可以忽略不计。
代码实现
public class NQueens {
/**
* 皇后个数
*/
private int n;
/**
* 当前解
*/
private int[] currentSolution;
/**
* 当前已经找到可行的方案个数
*/
private long sum;
public NQueens(int n) {
this.n = n;
this.sum = 0;
this.currentSolution = new int[n];
backtrack(0);
}
public boolean isSuitablePlacement(int t) {
// 因为是对称的,所以第一行只需要计算一半即可
if(t == 0) {
if((n + 1) / 2 <= currentSolution[t]) {
return false;
}
}
for(int i = 0; i < t; i++) {
// 判断列
if(currentSolution[i] == currentSolution[t]) {
return false;
}
// 判断斜向,这里通过斜率判断
if(Math.abs(currentSolution[i] - currentSolution[t]) == t - i) {
return false;
}
}
return true;
}
public void saveResult() {
// 这里可以打印放置情况
}
public void backtrack(int t) {
if(t >= n) {
sum++;
saveResult();
return;
}
for(int i = 0; i < n; i++) {
currentSolution[t] = i;
// 判断摆放是否合适,不合适就回溯
if(isSuitablePlacement(t)) {
backtrack(t + 1);
}
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
NQueens nQueens = new NQueens(12);
System.out.println(nQueens.sum);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
最后对比下一维和二维时间上的差距
12皇后
二维所需时间(单位毫秒): 1080
一维所需时间(单位毫秒): 207
13皇后
二维所需时间(单位毫秒): 4252
一维所需时间(单位毫秒): 1108
14皇后
二维所需时间(单位毫秒): 21358
一维所需时间(单位毫秒): 6331
当然了,还能运用减枝的方法进一步优化
使用回溯法和排列树(降维和减枝)解决N皇后问题