N皇后问题(回溯法)

使用回溯法解决


问题描述:

n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。所谓不能相互攻击,是指任意两个皇后不能在同一行或同一列或同一对角线上。问总共有多少种摆法以及每种摆法是什么

在这里插入图片描述

题目分析:

先以4皇后问题为例,可以容易画出有4种解决方法。
在这里插入图片描述

每放一个皇后,需要考虑可以放置的位置,根据前面已经放置皇后的攻击范围选择可以放置的地方。同时记录每个皇后的位置。如果没有找到可放置的位置,那么说明前面的一个皇后放置的位置不对,需要回溯到上一个状态,也就是改变上一个皇后的放置位置,然后判断下一个皇后能不能找到安全位置;如果可以放上,那么递归执行下一行,也就是继续往下一行放皇后,直到所有行都被放上皇后时终止,当所有行都被放上皇后且不能相互攻击时,记录这一种解法。然后回溯到初始状态,改变第一行放置的位置,也就是放到第一行下一列,重复上述过程,找出剩余的解法


解决方案:

  1. 使用二维数组attack[N][N]表示棋盘中所有的位置是否可以放置皇后,初始化所有元素为0,0代表可以放,1代表不能放,处于其他皇后的攻击范围
  2. 使用二维数组queen[N][N]记录皇后位置,初始化所有元素为·(点)代表不放,Q代表放皇后
  3. 对于任一个位置(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 
			} 
		}
	}
  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;
}


  • 1
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

雨陌潇潇

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值