8皇后问题

写的很好的一篇关于8皇后问题的文章.原文地址

1.问题描述:

n×n的棋盘上放置n个不能互相捕捉的国际象棋“皇后”的所有布局。这是来源于国际象棋中的一个问题。皇后是棋盘上最具杀伤力的一个棋子,她可以捕捉与她在同一行,或同一列,或同一斜线(有两条)上的所有棋子。如下图所示,红线经过的格子都会被皇后捕捉。

2.问题分析:

1)皇后的杀伤力在她所对应的行,列,和两条斜线上,所以应该满足该皇后所在的行、列和两条斜线都不存在其它皇后。

2)所以为了避免皇后冲突,一个可行的办法是将棋盘上所有的行,列,和斜线的占用状态分别用数组存起来,初始状态为未占用。在每成功放入一个皇后以后,将该皇后所对应的行,列,和两条斜线的状态更新为占用。在每次尝试将某个皇后放置到某个位置的时候,需要检查该位置所对应的行,列,和斜线的占用状态,只有全部未被占用,才可以将该皇后放到该位置。

3)接下来的问题有两个:一是如何用数组来存储棋盘上所有的行,列,和斜线的占用状态?二是对棋盘上每一个位置如何找到它所对应的行、列和斜线在状态数组中的位置。以下分行、列、左低右高斜线和左高右低斜线四种情况来讨论:

行:对n×n的棋盘有n行(1 n),用一个n + 1维的数组row[n + 1]row[0]不用)来表示每一行的占用状态。位置(ij)对应的行的占用状态为row[i]

列:对n×n的棋盘有n列(1 n),用一个n + 1维的数组col[n + 1]col[0]不用)来表示每一列的占用状态。位置(ij)对应的列的占用状态为col[j]

左低右高斜线:n×n的棋盘总共有2n – 1条左低右高斜线,所以可以用2n维的数组b[2n]b[0]不用)来存储每一条左低右高斜线的占用状态。。对某一条斜线,其特点是它所经过的每一个格子的(列号 + 行号)相等,所以可以用这个值作为数组下标来唯一标记每一条左低右高斜线,为了使下标从1开始,对所有的下标减1。如下图所示。位置(ij)对应的左低右高斜线的占用状态为b[i + j – 1]

左高右低斜线:n×n的棋盘总共有2n – 1条左高右低斜线,所以可以用2n维的数组c[2n]c[0]不用)来存储每一条左高右低斜线的占用状态。。对某一条斜线,其特点是它所经过的每一个格子的(列号行号)相等,所以可以用这个值作为数组下标来唯一标记每一条左高右低斜线,为了避免出现负值,将所有的下标值都加上一个n。如下图所示。位置(ij)对应的左高右低斜线的占用状态为c[n + j - i]

3.    解题思路:

1)    要将n个皇后放到n×n的棋盘中,则每一列必须且只能放一个皇后。所以问题转化为确定皇后在每一列中的位置(在第几行上)。

2)    从第一列开始,依次考察每一列。

3)    在考察每一列的时候,总是从第一行开始,尝试将皇后放入,如果可以放入,就接着考察下一列的第一行。如果不可以放入,就接着考察这一列的下一行,直到成功,然后接着考察下一列的第一行。

4)    如果这一列的每一行都不可以放入,说明这一列前面各列的皇后放置有问题,导致这一列无法放入,需要回溯。

5)    回溯的时候,如果前面的一列每一行都已经被尝试过了,就需要接着往前回溯,直到找到还有行未被尝试过的列,然后尝试这一列的下一行。

6)    在回溯过程中经过的每一列都需要将已经放入的皇后取出来,以备后面重新选择位置放入。

7)    如果每一列都被考察完毕,即每一列中的皇后都找到了合适的位置,则找到一个解。

8)    在找到一个解后,如果还要寻找其它解,则需要回溯,尝试其它情况。

9)    当回溯到第0列时,说明1 ~ n列的所有行都已经被尝试过了,没有其它情况可以尝试,结束程序。

