N皇后问题,是指在一个N*N的国际象棋棋盘上放置n个皇后,使得这n个皇后两两均不在同一行、同一列、同一条对角线上,求合法的方案数。如下图:n=5的情况
对于这个问题,如果采用组合数的方式来枚举每一种情况(即从n²个位置中选择n个位置),那么将需要的枚举量,当n=8是就是54 502 232次枚举,如果n更大,那么就会无法承受。
但是换个思路,考虑到每行每列只能放置一个皇后,那么如果吧n列皇后所在的行号依次写出,那么就会是1~n的一个排列。例如对于4-4a来说对应的排列就是24135,图4-4b就是35142.于是就只需要枚举1~n的所有排列,查看每个排列对应的放置方案是否合法,统计其中合法的方案即可。由于总共有个排列,因此当n=8是只需要40320次枚举,比之前的做法优秀许多。
于是可以再全排列的代码基础上进行求解,由于当到达递归边界时表示生成了一个排列,所以需要在其内部判断是否为合法方案,即遍历每两个皇后,判断他们是否在同一条对角线上,若不是,则累计count。
- 暴力枚举法
#include<cstdio> #include<stdlib.h> int count=0; int n; int P[8],hashTable[8]= {false}; void generateP(int index) { if(index==n+1)//递归边界,生成一个排列 { bool flag=true;//flag为true表示当前排列为一个合法方案 for(int i=1; i<=n; i++)//遍历任意两个皇后 { for(int j=i+1; j<=n; j++) { if(abs(i-j)==abs(P[i]-P[j]))//如果在一条对角线上 flag=false;//不合法 } } if(flag)//打印输出 { for(int i=1; i<=n; i++) printf("%d",P[i]); printf("\n"); count++;//若当前方案合法count+1 return; } } for(int x=1; x<=n; x++) { if(hashTable[x]==false) { P[index]=x; hashTable[x]=true; generateP(index+1); hashTable[x]=false; } } } int main() { int T; scanf("%d",&T); while(T--) { scanf("%d",&n); generateP(1); printf("%d皇后可行方案:%d种\n",n,count); } return 0; }
-
回溯法
实际上,通过思考(动手分析)可以发现,当已经放置了一部分皇后时,可能剩余的皇后无论怎样放置都不可能合法,此时就没必要往下递归了,直接放回上层即可,这样可以减少很多计算量。
#include<cstdio>
#include<stdlib.h>
int count=0;
int n;
int P[8],hashTable[8]= {false};
void generateP(int index)
{
if(index==n+1)//递归边界,生成一个排列
{
for(int i=1; i<=n; i++)
printf("%d",P[i]);
printf("\n");
count++;//能到达这里一定是合法的
return;
}
for(int x=1; x<=n; x++)//第x行
{
if(hashTable[x]==false)//第x行还没有皇后
{
bool flag=true;//flag为true表示当前皇后不会和之前的皇后冲突
for(int pre=1;pre<index;pre++)//遍历之前的皇后
{//第index列皇后的行号为x,第pre列皇后的行号为P[pre]
if(abs(index-pre)==abs(x-P[pre]))
{
flag=false;//与之前的皇后在一条对角线,冲突
break;
}
}
if(flag)//如果可以把皇后放在第x行
{
P[index]=x;//令第index列皇后的行号为x
hashTable[x]=true;//第x行已经被占用
generateP(index+1);//递归处理第index+1行皇后
hashTable[x]=false;//递归完毕,还原第x行为未占用
}
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&n);
generateP(1);
printf("%d皇后可行方案:%d种\n",n,count);
}
return 0;
}
例题:
问题 D: 八皇后
时间限制: 1 Sec 内存限制: 32 MB
提交: 1077 解决: 595
[提交][状态][讨论版][命题人:外部导入]
题目描述
会下国际象棋的人都很清楚:皇后可以在横、竖、斜线上不限步数地吃掉其他棋子。如何将8个皇后放在棋盘上(有8 * 8个方格),使它们谁也不能被吃掉!这就是著名的八皇后问题。
对于某个满足要求的8皇后的摆放方法,定义一个皇后串a与之对应,即a=b1b2...b8,其中bi为相应摆法中第i行皇后所处的列数。已经知道8皇后问题一共有92组解(即92个不同的皇后串)。
给出一个数b,要求输出第b个串。串的比较是这样的:皇后串x置于皇后串y之前,当且仅当将x视为整数时比y小。
输入
第1行是测试数据的组数n,后面跟着n行输入。每组测试数据占1行,包括一个正整数b(1 <= b <= 92)
输出
输出有n行,每行输出对应一个输入。输出应是一个正整数,是对应于b的皇后串。
样例输入
3
6
4
25
样例输出
25713864
17582463
36824175
#include<cstdio>
#include<stdlib.h>
int count=0;
int n,a;
int P[10],hashTable[10]= {false};
void generateP(int index)
{
if(index==8+1)//递归边界,生成一个排列
{
count++;//能到达这里一定是合法的
if(count==a)
{
for(int i=1; i<=8; i++)
printf("%d",P[i]);
printf("\n");
}
return;
}
for(int x=1; x<=8; x++)//第x行
{
if(hashTable[x]==false)//第x行还没有皇后
{
bool flag=true;//flag为true表示当前皇后不会和之前的皇后冲突
for(int pre=1; pre<index; pre++) //遍历之前的皇后
{
//第index列皇后的行号为x,第pre列皇后的行号为P[pre]
if(abs(index-pre)==abs(x-P[pre]))
{
flag=false;//与之前的皇后在一条对角线,冲突
break;
}
}
if(flag)//如果可以把皇后放在第x行
{
P[index]=x;//令第index列皇后的行号为x
hashTable[x]=true;//第x行已经被占用
generateP(index+1);//递归处理第index+1行皇后
hashTable[x]=false;//递归完毕,还原第x行为未占用
}
}
}
}
int main()
{
int T;
scanf("%d",&T);
while(T--)
{
scanf("%d",&a);
generateP(1);
//printf("%d皇后可行方案:%d种\n",n,count);
count=0;
}
return 0;
}
题目来自:http://codeup.cn/problem.php?cid=100000583&pid=3
补充:在论坛上看到有人写出了更快更巧的方法,感觉很是高深,值得研究一番(可怕的是没有注释。。)
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
long sum = 0, upperlim = 1;
void test(long row, long ld, long rd)
{
if (row != upperlim)
{
long pos = upperlim & ~(row | ld | rd);
while (pos)
{
long p = pos & -pos;
pos -= p;
test(row + p, (ld + p) << 1, (rd + p) >> 1);
}
}
else
sum++;
}
int main(int argc, char *argv[])
{
time_t tm;
int n = 16;
if (argc != 1)
n = atoi(argv[1]);
tm = time(0);
if ((n < 1) || (n > 32))
{
printf(" 只能计算1-32之间\n");
exit(-1);
}
printf("%d 皇后\n", n);
upperlim = (upperlim << n) - 1;
test(0, 0, 0);
printf("共有%ld种排列, 计算时间%d秒 \n", sum, (int) (time(0) - tm));
}