N皇后问题是一个经典的问题,在一个NXN得棋盘上放置N个皇后,每行一个并使其不能互相攻击(同一列、同一行、同一斜行上的皇后都会自动攻击)。
最初接触该问题是在学习人工智能时,在学习算法时再次接触该问题。N皇后问题时算法中回溯法应用的一个经典案例。
在着手解决该问题时,可以将其拆分为几个小问题。首先就是在棋盘上如何判断两个皇后是否能够相互攻击,在最初接触这个问题时,首先想到的方法就是把棋盘存储为一个二维数组,然后在需要在第i行第j列放置皇后时,根据问题的描述,首先判断是在第i行是否有皇后,由于每行只有一个皇后,这个判断也可以省略,然后判断第j列是否后皇后,这个也很简单,最后需要判断在同一斜线上是否有皇后,按照该方法需要判断两次,正对角线方向和负对角线方向,总体来说也不难。但是写完之后,总感觉很笨,应为在N皇后问题中这个函数的使用次数太多了,而这样做效率较差,个人感觉很不爽。上网查看了别人的实现之后大吃一惊,大牛们都是使用一个一维数组来存储棋盘,在某个位置上是否有皇后可以相互攻击的判断也很简单。具体细节如下:
把棋盘存储为一个N维数组a[N],数组中第i个元素的值代表第i行的皇后位置,这样便可以把问题的空间规模压缩维O(N),在判断是否冲突时也很简单,首先每行只有一个皇后,且在数组中只占据一个元素的位置,行冲突就不存在了,其次是列冲突,判断一下是否有a[i]与当前要放置皇后的列j相等即可。至于斜线冲突,通过观察可以发现所有在斜线上冲突的皇后的位置都有规律即它们所在的行列互减得绝对值相等,即| row – i | = | col – a[i] | 。这样某个位置是否可以放置皇后的问题已经解决。
下面要解决的是使用何种方法来找到所有的N皇后的解。上面说过该问题是回溯法的经典应用,所以可以使用回溯法来解决该问题,具体实现也有两个途径,递归和非递归。递归方法较为简单,大致思想如下:
void queen(int row)
{
if (n == row) //如果已经找到结果,则打印结果
print_result();
else {
for (k=0 to N) { //试探第row行每一个列
if (can_place(row, k) {
place(row, k); //放置皇后
queen(row + 1); //继续探测下一行
}
}
}
}
该方法由于在探测第i行后,如果找到一个可以放置皇后的位置j后,则会递归探测下一行,结束后则会继续探测i行j+1列,故可以找到所有的N皇后的解。
但是一般来说递归的效率比较差,下面重点讨论一下该问题的非递归实现。
非递归方法的一个重要问题时何时回溯及如何回溯的问题。程序首先对N行中的每一行进行探测,寻找该行中可以放置皇后的位置,具体方法是对该行的每一列进行探测,看是否可以放置皇后,如果可以,则在该列放置一个皇后,然后继续探测下一行的皇后位置。如果已经探测完所有的列都没有找到可以放置皇后的列,此时就应该回溯,把上一行皇后的位置往后移一列,如果上一行皇后移动后也找不到位置,则继续回溯直至某一行找到皇后的位置或回溯到第一行,如果第一行皇后也无法找到可以放置皇后的位置,则说明已经找到所有的解程序终止。如果该行已经是最后一行,则探测完该行后,如果找到放置皇后的位置,则说明找到一个结果,打印出来。但是此时并不能再此处结束程序,因为我们要找的是所有N皇后问题所有的解,此时应该清除该行的皇后,从当前放置皇后列数的下一列继续探测。
对于程序员来说程序才是王道,下面附上个人编写的代码,在VC++6.0和Linux下GCC编译通过,鄙陋指出望大牛不吝赐教。
·········10········20········30········40········50········60········70········80········90········100·······110·······120·······130·······140·······150
- /**
- * n皇后问题
- * date : 2010-3-12
- * author: lee
- */
-
- #include <stdio.h>
- #include <stdlib.h>
- #include <math.h>
-
- #define QUEEN 8 // the number of the queen
- #define INITIAL -10000 //defines the initial value of the board
-
- //container
- int a[QUEEN];
-
- //check if the queen can be placed on the position
- int valid(int row, int col);
- //initialize the board
- void clear();
- //print the result
- void print();
- //run the n-queen program
- void queen();
-
- int main(void)
- {
- clear();
- queen();
- return 0;
- }
-
- void clear()
- {
- int *p;
- for (p = a; p < a + QUEEN; ++p) {
- *p = INITIAL;
- }
- }
-
- void print()
- {
- int i, j;
- for (i = 0; i < QUEEN; ++i) {
- for (j = 0; j < QUEEN; ++j) {
- if (a[i] != j)
- printf("%c ", '.');
- else
- printf("%c ", '#');
- }
- printf("/n");
- }
- printf("--------------------------------------------/n");
- }
-
- int valid(int row, int col)
- {
- int i;
- for (i = 0; i < QUEEN; ++i) {
- if (a[i] == col || abs(i - row) == abs(a[i] - col))
- return 0;
- }
-
- return 1;
- }
-
- void queen()
- {
- int n = 0;
- int i = 0, j = 0;
- while (i < QUEEN) {
-
- while (j < QUEEN) {
- if (valid(i, j)) { //test if the queen can be placed on the position
- a[i] = j; //place the queen on the next line
- j = 0;
- break;
- } else { // if not, check the next position
- ++j;
- }
- }
-
- if (a[i] == INITIAL) { //if the current queen can't find its place
- if (i == 0) // and this is the first line ,then program end
- break;
- else { //else backtrack
- --i;
- j = a[i] + 1;
- a[i] = INITIAL;
- continue;
- }
- }
-
- if (i == QUEEN - 1) { //already got a solution, print the result
- printf("answer %d : /n", ++n);
- print();
- // _sleep(600);
- j = a[i] + 1;
- a[i] = INITIAL;
- continue;
-
- }
-
- ++i; // go on to place the queen on the next line if has any more
- }
- }