您的位置:首页 > 其它

8皇后问题求解

2017-05-09 00:09 120 查看

1. 八皇后问题的递归解法。

题目一看就是递归,因为问题描述可以表示为在第k层放好的情况下,放第k+1个皇后,使与

前面的不相冲突。

好,现在分析第k层算法(递归算法)

若k层==9, 表示搜索已经结束,打印结果,返回。

否则,在8个列上试探,若本列无皇后,正对角线无皇后,斜对角线无皇后,在该处放置皇后。

递归到下一层,即下一行(在下一行放皇后)。

问题虽然描述清了,但写代码还为时过早,还要明确几个问题。

1. 何为搜索?

计算机搜索就是计算机枚举所有可能的情况, 就是所谓的穷举.

搜索就是建立结果数组的过程,通过调试你会发现,搜索大部分的时间是在进行比较,运算, 很少部分时间是构建结果数组.

我们把这种构建数组的尝试,递进的过程叫搜索算法.

2. 要打印结果,那结果是怎样表示的 ?

结果是一个两维数组a[i][j],代表棋盘,当放上皇后用1表示,不放皇后用0表示,

很形象,对吗。打印的时候,你甚至可以打出图形来。

3. 怎样判断a[i][j]这个点能否放皇后?

四个条件,本行,本列,对角线和反对角线都不能有皇后

可以简化为3个条件,把本行去掉,因为每行只放一个皇后。放好后,我们就递归到下一行.

或者这一行所有位置都不能放皇后,我们就回溯。

其中i就是行,先放a[0][j],再放a[1][j]…最后a[8][j], 层数到9就可以打印前面结果了

4. 什么叫放置一个皇后?

所谓放置皇后,就是把a[i][j]数组中一个元素赋值为1.

同时把本列置为有皇后,把对应的正对角线和反对角线置为有皇后

每一列有没有皇后用一个数组a1[8]来表示, a1 中对应值为1,表示该列有皇后,为0表示无皇后.

同理,正对角线15个用a2[15]来表示, 反对角线15条用a3[15]来表示.

例如: 我要在a[3][1]上放皇后, 那要看a1[1]上是否有皇后, 还要看a2[4]上是否有皇后,还要看a3[9]上是否有皇后.

若都满足条件,则:

a[3][1] = 1;
a1[1] = 1;
a2[4] = 1;
a3[9] = 1;


5. 列数组,对角线数组,反对角线数组

列数组 a1[8], j 列值就是它的下标

对角线数组 a2[15]

