建模
不妨以树的抽象结构来思考所有可能的情况:由于每一行最多且必须放一个皇后,因此一行一行地放不仅简化了步骤,更方便按行枚举;又由于 N 皇后是不确定的,使用递归便于控制循环。
剪枝优化
「剪枝」一词十分贴切,剪的是树的一个节点,剪下的是这个节点后面所有的枝。如果某一个位置冲突了,整体必然冲突,后面的行也不用看了,直接跳出以减少时间的浪费。开一个一维数组存放每层的皇后所在的列,基于前面皇后的位置,编写一个用于判断某位置是否与前面冲突的函数。
代码实现
一开始自作聪明,想着走过的路不必再走,每一层从 queen[row]
开始。然而这是错误的,换一个角度看:每一层都来自上一层的递归,既然才递过来,后面的路都是没有走过的,因此都要从头走。之前的错误在于走到最后不退回去,循环变成一次性的了。以下是代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <iostream> #include <vector> #include <cmath>
#define MAXN 17 using namespace std; int N; long long sum; vector<int> queen(MAXN, 0);
bool conflict(int row, int cow) { if (row == 0) return false; for (int i = 0; i < row; i++) { if (queen[i] == cow || row - i == abs(cow - queen[i])) { return true; } } return false; }
void DFS(int row) { if (row >= N) { sum++; return; } for (int i = 0; i < N; i++) { if (!conflict(row, i)) { queen[row] = i; DFS(row + 1); } } }
int main() { cin >> N; DFS(0); cout << sum; return 0; }
|
此外,如果追求更高的速度,可以开一个二维数组记录冲突情况,随用随取,避免了 conflict()
函数的运算时间。如果对性能有极致的追求,位运算也大有用武之地。我嘛,就先咕咕咕了。