八皇后问题是回溯算法的典型案例,在回溯法中,常常是盲目搜索,耗费过多的搜索时间。在本次实验中,使用了启发式搜索,搜索时不是任取一个分支,而是选择最佳的分支往下搜索。通过定义状态空间、操作规则、搜索策略,我们可以清晰快速地得到原问题的一个解。
八皇后问题是一个以国际象棋为背景的问题:如何能够在 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值次大的空格放置皇后,依次类推,直至找到一个合法的解。
#include<stdio.h> #include <cstdio> #include<string> #include<math.h> #include<stdlib.h> #include<set> #include<map> #include<vector> #include<queue> #include<string.h> #include<algorithm> #include<iostream> #include<time.h> #include<list> using namespace std; const int n=8; int c[8]; //c[i]表示第i行的皇后放在第c[i]列 int fx[8][8]; //fx[i][j]表示在i行j列放置皇后后,剩下行中可以放Q的空格数 int ansflag=0; //标记是否已经找到答案 int vis[3][16]; //vis[0][j]表示第j列有无皇后,vis[1][i+j]表示对角线/上的和相同),vis[2][i-j+n]表示对角线上的差相同,+n避免负数 //启发式函数f():找到剩下行可以放Q的空格数 int f(int row) { 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++; //所有行都合法的放置了Q,结束 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; //[cur][i]处可以放置Q c[cur]=i; vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=1; fx[cur][i]=f(cur); //计算启发式函数 vis[0][i]=vis[1][cur+i]=vis[2][cur-i+n]=0; } } 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[cur-1][c[cur-1]]=-1; } } int main() { memset(c,-1,sizeof(c)); memset(fx,-1,sizeof(fx)); memset(vis,0,sizeof(vis)); search(0); 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; } }
解对应的棋盘:
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