使用回溯法解决
问题描述:
n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。所谓不能相互攻击,是指任意两个皇后不能在同一行或同一列或同一对角线上。问总共有多少种摆法以及每种摆法是什么
题目分析:
先以4皇后问题为例,可以容易画出有4种解决方法。
每放一个皇后,需要考虑可以放置的位置,根据前面已经放置皇后的攻击范围选择可以放置的地方。同时记录每个皇后的位置。如果没有找到可放置的位置,那么说明前面的一个皇后放置的位置不对,需要回溯到上一个状态,也就是改变上一个皇后的放置位置,然后判断下一个皇后能不能找到安全位置;如果可以放上,那么递归执行下一行,也就是继续往下一行放皇后,直到所有行都被放上皇后时终止,当所有行都被放上皇后且不能相互攻击时,记录这一种解法。然后回溯到初始状态,改变第一行放置的位置,也就是放到第一行下一列,重复上述过程,找出剩余的解法
解决方案:
- 使用二维数组attack[N][N]表示棋盘中所有的位置是否可以放置皇后,初始化所有元素为0,0代表可以放,1代表不能放,处于其他皇后的攻击范围
- 使用二维数组queen[N][N]记录皇后位置,初始化所有元素为·(点)代表不放,Q代表放皇后
- 对于任一个位置(x,y),如果放上了皇后,那么这个位置有8个方向是攻击方向,呈米字形,
根据每个方向设置方向数组dx、dy,
static const int dx[] = {-1, 1, 0, 0, -1, -1, 1, 1};
static const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1};
<<<dx[j],dy[j]>>>就代表一个方向,每个方向需要延伸到棋盘边界。同时还不能越界,将可攻击的位置标记为1。
for(int i=1; i<attack.size(); i++){ // 从皇后位置向1到n-1个距离延伸
for(int j=0; j<8; j++){// 遍历8个方向
int nx = x + i*dx[j]; // 生成新的位置行
int ny = y + i*dy[j]; // 生成新的位置列
// 判断新位置是否在棋盘范围内,防止数组越界
if(nx>=0 && nx<attack.size() && ny>=0 && ny<attack.size()){
attack[nx][ny] = 1; // 将新位置标记为1
}
}
}
- 完整代码
#include <stdio.h>
#include <vector> //向量
#include <string>
using namespace std;
class Solution{
public:
vector<vector<string> > solveNQueens(int n){
vector<vector<string> > solve; //存储最后结果
vector<vector<int> > attack; //标记皇后的攻击位置
vector<string> queen; //保存皇后位置
//使用循环初始化attack和queen数组
for(int i=0; i<n; i++){
attack.push_back((std::vector<int>()));
for(int j=0; j<n; j++){
attack[i].push_back(0);
}
queen.push_back("");
queen[i].append(n,'.');
}
backtrack(0,n,queen,attack,solve);
return solve;
}
private:
// part1 放置皇后与标记棋盘
// 自定义函数put_queen ,实现在(x,y)放置皇后后,对attack数组的更新
// x,y表示放置皇后的坐标。二维数组attack表示棋盘是否可放置皇后
void put_queen(int x, int y, vector<vector<int> > &attack){ //> >之间要加空格,否则当成>>
// 方向数组,方便后面对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};
attack[x][y] = 1; // 将皇后位置标记为1
// 通过两层循环将该皇后可能攻击到的位置进行标记
for(int i=1; i<attack.size(); i++){ // 从皇后位置向1到n-1个距离延伸
for(int j=0; j<8; j++){// 遍历8个方向
int nx = x + i*dx[j]; // 生成新的位置行
int ny = y + i*dy[j]; // 生成新的位置列
// 判断新位置是否在棋盘范围内,防止数组越界
if(nx>=0 && nx<attack.size() && ny>=0 && ny<attack.size()){
attack[nx][ny] = 1; // 将新位置标记为1
}
}
}
}
// part2 回溯算法设计
// 回溯法求解N皇后的递归函数
// k表示当前处理的行
// n表示N皇后问题的规模
// queen存储皇后的位置
// attack标记皇后的攻击位置
// solve存储N皇后的全部解法
void backtrack(int k, int n, vector<string> &queen, vector<vector<int> > &attack, vector<vector<string> > &solve){
if(k==n){//找到一组解
solve.push_back(queen); //将结果queen存储至solve
return;
}
// 遍历0至n-1列,在循环中,回溯试探皇后可放置的位置
for (int i=0; i<n; i++){
if(attack[k][i]==0){//判断当前第k行第i列是否可以防止皇后
vector<vector<int> > tmp = attack; //备份attack数组
queen[k][i]='Q';//标记该位置为'Q'
put_queen(k,i,attack); //更新attack数组
backtrack(k+1, n, queen, attack, solve);//递归试探k+1行的皇后位置
attack = tmp;//恢复attack数组
queen[k][i]='.';//恢复queen数组
}
}
}
};
int main(){
vector<vector<string> > result;
Solution solution;
result=solution.solveNQueens(8);
printf("8皇后问题共有%d种解法:\n\n",result.size());
for(int i=0; i<result.size(); i++){
printf("解法%d:\n",i+1);
for(int j=0; j<result[i].size(); j++){
printf("%s\n", result[i][j].c_str());
}
printf("\n");
}
return 0;
}