n皇后问题(回溯法+二进制优化)

n皇后问题描述

在一个n×n的国际象棋棋盘上放置n个皇后,使得她们中任意两个之间都不会互相“攻击”,即任意两个皇后不可以放在同一行、同一列、同一斜线上。
输入:n
输出:有多少种满足条件的放置方法。

回溯法

解空间

利用约束条件,一维数组即可。
x[i] 表示第i行的皇后的列数为x[i]。

约束条件

  • 不同行:数组x的下标保证是不重复的。
  • 不同列:x[i] ! = x[j] (i<=n,j<=n,i != j)
  • 不同对角线:abs(x[i]-x[j]) ! = abs(i-j)

对角线有两种,向左斜和向右斜,都是45°,那么怎么得出这个约束条件的呢?
利用斜率是±1得出的,知道两个点的横纵坐标,自然可以得出他们的斜率,(x[i]-x[j])/(i-j)=±1,加个绝对值符号,|x[i]-x[j]|/|i-j|=1,即abs(x[i]-x[j])=abs(i-j);

填到第k行,就要与前1~(k-1)行进行比较,看是否满足条件。

int judge(int k){
	for(int i=1;i<k;i++){
		if(x[i]==x[j]||abs(x[i]-x[j]) ! = abs(i-j))
			return 0;
	}
	return 1;
}

代码

#include<bits/stdc++.h>
using namespace std;

int n,sum;  // n表示n*n的棋盘,sum表示结果
int x[15];  // 数组存位置
bool judge(int t){  // 与前k-1行皇后进行比较,看是否满足条件
    for(int i=1;i<t;i++){
        if(x[i]==x[t]||abs(t-i)==abs(x[i]-x[t]))
            return 0;
    }
    return 1;
}
void dfs(int t){
    if(t>n){ // 说明已经完成一次放置
        sum++;
    }
    else{
        for(int i=1;i<=n;i++){
            x[t]=i; 
            if(judge(t)) // 可以放在第i位置,则继续往下一行搜索
                dfs(t+1); 
        }
    }
}
int main(){
    while(cin>>n&&n){
        memset(x,0,sizeof(x));
        sum=0;
        dfs(1); // 从第一行开始遍历
        cout<<sum<<endl;
    }
    return 0;
}

二进制优化

核心代码如下:

int n,sum,upperlim;  // upperlim 表示初始化为 (1 << n)-1
int x[15];
void dfs(int row , int ld , int rd){
    int pos , p;
    if(row!=upperlim){
        pos = upperlim & (~(row|ld|rd));
        while(pos){
            p=pos&(~pos+1);
            pos = pos - p;
            dfs(row|p,(ld|p)<<1,(rd|p)>>1);
        }
    }
    else{
        sum++;
    }
}

初始化:upperlim = (1<<n)-1,sum=0;
调用参数:dfs(0,0,0);
函数带三个参数row、ld和rd,分别表示在纵列和两个对角线方向的限制条件下这一行的哪些地方不能放。位于该行上的冲突位置就用row、ld和rd中的1来表示。把它们三个并起来,得到该行所有的禁位,取反后就得到所有可以放的位置(用pos来表示)。
p = pos &(~pos + 1)其结果是取出最右边的那个1。这样,p就表示该行的某个可以放子的位置,把它从pos中移除并递归调用dfs的过程。

(ld | p)<< 1 是因为由ld造成的占位在下一行要右移一下;
(rd| p)>> 1 是因为由rd造成的占位在下一行要左移一下。

ld rd row 还要和upperlime 与运算一下,这样做的结果就是从最低位数起取n个数为有效位置,原因是在上一次的运算中ld发生了右移,如果不and的话,就会误把n以外的位置当做有效位。
在进行到某一层的搜索时,pos中存储了所有的可放位置,为了求出所有解,必须遍历所有可放的位置,而每走过一个点必须要删掉它,否则就成死循环了。

代码

#include<bits/stdc++.h>
using namespace std;

int n,sum,upperlim;
int x[15];

void dfs(int row , int ld , int rd){
    int pos , p;
    if(row!=upperlim){
        pos = upperlim & (~(row|ld|rd));
        while(pos){
            p=pos&(~pos+1);
            pos = pos - p;
            dfs(row|p,(ld|p)<<1,(rd|p)>>1);
        }
    }
    else{
        sum++;
    }
}
int main(){
    while(cin>>n&&n){
        memset(x,0,sizeof(x));
        upperlim = (1<<n)-1;
        sum=0;
        dfs(0,0,0);
        cout<<sum<<endl;
    }
    return 0;
}
posted @ 2020-11-17 20:18  Cherish486  阅读(93)  评论(0编辑  收藏  举报