N皇后问题详解
N皇后问题 问题实在八皇后问题上的进一步扩展,对于八皇后问题做简单描述。八皇后问题,一个古老而著名的问题,是回溯算法的典型案例。该问题由国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有 76 种方案。1854 年在柏林的象棋杂志上不同的作者发表了 40 种不同的解,后来有人用图论的方法解出 92 种结果。如果经过±90度、±180度旋转,和对角线对称变换的摆法看成一类,共有42类。计算机发明后,有多种计算机语言可以编程解决此问题。
八皇后问题如果用穷举法需要尝试 88=16,777,216 种情况。每一列放一个皇后,可以放在第 1 行,第 2 行,……,直到第 8 行。穷举的时候从所有皇后都放在第 1 行的方案开始,检验皇后之间是否会相互攻击。如果会,把列 H 的皇后挪一格,验证下一个方案。移到底了就 “进位” 到列 G 的皇后挪一格,列 H 的皇后重新试过全部的 8 行。如图 1 所示,这种方法无疑是非常低效率的,因为它并不是哪里有冲突就调整哪里,而是盲目地按既定顺序枚举所有的可能方案。
回溯算法优于穷举法。将列 A 的皇后放在第一行以后,列 B 的皇后放在第一行已经发生冲突。这时候不必继续放列 C 的皇后,而是调整列 B 的皇后到第二行,继续冲突放第三行,不冲突了才开始进入列 C。如此可依次放下列 A 至 E 的皇后,如图 2 所示。将每个皇后往右边横向、斜向攻击的点位用叉标记,发现列 F 的皇后无处安身。这时回溯到列 E 的皇后,将其位置由第 4 行调整为第 8 行,进入列 F,发现皇后依然无处安身,再次回溯列 E。此时列 E 已经枚举完所有情况,回溯至列 D,将其由第 2 行移至第 7 行,再进入列 E 继续。按此算法流程最终找到如图 3 所示的解,成功在棋盘里放下了 8 个 “和平共处” 的皇后。继续找完全部的解共 92 个。
回溯算法求解八皇后问题的原则是:有冲突解决冲突,没有冲突往前走,无路可走往回退,走到最后是答案。为了加快有无冲突的判断速度,可以给每行和两个方向的每条对角线是否有皇后占据建立标志数组。放下一个新皇后做标志,回溯时挪动一个旧皇后清除标志。八皇后问题进一步扩展即可得到N皇后问题的答案。以下给出回溯算法解八皇后问题的编程代码实现。
N皇后代码实现(递归版本):
#include<stdio.h>
#include<time.h>
#define N 8 //表示皇后个数,以八皇后为例验证结果
int col[N]={0};
int left[2*N-1]={0};
int right[2*N-1]={0};
int hong[N]={0};
int n=1;
void printqueen()
{
int x,y;
// for(i=0;i<8;i++)
// printf("%d ",hong[i]);
printf("输出第%d个皇后\n",n++);
for(x=0;x<N;x++)
{
for(y=0;y<N;y++)
{
if(hong[x]==y)
printf("1");
else
printf("0");
}
printf("\n");
}
}
void queen (int i)
{
int j;
for(j=0;j<N;j++)
{
if(col[j]==0&&left[i+j]==0&&right[i-j+7]==0)
{
hong[i]=j;col[j]=1;right[i-j+7]=1;left[i+j]=1;
if(i<N-1)
queen(i+1);
else
{
printqueen();
// n++;
}
col[j]=right[i-j+7]=left[i+j]=0;
}
}
}
int main(void)
{
int a,b;
// freopen("main.txt","w",stdout);
a=clock();
queen(0);
b=clock();
printf("%d %d\n",n-1,b-a);
return 0;
}
程序输出结果:
N皇后代码实现(非递归版本):
#include<stdio.h>
#include<time.h>
#define N 8 //表示皇后个数,以八皇后为例验证结果
int col[N]={0};//标记列是否存放皇后
int right[2*N-1]={0};//标记左斜线是否放皇后
int left[2*N-1]={0};//标记右斜线是否放皇后
int q[N]={-1};//机录皇后位置
int cnt=0;
//***************************************************
// 初始化数组
//清空上次数据记录
//***************************************************
void init()
{
int i;
for(i=0;i<2*N-1;i++)
{
right[i]=left[i]=0;
if(i<N)
{
col[i]=0;
q[i]=-1;
}
}
}
//***************************************
//输出函数
//打印皇后坐标 以0,1格式输出
//***************************************
void printqueen()
{
int i,j;
printf("\n\n");
printf("输出第%d个皇后\n",++cnt);
for(i=0;i<N;i++)
{
for(j=0;j<N;j++)
{
if(q[i]==j)//有皇后则输出唯1
printf("1");
else
printf("0");
}
printf("\n");
}
}
//*****************************************************
// 功能函数
// 判断皇后位置并输出每种皇后结果
//*****************************************************
void Tiral()
{
int i,j,top=0;
for(i=1;i<N&&i>0;i++)//继续主调循环从第1行开始遍历求解
{
j=q[top+1]+1;
q[top+1]=-1;
for(;j<N;j++)//查找每行每个空格是否符合条件
{
if(!col[j]&&!left[i+j]&&!right[i-j+7])
{
col[j]=left[i+j]=right[i-j+7]=1;
q[++top]=j;//记录皇后位置
break;//如果放完皇后跳出内层循环
}
}
if(j==N)//如果j等于N摸掉皇后回朔回前一行
{
top--;
col[q[top+1]]=left[top+1+q[top+1]]=right[top+1-(q[top+1])+7]=0;//摸掉top以行皇后
i=i-2;
}
if(j<N)
{
if(i==N-1)
{
printqueen();
top--;
col[q[top+1]]=left[top+1+q[top+1]]=right[top+1-(q[top+1])+7]=0;//抹掉的N-1皇后
i--;
}
}
}
}
int main(void)
{
int i,j,a,b;
// freopen("main.txt","w",stdout);
a=clock();//记录开始时间
for(i=0;i<N;i++)//循环初始化0行数据
{
init();//初始化标记数组
q[0]=i; //初始化0行
col[i]=left[i]=right[7-i]=1;//标记数组初始化
Tiral();//从第一行开始进行回朔穷举找结果并输出
}
b=clock();//记录结束时间
printf("%d %d",cnt,(b-a));//输皇后数量,计算时间
return 0;
}