N皇后问题
【题目描述】 N皇后问题
即在NXN格的国际象棋上摆放N个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,请问有多少中摆法,并将每种摆法打印出来。图1所示即是摆法的一种。
【输入格式】
输入一个整数,即N(14>N>3)。
【输出格式】
输出所有摆法,每个摆法占一行
【输入样例】
4
【输出格式】
2413
3142
【输出说明】
N=4的棋盘输出的两种方案即图2所示
递归算法1★
若想遍历所有摆法而无一遗漏,可以逐行从上至下、从左至右尝试棋子的摆放。以N=4的棋盘为例,其遍历过程如图3所示。
Tips:棋盘坐标上一般会想到用二维数组来表示,如定义二维数组a[8][8],则a[0][0]=1代表棋盘第一行第一列有棋子,a[3][4]=0代表棋盘第4行第5列无棋子……但实际上只用一个一维数组可以解决该问题。
例如,使用a[8]来表示棋盘坐标时,假设a[0]=7,即表示第1行第7列有棋子,a[1]=2即表示第2行第2列有棋子,而且这种方法无须再判断两皇后是否在同一行。
可以定义一个Try(x,y)函数判断棋盘(x,y)处是否可以放皇后:
(1)不在同一列
(2)不在对角线上,即有两棋子坐标分别为(X1,Y1),(X2,Y2),则|X1-X2|!=|Y1-Y2|
源码:
//N皇后问题——递归算法
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
int n,a[21];
void print() //打印棋子
{int i=1;
for(;i<=n;i++)
cout<<a[i];
cout<<" ";
if(i%7==0)
cout<<endl;
}
void printGraph() //图形化打印布局
{
system("color F0"); //DOs命令改变背景色和前景色,颜色为16进制数0~F
for(int i=1;i<=n;++i)
{
for(int j=1;j<=n;++j)
if(a[i] == j)
cout<<"O";
else
cout<<"▅";
cout<<endl;
}
cout<<endl;
}
int Try(int x,int y) //测试x行y列可否摆放棋子,成功返回1,否则返回0
{
int j=1;
while(j<x) //与数组中已放好的数比较
{
if((a[j] == y) || (abs(x-j) == abs(a[j]-y)))
return 0;
++j; //右移一格继续尝试
}
return 1;
}
void place(int x) //递归函数
{
int y;
if(x>n) //棋子第n行已摆好,则打印成功方案
{
print();
//printGraph(); //打印图形化布局
}
else
{
for(y=1;y<=n;++y) //该行棋子依次从左向右移
if(Try(x,y)) //如果可以摆放
{
a[x]=y; //给a[x]赋值
place(x+1); //继续下一行的递归
}
}
}
int main()
{
cin>>n;
place(1); //从第1行开始递归
return 0;
}
该程序中有个用于调试的printGraph()函数,选用它可图形化显示布局方案,如图所示:
为了深入理解递归调用的过程,设N=4,则可将place(4)到place(4)的过程展开,如图所示:
运行过程如图下图所示。
模拟该程序运行过程时会发现,通过程序不断的进进退退,最后就打印除了全部的布局。
由此可知,递归中常常隐含回溯,回溯即选择一条路走下去,发现走不通,就返回去再选一条路走。
以下为八皇后问题的解决,共92种摆法:
改程序当N>13时,速度将慢的难以忍受,并且考虑到输出方案浪费了大量的时间,此后的优化算法只需要输出方案数即可。
递归算法2★
以N=4为例,下图左是正斜线,右是反斜线,即有2xN-1条、反斜线。
1号正斜线所占据的棋盘单元为(1,1)
2号正斜线所占据的棋盘单元为(1,2)(2,1)
3号正斜线所占据的棋盘单元为(1,3)(2,2)(3,1)
4号正斜线所占据的棋盘单元为(1,4)(2,3)(3,2)(4,1)
5号正斜线所占据的棋盘单元为(2,4)(3,3)(4,2)
6号正斜线所占据的棋盘单元为(3,4)(4,2)
7号正斜线所占据的棋盘单元为(4,4)
可以发现规律如下:
(1) 同一正斜线所占据的棋盘单元行列之和相等,如上面7条正斜线分别为2、3、4、5、6、7、8.
(2) 同一反斜线所占据的棋盘单元行列之差相等。
故可以定义bool数组x1[ ]用来记录行列之和为i的正斜线是否已经被占据,bool数组x2[ ]用来记录行列之差为i的反斜线是否已经被占据。考虑到行列之差可能为负数,棋盘坐标(x,y)对应x2[ x-y+n]。
再定义一个bool数组y[ ]判断列冲突,如果y[i]=1,说明前面已有皇后放到在这一列,再放会发生列冲突,y[i]=0,说明当前第i列还没有放置皇后。
参考代码:
//N皇后——递归算法2
#include<iostream>
#include<cstdio>
#include<cstdlib>
using namespace std;
int n,num;
int a[20]={0};
bool y[41],x1[41],x2[41];
void place(int x) //递归函数
{
if(x>n)
++num; //找到一个摆法,计数器加1
else
{
for(int i=1;i<=n;++i)
{
if(y[i] == 0 && x1[x+i] ==0 && x2[x-i+n] == 0) //如果列,正斜线,反斜线无冲突
{
a[x] = i; //给a[x]赋值
y[i] = 1; //列坐标做标记
x1[x+i] = 1; //正斜线做标记
x2[x-i+n] = 1; //反斜线做标记
place(x+1); //继续下一行的递归
y[i] = 0; //恢复
x1[x+i] = 0; //恢复正斜线标记为0
x2[x-i+n] = 0; //恢复反斜线标记为0
}
}
}
}
int main()
{
cin>>n;
place(1);
cout<<num<<endl;
}
优化后的程序提高了很多,但是当N>=14时候,速度还是较慢,我们还可以再次基础上继续优化,这里我就不继续写了,希望大家能够自己优化。