8皇后问题求解

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

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

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

  1. 若k层==9, 表示搜索已经结束,打印结果,返回。
  2. 否则,在8个列上试探,若本列无皇后,正对角线无皇后,斜对角线无皇后,在该处放置皇后。
  3. 递归到下一层,即下一行(在下一行放皇后)。

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

1. 何为搜索?

计算机搜索就是计算机枚举所有可能的情况, 就是所谓的穷举.
搜索就是建立结果数组的过程,通过调试你会发现,搜索大部分的时间是在进行比较,运算, 很少部分时间是构建结果数组.
我们把这种构建数组的尝试,递进的过程叫搜索算法.

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

结果是一个两维数组result[i][j],代表棋盘,当放上皇后用1表示,不放皇后用0表示,
很形象,对吗。打印的时候,你甚至可以打出图形来。

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

四个条件,本行,本列,对角线和反对角线都不能有皇后
可以简化为3个条件,把本行去掉,因为每行只放一个皇后。放好后,我们就递归到下一行.
或者这一行所有位置都不能放皇后,我们就回溯。
其中i就是行,先放result[0][j],再放result[1][j]…最后result[8][j], 层数到9就可以打印前面结果了

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

所谓放置皇后,就是把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;

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

  1. 列数组 a_column[8], j 列值就是它的下标
  2. 对角线数组 a_diagonal[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条线。
 所以,当result[i][j]置1时, 将a_diagonal[i+j] 置1表示占据了该条对角线.
  1. 反对角线数组 a_back_diagonal[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 表示.

所以,当result[i][j]置1时, 把a_back_diagonal[7+i-j] 也置1, 表示占据了该条反对角线
通过以上分析,我们知道当放置一个皇后时result[i][j]=1, 把对应的3个辅助数组也置1表示占用

  1. 递归算法和逻辑关系.
    递归算法就是queen(0)->queen(1)->queen(2)->…queen(8) 然后打印结果
    在每一层,都要逐列试探是否可以放置皇后, 可放,放置后继续向下递归,不可放,
    试探下一列,8列均已试满,回退!

补充,逐行放置与逐列放置是等价的. 而且由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 <stdio.h>

//建立笛卡儿坐标系,左上角为原点
//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.八皇后问题的非递归解法。 回溯法

这里给一个不用递归用循环的程序,看其怎样实现回溯.
虽然非递归不如递归简洁。 但其进退之间别有一番情趣! 而且它还可以实现2级(本题)或多级深度退却(例如迷宫).
本题循环的退出条件是column 被逼回为负数, 搜到结果是colum=8 有点新意!

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

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

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

先填充第一列皇后的行值result[0],填好后, 向前进一步,填充第二列. 当8列都填好后,就可以打印结果了.
如果当向下列前进时, 所有位置(所有行)都不能满足要求,则要回退到本列, 以便搜索其它值, 若本列已没有
值可以搜索,则继续回退.

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

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

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

当列递进到8时,表示result[0]-result[7]都填好了,可以打印结果了.
当列回退到-1时,表示全部搜索完成.

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

5.非递归实例代码

/* author: hjjdebug 
 * date : 2009 
 * 整理 : 2017
 * 再整理: 2023, 修改辅助数组名称为a_row,a_diagonal,a_back_diagonal
 *               修改结果数组名称为result,由列索引可找到行值.代表某列某行有皇后
 *               添加FLAGS 数据类型
 */  
#include <stdio.h>
#include <stdlib.h>
#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<MAXCOLUMN;i++) result[i]=0;
	 for (i=0;i<MAXCOLUMN;i++) a_row[i]=FREE;
	 for (i=0;i<2*MAXCOLUMN-1;i++) a_diagonal[i]=a_back_diagonal[i]=FREE;
	 c=0;  
}

//打印结果,result[c]代表了行号,让行号加1,让行号从1开始,符合人的习惯.
void printResult()
{
	 for (int c=0;c<MAXCOLUMN;c++)
	 {
		  printf("%d ",result[c]+1);
	 }
	 printf("\n");
}

void backward()
{
	 while (result[c]==REALMAXCOLUMN)
	 {
		  c--; //列减1,回退
		  //清除关于第c列,result[c]行,对应的行数组,对角线数组,反对角线数组有皇后标志
		  a_row[result[c]]=a_diagonal[c+result[c]]=a_back_diagonal[REALMAXCOLUMN-c+result[c]]=FREE;
	 }
	 result[c]++; //行加1
}

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值