什么是八皇后?
答:以一种大白话的方式说,就是有一个8*8的数组,数组元素全部初始化为0,我要在这个数组中填入8个1(皇后),且各个皇后之间不能在同一行、同一列、同一对角线(包括上下对角线),如何用程序语言描述清楚这种“皇后之间的冲突”问题,以及如何选定正确的皇后位置是解决此问题的关键。
非递归方法实现
/*1.not_finish为0时证明所有方案全部选择完毕
2.flag用来标记当前为i选的这个位置是否合适(满足与前面选过的不在同一行、不在同一上对角线、不在同一下对角线)
*/
#include<stdio.h>
#define Queens 8
int a[Queens + 1];
int main()
{
int i, k, flag, not_finish = 1, count = 0;
i = 1;//程序刚开始可以先确定第1列皇后所在行
a[1] = 1;//第1列皇后所在1行
printf("The possible configuration of 8 queens are:\n");
while (not_finish)
{
while (not_finish&&i <= Queens)//由于在本循环中可能会出现not_finish为0情况,需要多判断一次;i<=Queens证明当前方案还没结束
{
for (flag = 1, k = 1; flag&&k < i; k++)//算法采用先给i列皇后选位置(选行),在判断其位置是否正确,记得这里flag要弄成1,因为假设经过上次的调整后位置变正确了
{
if (a[k] == a[i])//不断的让已经选好的皇后所在行与当前皇后假定的这个行比较,检查是否在同一行
flag = 0;
}
for (k = 1; flag&&k < i; k++)//此时不用在条件1加flag=1,因为3个条件有1个不满足即此位置不合适
{
if((a[i] == a[k] - (k - i)) || (a[i] == a[k] + (k - i)))//判断当前皇后假设的所在行是否满足与先前已选定皇后不在同对角线上(上下对角线)
flag = 0;
}
if (!flag)//当前皇后假定的这个行不满足要求
{
if (a[i] == a[i - 1])//若你当前为此皇后假定的行已经追上了前一个皇后所在行,证明为了给当前皇后换个正确的位置,已经轮流检查一遍了,此时还是找不到当前皇后的合理位置,证明需要去改变一下前一个已经确定好的皇后的位置了
{
i--;//重新选前一个皇后的行
if (i > 1 && a[i] == Queens)//如果前一个皇后非第1个皇后,且在最后1行,那你在重新选取前一个皇后行时就要从1行开始选
a[i] = 1;
else if (i == 1 && a[i] == Queens)//如果同时满足前一个皇后为1皇后且1皇后已经在最后一行了,则证明所有可能的情况已经检查完毕,程序即将结束
{
not_finish = 0;//所有的方案全部结束,结束整个程序
}
else
a[i]++;//如果前一个皇后非以上条件,咱们就自然让前一个皇后放到下一行,试试经过改变前一皇后有没有可能选出下一皇后
}
else if (a[i] == Queens)//如果当前皇后行不满足要求,且并没有追上前一皇后,则改变一下当前皇后的行在对其进行判断是否合理
a[i] = 1;
else
a[i]++;//与上面else if同作用,只不过若当前皇后不在最后一行则让其变为下一行,否则再从第一行检查
}
else if (++i <= Queens)//证明i列皇后选定成功,该选定i+1列了,若i为8,证明第8列皇后选定完毕,i自增为9退出循环,打印方案
{
//先为i+1列皇后初选定个位置,若i列皇后都在最后1行了,那i+1列皇后从1行找起
if (a[i - 1] == Queens)//
a[i] = 1;
else
a[i] = a[i - 1] + 1;//否则从i列皇后所在行的下一行找起(因为i+1列皇后不能与i列皇后同行)
}
}
if (not_finish)//选出了一种方案
{
++count;
printf((count - 1) % 3 ? "\t[%2d]:" : "\n[%2d]:", count);
for (k = 1; k <= Queens; k++)
{
printf("%d", a[k]);
}
if (a[Queens - 1] < Queens)
a[Queens - 1]++;
else
a[Queens - 1] = 1;
i = Queens - 1;
}
}
}
/*
对69-73行的解释:在打印完一种方案后,想要再选下一种方案,做法是改变第7列皇后所在行,要么让它变成下一行,要么让它变成第一行
为什么不是变化第8列皇后的行呢?答:方案既然能输出,那么第8列皇后就只有唯一1行可以"容下"它,你想让它往哪换?,在变完第7列皇后
所在行后,自然要判断这行是否满足条件,所以i变成7
程序结束的标志是“1列皇后”已经位于第8行了
*/
通过递归代码解释原理
#include<stdio.h>
int place[8];//说明第i行皇后在第几列,例如place[0]=1,表示第0行皇后在第1列
bool flag[8] = {1,1,1,1,1,1,1,1};//表示第i列是否可占,例如flag[3]=false,表明第3行不能被占用
bool d1[15] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };//上对角线某位置能否可占的标识数组,看底部解释
bool d2[15] = { 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 };//下对角线某位置能否可占的标识数组
int count = 0;
int main()
{
void EightPrince(int);
EightPrince(0);//代表当前处理的为第0行皇后,有0~7共8个皇后
return 0;
}
void EightPrince(int n)
{
void print(int *);//打印函数
int col;
for (col = 0; col < 8; col++)//在选取第0行皇后所在列时需对所有列依次检查
{
if (flag[col] && d1[n - col + 7] && d2[n + col])//占领操作(下面4条语句):某一列必须满足此列未被占用(flag[col]!=0)、且此列相关的两条上下对角线没被占用过(d1[n - col + 7] && d2[n + col])
{
place[n] = col;//存下第n行皇后所在列,例如place[3]=3表示为第3行皇后所在第3列(共有0~7列)
flag[col] = 0;//标记此列已被当前行皇后占领,后续皇后不能占用此列
d1[n - col + 7] = 0;//标记d1数组的此位置为0,证明后续若有一个皇后的行列下标计算后也为此位置,那么这个皇后不能放在那个列,因为他处于与这个皇后同一上对角线上
d2[n + col] = 0;//标记d1数组的此位置为0,证明后续若有一个皇后的行列下标计算后也为此位置,那么这个皇后不能放在那个列,因为他处于与这个皇后同一下对角线上
if (n < 7)//若还未处理到最后一行皇后则递归处理其余皇后,当n=7时证明所有皇后已处理完毕,得出了一种方案,则执行else语句
{
EightPrince(n + 1);
}
else
{
count++;//记录当前是第几种方案
printf("NO.%d\n", count);
print(place);//打印此方案
}
//精华所在——抹除操作(以下3条语句)是为回溯过程做的准备,看底部解释
flag[col] = 1;
d1[n - col + 7] = 1;
d2[n + col] = 1;
}
}
}
void print(int *p)//用来打印选出来的方案
{
int table[8][8];
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
if (j == p[i])
{
table[i][j] = 1;
}
else
{
table[i][j] = 0;
}
}
}
for (int i = 0; i < 8; i++)
{
for (int j = 0; j < 8; j++)
{
printf("%d ", table[i][j]);
}
printf("\n");
}
}
为什么要回溯?
答:在我们进行了多次递归后,处理完了最后一行皇后所在列,此时得出了一种方案,但是我们想要全部方案,而不是仅仅的一种,这就需要回溯,比如我在确定第7行皇后在哪列时,col增到5我发现第7行皇后可以放在5列(此时col为5,for循环并未结束),放置完后由于这是最后一个皇后了,不会在执行EightPrince(n + 1);语句了,直接打印输出方案了,打印了过后就可以抹除第7行皇后所在位置了,抹除的操作(3条语句),我称它作为回溯前的准备,抹除后col会接着前面的5继续执行,判断6行不行、7行不行,发现都不行则证明n=7这层递归算是彻底结束了,此时第7行是没有任何皇后的(因为除了我们打印出的那种方案以外,在没有其余列可以放第七行皇后了),然后返回到上一次递归调用处(EightPrince(n + 1)),上一次递归调用不是为第6行皇后选好位置了嘛,一样它会有一个col,比如是4,那么我们再抹除第6层皇后位置后,依然要将剩余的col值走完(要注意,这一系列的回溯过程基于的还都是上一个方案遗留下来的),只有假设我在走完此时剩余的col值之前时又发现了一个地方可以放第6行皇后时他才开始第2种方案的过程——也就是占领第6行,递归执行第7行皇后,然后又将第7行元素处理完了,打印第2种方案后再次从头读我说的这段话。
何时回溯?
答:再将所有皇后处理完后,我将(抹除操作+执行完剩余的col值)统称为回溯。
对一些参数进行说明
d1和d2数组是啥意思?
答:首先说d2数组,若有一个8*8的二维数组(下标0~7),若我们将每个元素的行列号相加得出的值作为内容放入数组中,处理完所有元素后我们会发现,共有15条下对角线,且每条对角线上的元素值相同,且最大元素值为14,最小为0,那么我们就可以设置一个长度为15的d2数组,我将它叫做下对角线数组,当我们为某一行皇后选取了合适的列,我们就会在“占领操作”中有这样一条语句—— d2[n + col] = 0, 意思是之后若为某个皇后选出一列时,它们的n+col值与之前已经确定的一个皇后的n+col值相同,证明它们处于同一下对角线上,当然不能选。
至于d1数组,跟d2数组几乎一样,只不过它是描述的上对角线情况,一个二维数组的某条上对角线是用n-col值相同的元素确定的,在这里我写成n-col+7的目的是避免出现负数,最后可以确保对角线值的范围也在0~14,所以可以设置成与d2一样的形式,只不过“占领操作”变成了d1[n - col + 7]=0,不理解的读者可以看看下面的图片。