上一篇博客使用回溯法和子集树(降维法)解决N皇后问题
运用了降维,极大的提高了计算效率。不过能不能再精益求精呢?~能的,还能继续优化。前面运用了排列树,对于4x4来说,解空间树规模为nn = 44 = 256。这里我们采用减枝进一步优化。
思路很简单,假如x[0] = 1,则x[1],x[2],x[3]就不可能在等于1。
x[0] = 1就代表皇后摆放在第一行的第2个位置。则后面2,3,4行就不可能摆放在第二个位置。
这里就有点像对0123进行排列组合,有1023、1032等等,然后一个一个验证是否符合摆放要求。
所以由原来的子集树转换为了排列树。
子集规模树(排列树),一共有4层,解空间树规模(最底层)为n! = 4! = 24。
代码实现
/**
* 回溯 & 降维(一维) & 排列树
*/
public class NQueens3 {
/**
* 皇后个数
*/
private int n;
/**
* 当前解
*/
private int[] currentSolution;
/**
* 当前已经找到可行的方案个数
*/
private long sum;
public NQueens3(int n) {
this.n = n;
this.sum = 0;
this.currentSolution = new int[n];
// 这里要初始,避相同
for(int i = 0; i < n; i++) {
currentSolution[i] = i;
}
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 swap(int i, int j) {
int temp = currentSolution[i];
currentSolution[i] = currentSolution[j];
currentSolution[j] = temp;
}
public void backtrack(int t) {
if(t >= n) {
sum++;
saveResult();
return;
}
for(int i = t; i < n; i++) {
swap(i, t);
// 判断摆放是否合适,不合适就回溯
if(isSuitablePlacement(t)) {
backtrack(t + 1);
}
// 还原位置
swap(i, t);
}
}
public static void main(String[] args) {
long start = System.currentTimeMillis();
NQueens3 nQueens = new NQueens3(14);
System.out.println(nQueens.sum);
long end = System.currentTimeMillis();
System.out.println(end - start);
}
}
12皇后
使用子集树所需时间(单位毫秒):194
使用排列树(剪枝)所需时间(单位毫秒):119
13皇后
使用子集树所需时间(单位毫秒):1113
使用排列树(剪枝)所需时间(单位毫秒):768
14皇后
使用子集树所需时间(单位毫秒):5918
使用排列树(剪枝)所需时间(单位毫秒):3890