摘要
八皇后问题是回溯算法的典型案例,在回溯法中,常常是盲目搜索,耗费过多的搜索时间。在本次实验中,使用了启发式搜索,搜索时不是任取一个分支,而是选择最佳的分支往下搜索。通过定义状态空间、操作规则、搜索策略,我们可以清晰快速地得到原问题的一个解。
导言
八皇后问题是一个以国际象棋为背景的问题:如何能够在 8×8 的国际象棋棋盘上放置八个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。通过计算机编程,我们可以快速地求出问题的解。
实验过程
状态空间
(i,C[i]), i = 0,1,…,7;
(i,C[i])表示第i行的皇后放置在第C[i]列。
初始状态为C[i] = -1, i = 0,1,…,7;表示所有行都不放置皇后。
目标状态为C[i] != -1, i = 0,1,…,7;表示所有行都已经放置了皇后。
操作规则
第一个皇后放在第一行;
第二个皇后放在第二行且不与第一个皇后在同一列或对角线的空格上;
……
第i个皇后放在第i行且不与前面i-1个皇后在同一列或对角线的空格上。
搜索策略
由于在某一步放置某个皇后时,可能有多个空格可以使用,所以定义启发式函数:
fx = 剩下未放行中能够用来放皇后的空格数
如果第i行的皇后放在第j列合法,计算启发式函数的值fx(i,j)。计算出第i行所有空格的fx后,将第i个皇后放到第i行中那个与前面i-1个皇后不在同一列或对角线上且fx值最大的空格中(相同时取第一个)。
如果当前策略无法求解,则回溯至上一步,选择fx值次大的空格放置皇后,依次类推,直至找到一个合法的解。
C++源代码
#include<iostream>
#include<cstring>
using namespace std;
const int n = 8;
int C[8]; //状态空间,C[i]表示第i行的皇后放在第C[i]列;
int fx[8][8]; //fx值,fx[i][j]表示在i,j处放置皇后后,剩下行中可以放Q的空格数
int ansflag = 0;//标记是否已经找到答案
bool vis[3][15];//vis[0][j]表示第j列有无皇后,vis[1 or 2][i+j]表示(i,j)正反对角线有无皇后
int f(int row)
{
//找出剩下行可以放Q的空格数。
int cnt = 0;
for (int i = row+1; i < n; i++)
for (int j = 0; j < n; j++)
if (!vis[0][j] && !vis[1][i+j] && !vis[2][i-j+n])
cnt++;
return cnt;
}
void search(int cur)
{
if (cur == n) ansflag++; //所有行都合法地放置了皇后,结束
else
{
int flag = 0; //标记该行是否可以放置皇后
for(int i = 0; i < n; i++) //对cur行,测试每一个空格
{
if (!vis[0][i] && !vis[1][cur+i] && !vis[2][cur-i+n])//不与前面皇后冲突
{
flag = 1; //该行可以放置皇后
C[cur] = i; //尝试将皇后放在第i列
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 1;
fx[cur][i] = f(cur); //计算fx值
vis[0][i] = vis[1][cur+i] = vis[2][cur-i+n] = 0;//计算完fx将皇后拿掉,尝试下一个格子
}
}
if (flag) //该行可以放置皇后,接下来尝试求解
{
while(!ansflag) //没有找到答案之前,进行回溯。
{
int max = -1;
int col = -1; //记录fx最大的列
for (int i = 0; i < n; i++) //找fx最大的列
{
if (fx[cur][i] > max)
{
max = fx[cur][i];
col = i;
}
}
if (max == -1) //在本行任一空格放置皇后都无法求解,回溯
{
fx[cur-1][C[cur-1]] = -1; //将原来的最大值置为-1,那么下一次回溯找的是次大值。
return;
}
C[cur] = col; //找到fx最大的列,放置皇后,搜索下一行。
vis[0][col] = vis[1][cur+col] = vis[2][cur-col+n] = 1;
search(cur+1);
vis[0][col] = vis[1][cur+col] = vis[2][cur-col+n] = 0;
}
}
else //如果该行无法放置皇后,将上一行中放置皇后的位置对于的fx置-1,回溯。
fx[cur-1][C[cur-1]] = -1;
}
}
int main()
{
memset(C, -1, sizeof(C));
memset(fx, -1, sizeof(fx));
search(0);
for (int i = 0; i < n; i++)
cout<<"第"<<i<<"个皇后放置在第"<<i<<"行第"<<C[i]<<"列"<<endl;
cout<<endl<<endl;
for (int i = 0; i < n; i++)
{
for (int j = 0; j < n; j++)
{
if (j == C[i])
cout<<"Q"<<' ';
else
cout<<"X"<<' ';
}
cout<<endl;
}
return 0;
}
结果
一个可行的解:
第0个皇后放置在第0行第0列
第1个皇后放置在第1行第5列
第2个皇后放置在第2行第7列
第3个皇后放置在第3行第2列
第4个皇后放置在第4行第6列
第5个皇后放置在第5行第3列
第6个皇后放置在第6行第1列
第7个皇后放置在第7行第4列
解对应的棋盘:
Q X X X X X X X
X X X X X Q X X
X X X X X X X Q
X X Q X X X X X
X X X X X X Q X
X X X Q X X X X
X Q X X X X X X
X X X X Q X X X