N皇后问题----回溯法

N皇后问题描述为:如何能够在 n*n  的国际象棋棋盘上放置 n 个皇后,使得任何一个皇后都无法直接吃掉其他的皇后?为了达到此目的,任两个皇后都不能处于同一条横行、纵行或斜线上。

比如经典的8皇后问题的一个解:

                                    

一开始你也许会像我一样,想到用一个二维数组来实现,并且用线面描述的算法:

用一个N*N的矩阵来存储棋盘:

      1) 算法开始, 清空棋盘,当前行设为第一行,当前列设为第一列

      2) 在当前行,当前列的位置上判断是否满足条件(即保证经过这一点的行,列与斜线上都没有两个皇后),若不满足,跳到第4步

      3) 在当前位置上满足条件的情形:

                 在当前位置放一个皇后,若当前行是最后一行,记录一个解;

                 若当前行不是最后一行,当前行设为下一行, 当前列设为当前行的第一个待测位置;

                 若当前行是最后一行,当前列不是最后一列,当前列设为下一列;

                 若当前行是最后一行,当前列是最后一列,回溯,即清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一个待测位置;

                以上返回到第2步

      4) 在当前位置上不满足条件的情形:

                若当前列不是最后一列,当前列设为下一列,返回到第2步;

                若当前列是最后一列了,回溯,即,若当前行已经是第一行了,算法退出,否则,清空当前行及以下各行的棋盘,然后,当前行设为上一行,当前列设为当前行的下一    个待测位置,返回到第2步; 

当然,实际上我们的思路就该如此,但真的用这样一个二维数组去实现的话会发生什么事情?我们需要在每一个递归中遍历同行,同列, 两条对角线,那么我们要调用多少次的判断函数?并且当N的数值很大的时候,二维数组很明显是低效的。那么我们应该如何去实现呢?

首先,我们已经知道不可能有两个皇后在同一行,又一共有 n 个皇后,所以每行有且只有一位皇后。另外,同理我们可以知道每列有且只有一位皇后。

有了这个前提,那么我们就可以直接用一个有 n 大小的一位数组来储存了,每个元素用来储存列号,如图:                                                                                                                                                         

OK,现在我们有了一种较为轻便的储存方式,剩下的就是怎么得到一个有效的八皇后序列问题了。我们都知道回溯法,这个是遍历所有可能情况以得到满足条件的序列,那么我们就可以遍历所有的可能序列判断满不满足,而得到全部的序列的方法就可以根据我们的储存结构的特点,也即一位数组,来得到全排列,然后判断每一个全排列满不满足条件。。。全排列可以看看我前面的关于全排列的博文。。。

那么我们应该怎么判断一个全排列是不是满足条件呢?因为我们的储存结构的特点,各行列上都只有一个皇后了,那么久只需要判断对角线了,我们来看看一个8*8的棋盘:

                       

格子 [3 3] 和格子 [2 4] 我们可以发现 |3 - 2| == |3 - 4|,对 [3 5] 和 [4 4] 我们同样有 |3 - 4| == |5 - 4|,其他在同一对角线的元素都具有相同的性质,也就是说,在同一对角线上的元素会具有性质 |col_1 - col_2| == |row_1 - row_2|, 那么我们要判断两皇后是否在同一对角线,就只需要比较他们的行列值得对应差的绝对值了。

好了,到这里我们基本就可以得到N皇后问题的全部可能情况了,下面是实现的代码:

 1 #include <iostream>
 2 #include <stdio.h>
 3 #include <cmath>
 4 
 5 using namespace std;
 6 
 7 int count_;
 8 
 9 void output(int* queenArray, int numberOfQueen) {
10     printf("|--------------------------------\n|");
11     for (int i = 0; i != numberOfQueen; i++) {
12         for (int j = 0; j != numberOfQueen; j++) {
13             if (j == queenArray[i])
14                 printf(" $ |");
15             else
16                 printf(" * |");
17         }
18         printf("\n|");
19     }
20     printf("--------------------------------\n");
21 }
22 
23 void swap(int& a, int& b) {
24     int temp = a;
25     a = b;
26     b = temp;
27 }
28 
29 bool queenValuid(int* queenArray, int len) {
30     //判断某序列是否有两个皇后在同一对角线
31     for (int i = 0; i != len; i++) {
32         for (int j = i + 1; j != len; j++) {
33             if (abs(j - i) == abs(queenArray[j] - queenArray[i]))
34                 return false;
35         }
36     }
37     return true;
38 }
39 
40 void queen(int* queenArray, int begin, int end) {
41     if (begin == end) {
42         if (queenValuid(queenArray, end + 1)) {
43             count_++;
44             output(queenArray, end + 1);
45         }
46     } else {
47         //全排列
48         for (int i = begin; i <= end; i++) {
49             swap(queenArray[begin], queenArray[i]);
50             queen(queenArray, begin + 1, end);
51             swap(queenArray[begin], queenArray[i]);
52         }
53     }
54 }
55 
56 int main(int argc, char const *argv[])
57 {
58     char command;
59     int *queenArray;
60     int numberOfQueen;
61     do {
62         int start = clock();
63         {
64             count_ = 0;
65             printf("Enter the number of the queen: ");
66             scanf ("%d", &numberOfQueen);
67 
68             queenArray = new int[numberOfQueen];
69             for (int i = 0; i != numberOfQueen; i++)
70                 queenArray[i] = i;
71 
72             queen(queenArray, 0, numberOfQueen - 1);
73             printf("Total queen seq: %d\n", count_);
74 
75             delete []queenArray;
76         }
77         printf("\nrun time: %.3lf ms\n",double(clock()-start)/CLOCKS_PER_SEC);
78 
79         printf("Again?<Y/N>\n");
80         scanf("\n%c", &command);
81     } while (command == 'Y' || command == 'y');
82     
83     return 0;
84 }

 

posted @ 2013-11-29 18:33  厕所门口~~  阅读(649)  评论(0编辑  收藏  举报