在 N * N 的棋盘上,放置 N 个皇后,要求每一行,每一列,每一对角线上均只能放置一个皇后,求可能的方案及方案数。
对于 N = 8,采用回溯法很容易求解。
今天早上用了大约 10 分钟就搞定了,代码如下:
// n-queens 计数(朴素回溯) // by rappizit @ 2007-09-10 #include<cstdio> #include<cmath> usingnamespace std; constint N =20; // 最多 N 个皇后 int col [N]; // col [row] 表示第 row 行的皇后的列位置 int cnt =0; // 解法数目 int n =0; bool judge (int row) ...{ // judge whether the position (row, col [row]) is OK for (int i =0; i < row; i ++) ...{ if (col [i] == col [row] || abs (col [row] - col [i]) == row - i) ...{ returnfalse; } } returntrue; } void f (int row) ...{ if (row == n) ...{ // found a solution cnt ++; /**//* // print the solution for (int i = 0; i < n; i ++) { for (int j = 0; j < col [i]; j ++) { printf ("0 "); } printf ("1 "); for (int j = col [i] + 1; j < n; j ++) { printf ("0 "); } printf (" "); } printf (" "); */ } for (int i =0; i < n; i ++) ...{ col [row] = i; if (judge (row)) ...{ f (row +1); } } } int main () ...{ scanf ("%d", &n); // 输入皇后数目,不超过 20 f (0); printf ("total : %d ", cnt); return0; }
和普通算法一样,这是一个递归过程,程序一行一行地寻找可以放皇后的地方。过程带三个参数,row、ld和rd,分别表示在纵列和两个对角线方向的限制条件下这一行的哪些地方不能放。我们以6x6的棋盘为例,看看程序是怎么工作的。假设现在已经递归到第四层,前三层放的子已经标在左图上了。红色、蓝色和绿色的线分别表示三个方向上有冲突的位置,位于该行上的冲突位置就用row、ld和rd中的1来表示。把它们三个并起来,得到该行所有的禁位,取反后就得到所有可以放的位置(用pos来表示)。前面说过-a相当于not a + 1,这里的代码第6行就相当于pos and (not pos + 1),其结果是取出最右边的那个1。这样,p就表示该行的某个可以放子的位置,把它从pos中移除并递归调用test过程。注意递归调用时三个参数的变化,每个参数都加上了一个禁位,但两个对角线方向的禁位对下一行的影响需要平移一位。最后,如果递归到某个时候发现row=111111了,说明六个皇后全放进去了,此时程序从第1行跳到第11行,找到的解的个数加一。
// n-queens 一解(启发性回溯) // by rappizit @ 2007-09-10 #include<cstdio> #include <cmath> #include<algorithm> usingnamespace std; constint N =40; // 最多 N 个皇后 int col [N]; int cnt =0; int n =0; bool judge (int row) ...{ // judge whether the position (row, col [row]) is OK for (int i =0; i < row; i ++) ...{ if (col [i] == col [row] || abs (col [row] - col [i]) == row - i) ...{ returnfalse; } } returntrue; } void f (int row) ...{ if (row == n) ...{ // found a solution cnt ++; // print the solution for (int i =0; i < n; i ++) ...{ for (int j =0; j < col [i]; j ++) ...{ printf ("0 "); } printf ("1 "); for (int j = col [i] +1; j < n; j ++) ...{ printf ("0 "); } printf (""); } printf (""); } pair <int, int> diagonal [N]; for (int i =0; i < n; i ++) ...{ // 按照该位置在剩下的棋盘里的两条对角线较长的那条的长度作为启发函数的值 diagonal [i] = pair <int, int> (max (min (i, n -1- row), min (n -1- i, n -1- row)), i); } sort (diagonal, diagonal + n); for (int i =0; i < n &&!cnt; i ++) ...{ col [row] = diagonal [i].second; // 带启发性 //col [row] = i; // 盲目 if (judge (row)) ...{ f (row +1); } } } int main () ...{ scanf ("%d", &n); // 输入皇后数目,不超过 40 f (0); return0; } /**//* 只搜索一个解的情况下,带启发性的回溯效率提高很多,但是到了 40 皇后的时候还是 SB 了。 */