4.代码:

#include <iostream>
using namespace std;

void QueenLayout(int n);
void Display(int col[],int n);

int main()
{
	QueenLayout(8);
	return 0;
}

void QueenLayout(int n)
{
    int j;
    char next;    
    int *row=new int[n+1]; //每一行的状态(是否已经被占用:0 - 未被占用,1 - 被占用)。
    int *col=new int[n+1]; //皇后在每一列中的位置(行号)
    int *b=new int[2*n]; //每一条正斜线(左低右高)的状态(是否已经被占用:0 - 未被占用,1 - 被占用)。
    int *c=new int[2*n]; //每一条反斜线(左高右低)的状态(是否已经被占用:0 - 未被占用,1 - 被占用)。    
    bool occupied=false; //用于判断在每一列中放置皇后时,她所对应的行和两条斜线,是否至少有一条已经被占用。
	
    int m=1; //当前被考察的列
    col[0]=col[1]=1; //从第一列开始的第一行开始考察,如果可以就考察第二列的第一行,如果不可以就考察第一列的第二行(即col[1] = 2)
	
    //初始化所有的行为未被占用状态
    for(j=0;j<=n;j++)
    {
        row[j]=0;
    }
	
    //初始化所有的斜线为未被占用状态
    for(j=0;j<2*n;j++)
    {
        b[j]=c[j]=0;
    }
	
    do
    {
        if(!occupied)
        {
            //已经考察到第n列,所以一次考察完毕。
            if(m==n)
            {
                //打印找到的一个解
				Display(col,n);

                //是否接着寻找下一个解
                cout<<"Find next solution? (Y/N)\n";
                cin>>next;
                if(next != 'Y' && next != 'y')
                {
                    return;
                }
				
                //已经找到一个解,回溯找下一个解。
                //一直回溯到还有行未被考察过的列
                while(col[m]==n)
                {
                    m--;
                    //在回溯过程中经过的每一列都需要将已经放入的皇后取出来,以备后面重新选择位置放入。
                    row[col[m]]=b[m+col[m]-1]=c[n+m-col[m]]=0;
                }
                //考察这一列的下一行
                col[m]++;
            }
            else
            {
                //因为occupied==false,所以皇后可以被放在当前考察列的当前考察行
                //将该皇后对应的行和两条斜线置为被占用状态
                row[col[m]]=b[m+col[m]-1]=c[n+m-col[m]]=1;
                //考察下一列的第一行
                col[++m]=1;
            }
        }
        else
        {
            //因为occupied==true,所以皇后不可以被放在当前考察列的当前考察行
            //一直回溯到还有行未被考察过的列
            while(col[m]==n)
            {
                m--;
                //在回溯过程中经过的每一列都需要将已经放入的皇后取出来,以备后面重新选择位置放入。
                row[col[m]]=b[m+col[m]-1]=c[n+m-col[m]]=0;
            }
            //考察这一列的下一行
            col[m]++;
        }
        //考察皇后是否可以被放在当前考察列的当前考察行,即她对应的行和两条斜线是否全部未被占用
        occupied=row[col[m]] || b[m+col[m]-1] || c[n+m-col[m]];
    }while(m!=0);//退到m == 0, 表示 1 ~ n 列的所有行都被考察过了,即考察过了所有可能的情况,整个考察过程结束。
	
    delete []row;
    delete []col;
    delete []b;
    delete []c;    
}

void Display(int col[],int n)
{
	char **map=new char*[n];
	for(int i=0;i<n;i++)
		map[i]=new char[n];
	for(i=0;i<n;i++)
		for(int j=0;j<n;j++)
			map[i][j]='+';
	for(i=1;i<=n;i++)
		map[col[i]-1][i-1]='0';
	for(i=0;i<n;i++)
	{
		for(int j=0;j<n;j++)
			cout<<map[i][j]<<' ';
		cout<<endl;
	}
	cout<<endl;
}

一部分测试结果



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值