n后问题(回溯)
1.问题描述
在nxn格的棋盘上放置彼此不受攻击的n个皇后。按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线 上的棋子。n后问题等价于,在nxn格的棋盘上放置n个皇后,任何2个皇后不放在同一行或同一列或同一斜线上。
2.构建解空间:
①怎么表示解:(x1,x2,x3……,xn) xi表示(i,xi)即第i个皇后放在第i行的第xi列。
②显约束:xi=1,2,……n
③隐约束:
- 不在同一列:xi ≠ xj
- 不在同一正、反对角线:| xi - xj | ≠ | i - j |
- i ≠ j , i,j = 1,2,……n
④解空间树:排列树。(每一分量的取值范围随着位置变化而变化,比如前一个分量有n中选择,后一个分量有n-1种选择)
如果用蛮力处理,即在8x8的棋盘上安排出8个位置,这有 种安排法,逐一测试,即要检查将近个8元组用回溯法最多只要作次检查,即最多只要查40320个8元组实际上还少得多,对于8皇后问题,不受限结点是8皇后解空间树的结点总数的2.34%。因此,用回溯法处理比用枚举法好得多!
3.代码:
递归回溯:
class Queen {
friend int nQueen(int);
private:
bool Place(int k);
void Backtrack(int t);
int n;//皇后个数
int *x;//当前解
1ong sum;//当前已找到的可行方案数
};
bool Queen::Place(int k) {
for (int j=1; j < k; j++)
if ((abs(k-j) == abs(x[j]-x[k]))||(x[j] == x[k]))
return false;
return true;
}
这里用的是变种的子集树。其实用排列树的框架性能更好。
void Queen: :Backtrack(int t) {
if(t>n)
sum++;
else {
for (int i=1; i<= n; i++) {
x[t] = i;
if (Place(t))
Backtrack(t+1);
}
}
}
int nQueen(int n) {
Queen X;//初始化X
X.n = n;
X.sum = e;
int *p = new int [n+1];
for (int 1=0; i <= n; i++){
p[i] = 0;
X.x= p;
}
X. Backtrack(1);
delete[] p;
return x.sum;
}
迭代回溯
void:Queen Backtrack(){
x[1]=0;
int k=1;
while(k>0){
x[k]++;
while(x[k]<=n&&!place(k))//第k个分量选取合适的值
x[k]++;
if(x[k]<=n){//选取到了合适的值
if(k==n){//到达叶子节点,方案数++
sum++;
}
else{
k++;//下一个分量
x[k]=0;//初始化为0
}
}
else//找不到合适的取值,回溯
k--;
}
}
排列树法:
void Queen::Backtrack(int t) {
if(t>n)
sum++;
else {
for (int i=t; i<= n; i++) {
swap(x[t],x[i]);
if (Place(t))
Backtrack(t+1);
swap(x[t],x[i]);
}
}
}