N皇后问题(位运算版本)
问题描述
在一个 的国际象棋棋盘上放置 个皇后,使得任意两个皇后都不能处于同一行、同一列或同一斜线上。目标是找出所有满足该条件的皇后放置方案。
时间复杂度是O(n!),对于n 乘 n的格子,每行都有n种选择,选择可能依次递减。同时要便随着大量剪枝。
一,经典解法
调用递归,使用类的成员变量或全局变量,引用传参等方法收集可能。
里面关于如何判断两个皇后是否在同一条斜线上,利用共斜线的话是斜率等于1或-1,利用斜率,即两个的坐标:行的变化量等于列的变化量。
要有封装和简化的思想。将原来代码量大的函数抽离一部分封装成函数,既使代码简洁,又方便后面修改。
思路
依次检验每个坐标符不符合条件,如果符合就收集进temp临时向量中,知道遇到终止条件后将temp添加进ans里,return返回。
代码
class NQueen
{
public:
NQueen(int N);
void printAns();
~NQueen() = default;
private:
int N;
std::vector<std::vector<std::pair<int,int>>> ans;
std::vector<std::pair<int,int>> temp;
void recursion(int row, int col);
bool isValid(int row, int col, std::vector<std::pair<int,int>>& temp);
};
NQueen::NQueen(int N) : N(N)
{
}
void NQueen::printAns()
{
recursion(0, 0);
std::vector<std::vector<int>> board(N, std::vector<int>(N, 0));
for (const auto& it : ans)
{
for (const auto& jt : it)
{
board[jt.first][jt.second] = 1;
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
if (board[i][j] == 1) cout << "Q ";
else cout << "* ";
}
cout << endl;
}
for (int i = 0; i < 2 * N - 1; i++)
{
cout << "=";
}
cout << endl;
board.clear();
board.resize(N, std::vector<int>(N, 0));
}
}
void NQueen::recursion(int row, int col)
{
if (row == N)
{
ans.push_back(temp);
return;
}
for (int i = 0; i < N; i++)
{
if (isValid(row, i, temp))
{
temp.push_back(std::make_pair(row, i));
recursion(row + 1, col);
temp.pop_back();
}
}
}
bool NQueen::isValid(int row, int col, std::vector<std::pair<int, int>>& temp)
{
for (const auto& it : temp)
{
if (it.first == row || it.second == col || abs(it.first - row) == abs(it.second - col))
{
return false;
}
}
return true;
}
二,位运算版本
前置知识
可以看下我的博客有关于位运算的那几节。
位运算实现加减乘除
位图的学习
位运算的骚操作
首先我应该明白int类型数字底层是二进制数字0和1的组合。
int类型4字节,32bit。
思路
根据要求可以知道每行最多一个皇后,而又是递归,递归时行数row要更新加一。所以行上面没有限制,再后面temp.push_back()时只需要考虑列和斜对角的限制。
利用数字bit位上1和0来表示该位置能否放置皇后,跟我之前字符串的递归子序列中标记数组一样的功能。
同时利用位运算来更新限制。
列和斜对角线的影响在本层递归放置皇后后,进行更新限制。利用&和|逻辑运算合并这些限制。
通过位移来实现斜对线的更新
直接上代码
优势
为什么更快,虽然递归是通过值传递来传递影响但是位运算太快,弥补这上面的缺陷。
代码
注释版
// 递归求解 N 皇后问题的函数
// row: 当前正在处理的行号
// left: 表示从左上到右下对角线方向上的冲突信息,使用位运算存储
// right: 表示从右上到左下对角线方向上的冲突信息,使用位运算存储
// col: 表示列方向上的冲突信息,使用位运算存储
void NQueen::recursion2(int row, int left, int right, int col)
{
// 当 row 等于 N 时,说明已经成功放置了 N 个皇后,找到了一个解
if (row == N)
{
// 将当前的皇后放置方案添加到结果集合 ans 中
ans.push_back(temp);
// 回溯,结束当前递归调用
return;
}
// 计算当前行所有不能放置皇后的位置
// left | right | col 是将三个方向的冲突信息进行按位或运算
// 得到的结果中,为 1 的位表示该位置不能放置皇后
int limit = left | right | col;
// 遍历当前行的每一列
for (int i = 0; i < N; i++)
{
// 检查当前位置 (row, i) 是否可以放置皇后
// isValid 函数用于判断该位置是否与已放置的皇后冲突
if (isValid(limit, i))
{
// 如果该位置可以放置皇后,将其添加到临时方案 temp 中
temp.push_back(std::make_pair(row, i));
// 计算下一行在各个方向上的冲突信息
// (left | (1 << i)) << 1: 将当前列的左对角线冲突信息更新
// 1 << i 表示将 1 左移 i 位,得到当前列的位置信息
// 与 left 按位或运算后,再左移一位,表示下一行的左对角线冲突
int _left = (left | (1 << i)) << 1;
// (right | (1 << i)) >> 1: 将当前列的右对角线冲突信息更新
// 与 left 类似,只不过是右移一位,表示下一行的右对角线冲突
int _right = (right | (1 << i)) >> 1;
// (col | (1 << i)): 将当前列的列冲突信息更新
// 表示该列已经被占用
int _col = (col | (1 << i));
// 递归调用 recursion2 函数,处理下一行
recursion2(row + 1, _left, _right, _col);
// 回溯操作,撤销当前选择
// 将该位置从临时方案 temp 中移除,尝试其他可能的位置
temp.pop_back();
}
}
}
class NQueen
{
public:
NQueen(int N);
void printAns();
void printAnsByBit();
~NQueen() = default;
private:
int N;
std::vector<std::vector<std::pair<int,int>>> ans;
std::vector<std::pair<int,int>> temp;
void recursion(int row, int col);
void recursion2(int row, int left, int right, int col);
bool isValid(int row, int col, std::vector<std::pair<int,int>>& temp);
void moveBit(int&, int);
bool isValid(int, int);
};
void NQueen::printAnsByBit()
{
recursion2(0, 0, 0, 0);
std::vector<std::vector<int>> board(N, std::vector<int>(N, 0));
for (const auto& it : ans)
{
for (const auto& jt : it)
{
board[jt.first][jt.second] = 1;
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
if (board[i][j] == 1) cout << "Q ";
else cout << "* ";
}
cout << endl;
}
for (int i = 0; i < 2 * N - 1; i++)
{
cout << "=";
}
cout << endl;
board.clear();
board.resize(N, std::vector<int>(N, 0));
}
}
void NQueen::printAnsByBit()
{
recursion2(0, 0, 0, 0);
std::vector<std::vector<int>> board(N, std::vector<int>(N, 0));
for (const auto& it : ans)
{
for (const auto& jt : it)
{
board[jt.first][jt.second] = 1;
}
for (int i = 0; i < N; i++)
{
for (int j = 0; j < N; j++)
{
if (board[i][j] == 1) cout << "Q ";
else cout << "* ";
}
cout << endl;
}
for (int i = 0; i < 2 * N - 1; i++)
{
cout << "=";
}
cout << endl;
board.clear();
board.resize(N, std::vector<int>(N, 0));
}
}