课设——八皇后问题(N皇后解决)

1. 问题定义与需求分析

1、问题描述:

八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

基本要求:

在一个8×8的棋盘里放置8个皇后,要求每个皇后两两之间不相"冲突"(在每一横列竖列斜列只有一个皇后)。

1、冲突。包括行、列、两条对角线:

(1)列:规定每一列放一个皇后,不会造成列上的冲突;

(2)行:当第i行被某个皇后占领后,则同一行上的所有空格都不能再放皇后,要把以i为下标的行位置标为被占领状态;

(3)对角线:对角线有两个方向。在同一对角线上的所有点(设下标为(i,j)),要么(i+j)是常数,要么(i-j)是常数。因此,当第i个皇后占领了第j列后,要同时把以(i+j)、(i-j)为下标的标记置为被占领状态。

2、数据结构。

(1)解数组A。A[i]表示第i个皇后放置的列;范围:1..8

(2)行冲突标记数组B。B[i]=0表示第i行空闲;B[i]=1表示第i行被占领;范围:1..8

(3)对角线冲突标记数组C、D。

C[I-J]=0表示第(I-J)条对角线空闲;C[I-J]=1表示第(I-J)条对角线被占领;范围:-7..7

D[I+J]=0表示第(I+J)条对角线空闲;D[I+J]=1表示第(I+J)条对角线被占领;范围:2..16

实现提示:

1、数据初始化。

2、从n列开始摆放第n个皇后(因为这样便可以符合每一竖列一个皇后的要求),先测试当前位置(n,m)是否等于0(未被占领):如果是,摆放第n个皇后,并宣布占领,接着进行递归;如果不是,测试下一个位置(n,m+1),但是如果当n<=8,m=8时,却发现此时已经无法摆放时,便要进行回溯。

3、当n>8时,便一一打印出结果。

4.回溯算法的实现

(1)为解决这个问题,我们把棋盘的横坐标定为i,纵坐标定为j,i和j的取值范围是从1到8。当某个皇后占了位置(i,j)时,在这个位置的垂直方向、水平方向和斜线方向都不能再放其它皇后了。用语句实现,可定义如下三个整型数组:a[8],b[15],c[24]。

其中:

a[j-1]=1 第j列上无皇后

a[j-1]=0 第j列上有皇后

b[i+j-2]=1 (i,j)的对角线(左上至右下)无皇后

b[i+j-2]=0 (i,j)的对角线(左上至右下)有皇后

c[i-j+7]=1 (i,j)的对角线(右上至左下)无皇后

c[i-j+7]=0 (i,j)的对角线(右上至左下)有皇后

(2)为第i个皇后选择位置的算法如下:

for(j=1;j<=8;j++) /*第i个皇后在第j行*/
if ((i,j)位置为空)) /*即相应的三个数组的对应元素值为1*/

{占用位置(i,j) /*置相应的三个数组对应的元素值为0*/

if i<8为i+1个皇后选择合适的位置;

else 输出一个解

}

2.概要设计

一行一行的放置皇后,所以不需要判断行冲突。判断列冲突很简单,直接和前面的比一下是否一样即可,而对于对角线冲突,就有一个特殊的小技巧:由于每一条主对角线(x-y)是一定的,每一条副对角线(x+y)是一定的。于是,我们通过判断那些定值与前面已经放置的皇后的定值比较即可判断是否冲突。

在最终的N皇后求解程序中,除了算法部分,还加入了界面优化,多次求解的询问,以及清屏操作。

3.详细设计

数据结构设计

A表示第i个皇后放置的列 (解数组)

B表示列冲突标记数组

C、D表示对角线冲突标记数组:处理的时候注意为了不让数组越界C[i+j-2] D[i-j+7]

N 皇后 C[i+j-2]  D[i-j+n-1]

 

 

 算法求解部分伪代码如下:

为第i个皇后选择所在行

{

   从第一个位置开始一个一个尝试

   {

         if 该位置合法

         {

             放置并标记冲突位置

               if 所有皇后还未处理完

                     放置 第i+1个皇后

               else

                 找到一个解并输出

               回溯 把之前的标记还原 (下一个位置可能放置成功了也可能失败了)

         }

   }

}

4. 编码与测试分析

测试:

源代码如下:

#include<iostream>

#include<stdio.h>

#include<stdlib.h>

using namespace std;

const int MAXN = 100;  //n皇后最大n=100

int ans;

int A[MAXN], B[MAXN], C[2 * MAXN], D[2 * MAXN];

/*

    A表示第i个皇后放置的列 (解数组)

    B表示列冲突标记数组

    C、D表示对角线冲突标记数组:处理的时候注意为了不让数组越界C[i+j-2] D[i-j+7]

    N 皇后 C[i+j-2]  D[i-j+n-1]

*/

void show(int* a, int n)

{

    cout << "case" << ans << ":" << endl;

    for (int i = 1; i <= n; i++)

    {

        int c = a[i];

        for (int i = 1; i < c; i++) cout << "[]";

        cout << " &";

        for (int j = 1; j <= n - c; j++) cout << "[]";

        cout << endl;

    }

    cout << endl;

}



void solve(int i, int n)//为第i个皇后选择所在行

{

    for (int j = 1; j <= n; j++)

    {

        if ((B[j] == 0) && (C[i + j - 2] == 0) && (D[i - j + n - 1] == 0)) //该位置合法

        {

             //cout << "i: " << i << "j: " << j << endl;

             A[i] = j;

             //放置并标记冲突位置

             B[j] = 1;

             C[j + i - 2] = 1;

             D[i - j + n - 1] = 1;

             if (i < n)

             {

                 solve(i + 1, n); //放置下一个皇后



             }

             else {

                 ans++;

                 show(A, n);

             }

             //回溯 把之前的标记还原 (下一个位置可能放置成功了也可能失败了)

             B[j] = 0;

             C[j + i - 2] = 0;

             D[i - j + n - 1] = 0;

        }

    }

}





int main()

{

    system("color F5");

    int n, ch=1;

    while (ch)

    {

        cout << "N皇后问题求解(输入N):";

        cin >> n;

        ans = 0;

        memset(A, 0, sizeof(int));

        memset(B, 0, sizeof(int));

        memset(C, 0, sizeof(int));

        memset(D, 0, sizeof(int));

        //初始化

        solve(1, n);

        if (!ans) cout << "该问题无解" << endl;

        cout << endl << endl << endl << "是否继续求解?(1是0否)";

        cin >> ch;

        system("cls");           //清屏 

    }

    return 0;

}

5.结束语

求解过程遇到的问题:

  1. 对于回溯位置的选取:一开始其实是不太理解应该在哪个地方进行回溯的。因为在需求中给出了整体算法的框架,关键的回溯操作空出来了。基于这个问题是要求多解,因此在搜索过程中必然需要用到回溯。值得注意的是这个问题需要回溯的情况有两种
    1. 已经找到一解。(多解)
    2. 无法继续求解。(求解)

第一种情况回溯实现的是多解,而第二种情况实现的是求解。理解了这一点回溯的位置自然也就很容易找到了。

  1. 对于递归的理解:本题对于回溯的操作是在下一步的递归后面还原上一步的标记(也就是回溯),一开始对于这里也有疑惑。为什么只还原上一步的标记,递归过程中每走一步状态标记数组都会发生变化。这里需要明确和真正理解什么是递归,它实际上是一层一层嵌套的结果,也就是说内层的状态改变只会影响更内层的,而不会影响外层递归

 

6.参考文献(网址)

https://blog.csdn.net/qinghezhen/article/details/17849837

主要是参考这篇博客里面给出的N皇后答案个数,依次来验证自己写的程序是否正确。

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值