问题简述
在n*n的棋盘上放n个皇后,使其互相不能进行攻击,问共有几种放法。
题解
递归版本
看到问题之后自己先写了一个简单的递归版本。没有什么太多想说的,就有一点,一开始想的还是用个二维数组标标0/1,后来想到每一行有且仅有一个皇后,那其实只要说知道某行的那个皇后在第几列就行了,于是改成了一维数组。
//recursive
#include<stdio.h>
#include<iostream>
#include<time.h>
using namespace std;
int a[30];
int n,sum=0;
int judge(int x,int y)
{
for(int i=1;i<x;i++)
if(y==a[i]||(x+y)==(i+a[i])||(x-y)==(i-a[i]))
return 0;
return 1;
}
void fun(int i)
{
if(i>n)
{
//for(int k=1;k<=n;k++)
// printf("%d ",a[k]);
//printf("\n");
sum++;
}
else
{
for(int j=1;j<=n;j++)
if(judge(i,j))
{
a[i]=j;
fun(i+1);
}
}
}
int main()
{
time_t start,stop;//计时用的
start=time(NULL);
scanf("%d",&n);
fun(1);
stop=time(NULL);
printf("%ld\n",(stop-start));//n=15用时66s左右
return 0;
}
非递归版本
听说非递归比递归效率高而且不会堆栈(?)溢出,于是开始写非递归版本。就是要手动回溯。
这里主要是这样一个情况:某一行的皇后依次在他可以放置的地方放下去,直到放无可放了,那么就要回到上一行,将上一行的皇后放到下一个可放的位置上去,这里要有一个回溯。有两种特殊的情况:如果是第一行的皇后到头了,那么整个程序结束。如果是最后一行的皇后放下去了,那么就算得了一组解,然后回到倒数第二行,摆下一个(也就是另一个)位置。
non-recursive
#include<stdio.h>
#include<iostream>
#include<time.h>
#include<string.h>
using namespace std;
int a[30];
int judge(int x,int y)
{
for(int i=1;i<x;i++)
if(y==a[i]||(x+y)==(i+a[i])||(x-y)==(i-a[i]))
return 0;
return 1;
}
int main()
{
time_t start,stop;
start=time(NULL);
int i=1,j=1;
int n,sum=0;
scanf("%d",&n);
memset(a,-1,sizeof(a));
while(i<=n)
{
while(j<=n)
{
if(judge(i,j))//能放
{
a[i]=j;
j=1;
break;
}
else//不能放,下一列
j++;
}
if(a[i]==-1)//该行放不了
{
if(i==1)//第一行不能再放了
break;
else
{
i--;
j=a[i]+1;
a[i]=-1;
continue;
}
}
if(i==n)//最后一行 (已经是该行能放的情形了)
{
// for(int k=1;k<=n;k++)
// printf("%d ",a[k]);
// printf("\n");
sum++;
j=a[i]+1;
a[i]=-1;
continue;
}
i++;
}
printf("%d\n",sum);
stop=time(NULL);
printf("%ld\n",(stop-start));//n=15用时65左右
return 0;
}
但是从结果可以看到,这个速度并没有变快,而且代码烦了不止一点点,可以说是非常气人了。
位运算版本
首先列几个位运算:
a & b 按位与 每一位上进行与运算
a | b 按位或 每一位上进行或运算
a ^ b 按位异或 每一位上进行异或运算(不同则为1)
~a 按位取反 每一位上取反
a << b 左移b位 …… 字面意思,相当于十进制*2
a >> b 右移b位 本题不涉及符号问题,故同上,相当于十进制/2
首先说明,我看了以上这些位运算操作之后,想不到和这道题有一点联系,于是直接看了别人的代码,在看他的注释之前,自己写了一点解析。我看的是他先给了个核心部分,代码如下
//初始化: upperlim = (1 << n)-1; Ans = 0;
//调用参数:test(0, 0, 0);
void test(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;
test(row | p, (ld | p) << 1, (rd | p) >> 1);
}
}
else
++Ans;
}
自己的解读
upperlim = (1 << n)-1
n位上都是1的数 (所以row=upperlim时得到一个解)
三个参数row,ld,rd中的1表示该位不能放
pos=upperlim&(~(row|ld|rd))
row|ld|rd形成的二进制数中的1表示该位不行
取反后形成的二进制数中的1表示该位可以
与upperlim 按位与 后确保了位数为n
p=pos&(~pos+1)
p的2进制中有唯一一个1表示pos的最右边一个1的位置,也即可放的最右一列
举个例子:
pos =10100
~pos =01011//~将右侧0全变为1,第一个1变为0
~pos+1 =01100//+1把0111…111变为1000…000
pos&…=00100//左边几位 取反 后与原数 按位与 后都为0,而最右端的1此时仍为1
pos=pos-p
因为前面是while(pos)所以每次都把最右边一个1取掉,也就是放入的操作
用来看什么时候取完的
放入由p定位的皇后之后新的三个参数
(row | p, (ld | p) << 1, (rd | p) >> 1)
row|p就是把p这一列标为不行,ld|p再<<1表示p这的左侧一列标为不行
相当于每进行一层递归(到下一行),就会多一个由p决定的不行的位置,然后平移
00*00 例如第一行ld=00000,p=00100,
0100* 那么第二行的ld*=(ld|p)<<1=01000,p*比如=5
10010 第三行的ld**=(ld*|p*)<<1=10100
可以看到这个ld已经将皇后的左下斜线上的点都标为了1
完整代码如下
#include<stdio.h>
#include<iostream>
#include<time.h>
#include<string.h>
using namespace std;
long sum,upperlim;
int n;
int fun(long row,long ld,long rd)
{
int pos,p;
if(row!=upperlim)
{
pos=upperlim&(~(row|ld|rd));
while(pos)
{
p=pos&(~pos+1);
pos=pos-p;
fun((row|p),(ld|p)<<1,(rd|p)>>1);
}
}
else
sum++;
}
int main()
{
time_t start,stop;
start=time(NULL);
scanf("%d",&n);
upperlim=(1<<n)-1;
fun(0,0,0);
printf("%d\n",sum);
stop=time(NULL);
printf("%ld\n",(stop-start));//n=15三秒,n=16用时17s,n=17用109秒
return 0;
}
可以看到速度上有了较为明显的提升。当然这个地方要注意,因为是整型的缘故,最多n=32。除了使用位运算外,还可以利用对称性来进行进一步优化,这里立一个flag,等有空的时候会来做这个事情的。对n皇后问题的初次尝试到这里就告一段落。(逃
最后分享一个终级版本
我说不出话来
//poj3239,n的规模300,但只要一个解
#include<iostream>
using namespace std;
int main()
{
int n,k,i;
while(1)
{
cin>>n;
if(n==0)break;
if(n%6!=2 && n%6!=3)
{
if(n%2==0)
{
for(i=2;i<=n;i+=2)cout<<i<<' ';
for(i=1;i<n-1;i+=2)cout<<i<<' ';
cout<<n-1<<endl;
}else
{
for(i=2;i<n;i+=2)cout<<i<<' ';
for(i=1;i<n;i+=2)cout<<i<<' ';
cout<<n<<endl;
}
}else
{
k=n/2;
if(k%2==0 && n%2==0)
{
for(i=k;i<=n;i+=2)cout<<i<<' ';
for(i=2;i<=k-2;i+=2)cout<<i<<' ';
for(i=k+3;i<=n-1;i+=2)cout<<i<<' ';
for(i=1;i<k+1;i+=2)cout<<i<<' ';
cout<<k+1<<endl;
}
else if(k%2==0 && n%2==1)
{
for(i=k;i<=n-1;i+=2)cout<<i<<' ';
for(i=2;i<=k-2;i+=2)cout<<i<<' ';
for(i=k+3;i<=n-2;i+=2)cout<<i<<' ';
for(i=1;i<=k+1;i+=2)cout<<i<<' ';
cout<<n<<endl;
}
else if(k%2==1 && n%2==0)
{
for(i=k;i<=n-1;i+=2)cout<<i<<' ';
for(i=1;i<=k-2;i+=2)cout<<i<<' ';
for(i=k+3;i<=n;i+=2)cout<<i<<' ';
for(i=2;i<k+1;i+=2)cout<<i<<' ';
cout<<k+1<<endl;
}
else
{
for(i=k;i<=n-2;i+=2)cout<<i<<' ';
for(i=1;i<=k-2;i+=2)cout<<i<<' ';
for(i=k+3;i<=n-1;i+=2)cout<<i<<' ';
for(i=2;i<=k+1;i+=2)cout<<i<<' ';
cout<<n<<endl;
}
}
}
return 0;
}