八皇后问题,是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。高斯认为有76种方案。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,可以快速解决此类问题。

回溯法的基本做法是搜索,或是一种组织得井井有条的,能避免不必要搜索的穷举式搜索法。这种方法适用于解一些组合数相当大的问题。
回溯法在问题的解空间树中,按深度优先策略,从根结点出发搜索解空间树。算法搜索至解空间树的任意一点时,先判断该结点是否包含问题的解。如果肯定不包含,则跳过对该结点为根的子树的搜索,逐层向其祖先结点回溯;否则,进入该子树,继续按深度优先策略搜索。
回溯法指导思想——走不通,就掉头。
八皇后问题是使用回溯法解决的典型案例。算法的解决思路是: 从棋盘的第一行开始,从第一个位置开始,依次判断当前位置是否能够放置皇后,判断的依据为:同该行之前的所有行中皇后的所在位置进行比较,如果在同一列,或者在同一条斜线上(斜线有两条,为正方形的两个对角线),都不符合要求,继续检验后序的位置。如果该行所有位置都不符合要求,则回溯到前一行,改变皇后的位置,继续试探。如果试探到最后一行,则所有皇后摆放完毕。最后一定要记得将棋盘恢复原样,避免影响下一次摆放。
下面是C++版的源代码,用回溯法求解N皇后问题:
#include <iostream>
#include <vector>
#include <array>
#include <string>
using namespace std;
const int N = 8; //N皇后问题
//attack NxN的二维数组,true可放值皇后的位置,false不可放皇后的位置
//实现在(x,y)处放置皇后,并根据规则更新attack数组。
void put_queen(int x, int y, array<array<bool,N>,N> &attack)
{
//180°、0°、90°、-90°、225°、135°、-45°、45°,皇后的8个攻击方向
static const int dx[] ={-1,1,0,0, -1,-1,1,1};
static const int dy[] ={0, 0,1,-1,-1,1,-1,1};
int i,j,new_x,new_y;
attack[x][y] = 1; //不能再放皇后的位置设为1
for(i=0;i<8;i++) //8个方向
{
for(j=1;j<N;j++)
{
new_x = x + dx[i]*j;
new_y = y + dy[i]*j;
if(new_x >=0 && new_x < N && new_y >=0 && new_y < N)
{
attack[new_x][new_y] = false;//不能再放皇后的位置设为False
}
else break;//棋盘越界则停止该方向上的搜索
}
}
}
//回溯法求解N皇后问题的递归函数
void backtrack(int row, vector<string> &queen, array<array<bool,N>,N> attack, vector<vector<string>> &solve)
{
//k 表示当前分析到的行号
//queen 存储皇后的位置
//attack 存储皇后的攻击范围
//solve 存储N皇后问题的全部解法
//退出递归的条件
if(row==N) //找到一组解
{
solve.push_back(queen); //将结果queen存储到solve
return;
}
//核心部分
for(int col =0; col< N; col++)//遍历各列,回溯试探皇后可放的位置
{
if(attack[row][col])//如果k行i列可放(可放为true)
{
array<array<bool,N>,N> temp = attack;//备份attack数组,以待回溯
queen[row][col] = 'Q';//放置皇后--标记为“Q"
put_queen(row,col,attack);//更新attack数组
backtrack(row+1,queen,attack,solve); //递归试探下一行
attack = temp;//恢复attack数组
queen[row][col]='.';//恢复queen数组
}
}
}
//设置棋盘初始状态,调用求解函数
vector<vector<string>> solveNQueens()
{
vector<vector<string>> solve;
array<array<bool,N>,N> attack;
vector<string> queen;
//设置初始状态
for(int i=0; i<N; i++)
{
for(int j=0; j<N; j++)
{
attack[i][j] = true;//初始全部位置可放皇后(可放则为true)
}
queen.push_back("");
queen[i].append(N,'.');//初始全部位置都无皇后,以点号表示
}
backtrack(0,queen,attack,solve);//调用backtrack函数求解
return solve;
}
int main()
{
vector<vector<string>> solve = solveNQueens();
cout <<"棋盘大小为"<<N <<"x"<<N<< "时,N皇后问题总共有"<< solve.size() <<"组解:"<<endl;
//cout <<sizeof(bool)<<endl;
for(int i=0; i< solve.size(); i++)
{
cout <<"第"<< i+1 <<"组解:"<<endl;
for(int row=0;row<N; row++)
{
cout<<solve[i][row]<<endl;
}
cout<<endl;
}
return 0;
}8皇后问题共有92组解。下图是最按行搜索的最后两组解。

12皇后问题共有14200组解。下图是最按行搜索的最后一组解。

本文分享自 Python可视化编程机器学习OpenCV 微信公众号,前往查看
如有侵权,请联系 cloudcommunity@tencent.com 删除。
本文参与 腾讯云自媒体同步曝光计划 ,欢迎热爱写作的你一起参与!