北京高端建设网站网络营销案例视频
问题描述
在一个 的国际象棋棋盘上放置 个皇后,使得任意两个皇后都不能处于同一行、同一列或同一斜线上。目标是找出所有满足该条件的皇后放置方案。
时间复杂度是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));}}