n皇后问题其实就是一个全排列问题,列的顺序固定下,第一列可选择全部行,第二列可选择剩余的n-1行,一直到最后一列选择仅剩的最后一行的一个全排列,当到达递归终点时,表明一个全排列已经结束,然后开始检查这个全排列是否合法,不合法就立即返回,合法就计数加一。
等所有可能的排列都走尽时,得到的计数就是所有可能的结果了。
代码如下:
bool isUsed[maxSize] = {false}; //表明这个数字(行号)是否被用过
int p[maxSize] = {0}; //存储排列的数组
int n; //n皇后问题
int count = 0; //总计数
void nQueueProblem(int index){ //index表示当前进行的是排列中的第index位选择
if(index == n + 1){
bool flag = true;
for(int i = 1 ; i <= n ; ++i) {
bool needBreak = false; //内层循环判断非n皇后排列时,可以直接跳出外层循环
for (int j = i + 1; j <= n; ++j)
if (abs(i - j) == abs(p[i] - p[j])) { //对角线就是任意和主对角线或副对角线平行的线,这个公式涵盖了所有对角线情况
flag = false;
needBreak = true;
break;
}
if(needBreak)
break;
}
if(flag)
++count;
return ;
}
for(int i = 1 ; i <= n ; ++i){
if(isUsed[i] == false){ //如果当前数字i还未使用也即行号i还未选择过
p[index] = i; //选择i,也即排列中index位选择第i行
isUsed[i] = true; //数字i已经用过了(行号i已经选择过了)
nQueueProblem(index+1); //继续排列中下一个空位的选择
isUsed[i] = false; //返回后数字i重新回归未用过的集合中
}
}
}
int main(int argc, const char * argv[]) {
cin >> n;
nQueueProblem(1);
cout << count;
}
以上方法还有改进的空间,比如第一次排列的结果是123456789…n,这个排列显然是对角线式的不满足n皇后条件,而且从2开始就已经不满足了,因为剩余后面的各种组合都是无效组合,因此可以在递归的过程中进行优先排定剔除掉不可能的排列,以节省大量无效操作提高效率:
代码如下:
void nQueueProblem(int index){
if(index == n + 1){ //安全的到达最后一步,表明形成的已经是一个n皇后的排列了
++count;
return;
}
for(int i = 1; i <= n ; ++i){
if(isUsed[i] == false) {
bool flag = true; //先假定这个数字是可用的,也即这个数字加入排列后不会破坏n皇后条件
for(int pre = 1 ; pre < index ; ++pre) //检查这个数字是否真的可用,也即假设当前选用了i后,看是否会破坏n皇后条件
if(abs(pre - index) == abs( i - p[pre])){ //如果加入了数字i(选用了行号i)破坏了n皇后条件
flag = false;
break;
}
if (flag) { //如果这个数字(或称为行号)真的是可用的
p[index] = i; //使用这个数字,将其放入排列
isUsed[i] = true;
nQueueProblem(index + 1);
isUsed[i] = false;
}
}
}
}