考察普通笛卡尔坐标下的棋盘。(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条线。
所以,当a[i][j]置1时, 将a2[i+j] 置1表示占据了该条对角线.


反对角线数组 a3[15]

看(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 表示.


所以,当a[i][j]置1时, 把a3[7+i-j] 也置1, 表示占据了该条反对角线

通过以上分析,我们知道当放置一个皇后时a[i][j]=1, 把对应的3个辅助数组也置1表示占用

递归算法和逻辑关系.

递归算法就是queen(0)->queen(1)->queen(2)->…queen(8) 然后打印结果

在每一层,都要逐列试探是否可以放置皇后, 可放,放置后继续向下递归,不可放,

试探下一列,8列均已试满,回退!

补充,逐行放置与逐列放置是等价的. 而且由4角对称性可知,结果应该是4的倍数.

实测结果是92, 符合这个判断!

据以上分析,整理数据结构和逻辑关系,可以写出c代码程序如下:

递归代码实例

/* author: hjjdebug
* date : 2009
* 再整理 : 2017
*/
#include <stdio.h>

//建立笛卡儿坐标系,i 为y轴,方向向下, j为x轴,方向向右. 左上角为原点
int a[8][8];
int a1[8];      //列值辅助数组,a1[j], 范围, a1[0]-z1[7] , 8个元素
int a2[15];     //正对角线辅助数组a2[i+j], 范围 a2[0]-a2[14] , 15个元素
int a3[15];     //反对角线辅助数组a3[i-j+7], 范围 a3[0]-a3[14] , 15个元素

void print_result()
{
for(int i=0; i<8; i++)
{
for(int j=0; j<8; j++)
{
printf("%c ",a[i][j]==1 ? '*' : '-');
}
printf("\n");
}
}

static int count=0;
void queen( int i)
{
int j;
if(i==8)
{
printf("count:%d\n", ++count);
print_result();
return;
}
for(j=0; j<8; j++)
{
//判定a[i][j] 能否放皇后
if(a1[j]==0 && a2[i+j]==0 && a3[i-j+7]==0)
{
a[i][j] = 1;
a1[j]=1;
a2[i+j]=1;
a3[i-j+7]=1;
queen(i+1);
//恢复环境, 是回退的意思,以便查找下一位置.
a[i][j]=0;
a1[j]=0;
a2[i+j]=0;
a3[i-j+7]=0;
}
}
}

int main()
{
queen(0);
}


代码很简洁,嗯! 是的.

递归算法是自己调用自己的算法,实际上属于深度优先搜索算法. 每调用一次自己,就向更深一层前进了一步.

递归返回时,会逐层返回,或叫逐层回退. 返回并不是一退到底,而是只退一层, 根据具体情况,可以选择其它操作,

是继续进,还是继续退.

递归利用函数栈存储数据和利用函数返回实现自动回退。 这实际上是隐含回溯。

2.八皇后问题的非递归解法。 回溯法

这里给一个不用递归用循环的程序,看其怎样实现回溯.

虽然非递归不如递归简洁。 但其进退之间别有一番情趣! 而且它还可以实现2级(本题)或多级深度退却(例如迷宫).

本题循环的退出条件是column 被逼回为负数, 搜到结果是colum=8 有点新意!

1.该程序的结果是如何表示的?

用1个一维数组a[]来表示, a[0]表示第一列皇后所在的行数, a[1]表示第二列皇后所在的行数… a[7]表示第8列…

2.该程序的算法过程是怎样的?

先填充第一列皇后a[0],填好后, 向前进一步,填充第二列. 当8列都填好后,就可以打印结果了.

如果当向下列前进时, 所有位置(所有行)都不能满足要求,则要回退到本列, 以便搜索其它值, 若本列已没有

值可以搜索,则继续回退.

3.前进和回退是什么意思?

前进就是列column加1,然后继续操作,回退就是列(column)减1,并恢复现场, 再搜索其它值.

4.搜索到结果和搜索结束条件是什么?

当列递进到8时,表示a[0]-a[7]都填好了,可以打印结果了.

当列回退到-1时,表示全部搜索完成.

简单分析: 结果保存在 row[]中, row[0]保存第一列的行值, row[1]保存第二列的行值…

5.非递归实例代码

/* author: hjjdebug
* date : 2009
* 整理 : 2017
*/
#include <stdio.h>
#include <stdlib.h>
#define MAXCOLUMN 8
#define REALMAXCOLUMN (MAXCOLUMN - 1)
int row[MAXCOLUMN];   //保存每一列对应的行值
int c;                //column
int a1[8];              //表示莫一行是否有皇后, 0->有皇后,1->无皇后, 下标表示行值
//正斜线和反斜线都是从上往下顺序数, 从0开始
int a2[2*MAXCOLUMN-1];  // 表示对角线皇后配置, 下标表示第几条斜线
int a3[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(a1[row[c]]&&a2[c+row[c]]&&a3[REALMAXCOLUMN-c+row[c]])
{
if (c==REALMAXCOLUMN)
{
printResult();
backward();
}
else
{
/*在第c列,row[c]行位置设定有皇后标志*/
a1[row[c]]=a2[c+row[c]]=a3[REALMAXCOLUMN-c+row[c]]=0;
//列加1,从第1行开始配置
row[++c]=0;
}
}
else
{
backward();
}
}
return    0;

}
//变量说明: c是列值,row[]是行值数组,也是结果数组
//a1[]是行数组,a2[]是对角线数组,a3[]是反对角线数组.值为1表示未被占用
void init()
{
int i;
for (i=0;i<MAXCOLUMN;i++) a1[i]=1;
for (i=0;i<2*MAXCOLUMN-1;i++) a2[i]=a3[i]=1;
for (i=0;i<MAXCOLUMN;i++) row[i]=0;
c=0;
}

//打印结果,让行号从1开始
void printResult()
{
for (int i=0;i<MAXCOLUMN;i++)
{
printf("%d ",row[i]+1);
}
printf("\n");
}

void backward()
{
while (row[c]==REALMAXCOLUMN)
{
c--; //列减1
//清除关于第c列,row[c]行有皇后标志
a1[row[c]]=a2[c+row[c]]=a3[REALMAXCOLUMN-c+row[c]]=1;
}
row[c]++; //行加1
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: