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
#include
#include
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.结束语
求解过程遇到的问题:
第一种情况回溯实现的是多解,而第二种情况实现的是求解。理解了这一点回溯的位置自然也就很容易找到了。
6.参考文献(网址)
https://blog.csdn.net/qinghezhen/article/details/17849837
主要是参考这篇博客里面给出的N皇后答案个数,依次来验证自己写的程序是否正确。