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 }