一、n皇后问题介绍
在一个n*n的棋盘放置n个皇后,使皇后彼此之间不能相互攻击,满足:
1、同一行只能有一个皇后
2、同一列只能有一个皇后
3、每个皇后所在的主次对角线只有一个皇后,即它本身
共有多少种满足要求的摆法?
例如:
**.*
.***
***. 这是一种4*4中的一种摆放方式,其中'*'代表没有放置,'.'代表
*.** 放置了皇后
二、解题思路
可以从上往下依次摆放皇后,对于每一行,检查该行每一列是否满足摆放的
要求,如若满足,继续摆放下一行,直至每一行都有符合要求的皇后,得到
一种可行的摆放方案,然后通过回溯尝试不同的可能性。
三、实现流程
1.首先定义一个n*n的二维数组,为方便,可以将cheeboard[i][k]=0定义
成未放皇后,1为放置了皇后
2.然后调用backtracking函数,用for循环从第一行开始对每一列测试放皇
后,当然首先要满足那三个要求,这里可以用isValid函数对该位置能不能放
皇后进行判断,如若返回真,则将cheeboard[i][k]赋值为1,并调用
backtracking函数进行下一行皇后的摆放,最后回溯将cheeboard[i][k]
赋值为0,看看该行其余列是否可以放皇后
3.对于backtracking的返回条件,当然是当row(当前所在行) = n,即已经
将每一行都放置了皇后后结束,同时sum++,这样就找到了一种摆法,通过不断
重复这些操作,即可将所有情况找出来。
四、代码实现
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10;
int sum = 0; // 摆放的总数
bool isValid(int chessboard[MAXN][MAXN], int n, int row, int col){
for (int i = 0; i < row; i ++) { // 判断是否同列,因为是从上往下一行一行来的,所以1到row-1即可
if (chessboard[i][col] == 1) // 且不用再判断是否是同一行了
return false;
}
for (int i = row - 1, k = col - 1; i >= 0 && k >= 0; i --, k --) {
if (chessboard[i][k] == 1) // 判断与主对角线平行的对角线,即左上的斜线
return false;
}
for (int i = row - 1, k = col + 1; i >= 0 && k < n; i --, k ++) {
if (chessboard[i][k] == 1) // 判断与次对角线平行的对角线,即右上的斜线
return false;
}
return true;
}
void backtracking(int chessboard[MAXN][MAXN], int n, int row){
if(row == n) { // 所有行都放了皇后
sum ++;
return;
}
for (int i = 0; i < n; i ++) { // 对该行每一列测试放皇后
if (isValid(chessboard, n, row, i)) {
chessboard[row][i] = 1;
backtracking(chessboard, n, row + 1); // 进行下一行的放置
chessboard[row][i] = 0; // 回溯
}
}
}
int main() {
int n;
cin >> n;
int chessboard[MAXN][MAXN] = {0};
backtracking(chessboard, n, 0);
cout << sum;
return 0;
}
五、改进方法
上面的方法是通过对每一行每一列依次判断的,并且isValid用了三个for
循环,因此会有点浪费时间,那么有没有什么可以优化的地方呢?答案是有
的。
我们可以将二维数组直接降为一维数组,通过对二维数组的定义,可以发现,
只有放置了皇后的才置为了1,其余都为0。那么实际上我们可以定义一个
result数组,其中result[i] = k表示第i行的第k列放置了皇后(假设
下标从0开始)
那么可以通过for循环对result[i]不同的赋值(0到n-1)实现在每一行的每一
列测试放置皇后,同时isValid函数中可以只用一个for循环解决,其中判断
条件为(result[i] = col || abs(row-i) == abs(col-result[i])),
前者判断该列是否已经有了皇后(result[i]存储的是第i行的皇后位置,后者
判断该位置(row,col)的同一斜线是否有皇后,因为如果在同一斜线上,行数
之差等于列数之差
这样,我们就实现了优化,下面是代码:
#include <bits/stdc++.h>
using namespace std;
const int MAXN = 10;
int sum = 0; // 摆放的总数
int result[MAXN] = {0};
bool isValid(int n, int row, int col){
for (int i = 0; i < row; i ++) { // 判断是否同列,因为是从上往下一行一行来的,所以1到row-1即可
if (result[i] == col || abs(row - i) == abs(col - result[i]))
return false;
}
return true;
}
void backtracking(int n, int row){
if(row == n) { // 所有行都放了皇后
sum ++;
return;
}
for (int i = 0; i < n; i ++) { // 对该行每一列测试放皇后
if (isValid(n, row, i)) {
result[row] = i; // 注意这里的写法,表示第row行第i列放置了皇后
backtracking(n, row + 1); // 进行下一行的放置
result[row] = 0; // 回溯
}
}
}
int main() {
int n;
cin >> n;
backtracking(n, 0);
cout << sum;
return 0;
}
六、相关题目推荐
1.洛谷P1219 [USACO1.5]八皇后 Checker Challenge
2.力扣51–n皇后(该题需要知道一些vector的用法)
3.蓝桥杯官网–受伤的皇后
七、结语
每道题问的不一定一样,需要根据具体题目灵活变通,n皇后问题最重要的是理解其中的回溯思想。希望这篇文章有所帮助!qwq