题目一看就是递归,因为问题描述可以表示为在第k层放好的情况下,放第k+1个皇后,使与
前面的不相冲突。
好,现在分析第k层算法(递归算法)
问题虽然描述清了,但写代码还为时过早,还要明确几个问题。
计算机搜索就是计算机枚举所有可能的情况, 就是所谓的穷举.
搜索就是建立结果数组的过程,通过调试你会发现,搜索大部分的时间是在进行比较,运算, 很少部分时间是构建结果数组.
我们把这种构建数组的尝试,递进的过程叫搜索算法.
结果是一个两维数组result[i][j],代表棋盘,当放上皇后用1表示,不放皇后用0表示,
很形象,对吗。打印的时候,你甚至可以打出图形来。
四个条件,本行,本列,对角线和反对角线都不能有皇后
可以简化为3个条件,把本行去掉,因为每行只放一个皇后。放好后,我们就递归到下一行.
或者这一行所有位置都不能放皇后,我们就回溯。
其中i就是行,先放result[0][j],再放result[1][j]…最后result[8][j], 层数到9就可以打印前面结果了
所谓放置皇后,就是把result[i][j]数组中一个元素赋值为1.
同时把本列置为有皇后,把对应的正对角线和反对角线置为有皇后
每一列有没有皇后用一个数组a_column[8]来表示, a_column 中对应值为1,表示该列有皇后,为0表示无皇后.
同理,正对角线15个用a_diagonal[15]来表示, 反对角线15条用a_back_diagonal[15]来表示.
例如: 我要在result[3][1]上放皇后, 那要看a_column[1]上是否有皇后, 还要看a_diagonal[4]上是否有皇后,还要看a_back_diagonal[9]上是否有皇后.
若都满足条件,则:
result[3][1] = 1; a_column[1] = 1; a_diagonal4] = 1; a_back_diagonal[9] = 1;
考察普通笛卡尔坐标下的棋盘。(0,0)第一条线 (1,0)(0,1)第二条线 (2,0)(1,1)(0,2)第三条线...(7,0)..(0,7)第8条线 row+column 是常数,继续(7,1)..(1,7)为第9条线,第(7,7)为第15条线。 所以,当result[i][j]置1时, 将a_diagonal[i+j] 置1表示占据了该条对角线.
看(0,0)(1,1)。。(7,7)构成一条线。这是中间。row=column, row-column=0 向两边扩散也有2n-1条对角线。右上(0,7)第一条,然后(0,6)(1,7)第二条... (0,1)(1,1)第8条. 继续数到第15条. 反对角线的下标index可以用 7+row-column 表示.
所以,当result[i][j]置1时, 把a_back_diagonal[7+i-j] 也置1, 表示占据了该条反对角线
通过以上分析,我们知道当放置一个皇后时result[i][j]=1, 把对应的3个辅助数组也置1表示占用
补充,逐行放置与逐列放置是等价的. 而且由4角对称性可知,结果应该是4的倍数.
实测结果是92, 符合这个判断!
据以上分析,整理数据结构和逻辑关系,可以写出c代码程序如下:
/* author: hjjdebug
* date : 2009
* 再整理 : 2017
* 再整理 : 2023, 修改变量名称,使得更有意义:
* 将原来的a1,a2,a3改为 a_column,a_diagonal,a_back_diagonal
* 并将i改为r(row), j改为c(column),a改为result
*/
#include
//建立笛卡儿坐标系,左上角为原点
//r->row 行,为y轴,方向向下,范围0-7,0表示第一行,7表示第8行
//c->column为x轴,方向向右. 范围0-7,0表示第一列,7表示第8列
int result[8][8]; //结果数组result, 为1表示有皇后,为0表示无皇后
//以下3个辅助数组,其值为1表示该线被占用, 0表示该线未被占用
int a_column[8]; //列值辅助数组,a_column[c], a_column[0]-a_column[7] , 8个元素,代表8个列线
int a_diagonal[15]; //正对角线辅助数组a_diagonal[r+c], a_diagonal[0]-a_diagonal[14] , 15个元素,代表15条对角线
int a_back_diagonal[15]; //反对角线辅助数组a_back_diagonal[r-c+7], a_back_diagonal[0]-a_back_diagonal[14] , 15个元素,代表15条反对角线
void print_result()
{
for(int r=0; r<8; r++)
{
for(int c=0; c<8; c++)
{
printf("%c ",result[r][c]==1 ? '*' : '-');
}
printf("\n");
}
}
static int count=0;
void queen( int r)
{
if(r==8)
{
printf("count:%d\n", ++count);
print_result();
return;
}
for(int c=0; c<8; c++)
{
//判定a[r][c] 能否放皇后
if(a_column[c]==0 && a_diagonal[r+c]==0 && a_back_diagonal[r-c+7]==0)
{
result[r][c] = 1;
a_column[c]=1;
a_diagonal[r+c]=1;
a_back_diagonal[r-c+7]=1;
queen(r+1);
//从上一层返回,有回退的意思
//恢复环境,以便使c+1,查找下一位置.
result[r][c]=0;
a_column[c]=0;
a_diagonal[r+c]=0;
a_back_diagonal[r-c+7]=0;
}
}
}
int main()
{
queen(0);
}
代码很简洁,嗯! 是的.
递归算法是自己调用自己的算法,实际上属于深度优先搜索算法. 每调用一次自己,就向更深一层前进了一步.
递归返回时,会逐层返回,或叫逐层回退. 返回并不是一退到底,而是只退一层, 根据具体情况,可以选择其它操作,
是继续进,还是继续退.
递归利用函数栈存储数据和利用函数返回实现自动回退。 这实际上是隐含回溯。
这里给一个不用递归用循环的程序,看其怎样实现回溯.
虽然非递归不如递归简洁。 但其进退之间别有一番情趣! 而且它还可以实现2级(本题)或多级深度退却(例如迷宫).
本题循环的退出条件是column 被逼回为负数, 搜到结果是colum=8 有点新意!
用1个一维数组result[]来表示, result[0]表示第一列皇后所在的行数, result[1]表示第二列皇后所在的行数… result[7]表示第8列…
先填充第一列皇后的行值result[0],填好后, 向前进一步,填充第二列. 当8列都填好后,就可以打印结果了.
如果当向下列前进时, 所有位置(所有行)都不能满足要求,则要回退到本列, 以便搜索其它值, 若本列已没有
值可以搜索,则继续回退.
前进就是列column加1,然后继续操作,回退就是列(column)减1,并恢复现场, 再搜索其它值.
当列递进到8时,表示result[0]-result[7]都填好了,可以打印结果了.
当列回退到-1时,表示全部搜索完成.
简单分析: 结果保存在 result[]中, result[0]保存第一列的行值, result[1]保存第二列的行值…
/* author: hjjdebug
* date : 2009
* 整理 : 2017
* 再整理: 2023, 修改辅助数组名称为a_row,a_diagonal,a_back_diagonal
* 修改结果数组名称为result,由列索引可找到行值.代表某列某行有皇后
* 添加FLAGS 数据类型
*/
#include
#include
#define MAXCOLUMN 8
#define REALMAXCOLUMN (MAXCOLUMN - 1)
int c; //column, 表示当前进行到的列数
int result[MAXCOLUMN]; //result[0]-result[7]8个元素,结果数组,保存皇后位置,代表第几列,第几行有皇后
//result[c] ,在运算过程中,它代表了由列值到行值的转换
//三个辅助数组,下标都是从0开始, 数值0表示占用,1表示未占用
typedef enum
{
OCCUPY,
FREE,
}FLAGS;
FLAGS a_row[8]; // 表示某一行是否有皇后, 下标表示行值
FLAGS a_diagonal[2*MAXCOLUMN-1]; // 表示对角线皇后配置, 下标表示第几条斜线
FLAGS a_back_diagonal[2*MAXCOLUMN-1]; // 表示反对角线皇后配置, 下标表示第几条反斜线
void init();
void printResult();
void backward();
int main()
{
init();
// c: the column, from 0 to REALMAXCOLUMN
// 当c等于 REALMAXCOLUMN 时,表示找到了一个结果
// 当c被逼回到负数时,表示完全搜索结束。
while (c>=0)
{ //关联的行,对角线,反对角线均未占用时
if(a_row[result[c]]&&a_diagonal[c+result[c]]&&a_back_diagonal[REALMAXCOLUMN-c+result[c]])
{
if (c==REALMAXCOLUMN)
{
printResult();
backward();
}
else
{
/*在第c列,result[c]行位置设定有皇后标志*/
a_row[result[c]]=a_diagonal[c+result[c]]=a_back_diagonal[REALMAXCOLUMN-c+result[c]]=OCCUPY;
//列加1,从第1行开始配置
result[++c]=0;
}
}
else
{
backward();
}
}
return 0;
}
//变量说明: c是列值,result[]是行值数组,也是结果数组
//a_row[]是行数组,a_diagonal[]是对角线数组,a_back_diagonal[]是反对角线数组.值为1表示未被占用
void init()
{
int i;
for (i=0;i