八皇后问题

来源:互联网 发布:macbook免费清理软件 编辑:程序博客网 时间:2024/05/20 08:27

八皇后问题 —> N皇后问题(基于回溯法,核心思想是递推)

不管你是不是计算机专业的人,可能都听说过八皇后问题,毕竟这着实是一个很有趣的问题(#^.^#),之前一直想看看这个问题,但是也是因为各种“懒”,一直没沉下心去看,前几天又有一个大佬问我会不会八皇后,我的兴趣瞬间被提上来了,果然是要到研究它的时候了吗? >”<||||,哎呀呀,那今天我们就一起来好好学习学习吧
下面来具体介绍一下这个有趣的问题:

“八皇后问题”是一个古老而著名的问题,是回溯算法的典型案例。该问题是国际西洋棋棋手马克斯·贝瑟尔于1848年提出:在8×8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一 行、同一列或同一斜线上,问有多少种摆法。 高斯认为有76种方案(你学会的话,是不是你比高斯都6呢?O(∩_∩)O哈哈~)。1854年在柏林的象棋杂志上不同的作者发表了40种不同的解,后来有人用图论的方法解出92种结果。计算机发明后,有多种计算机语言可以解决此问题。

八皇后问题可以推广为更一般的n皇后摆放问题:这时棋盘的大小变为n1×n1,而皇后个数也变成n2。而且仅当 n2 = 1 或 n1 ≥ 3 时问题有解。


今天我会给大家三个代码的解释,分别是:

1.一个很正常的基于回溯法的,而且可以将棋盘打印出来的C++程序
2.一个能够较快计算3~32皇后问题的方案个数的C++代码(因为用位运算实现,所以比较快,但是好像算到17皇后之后还是很慢啊······)
3.最后一个是用Python写的能够打出一行棋盘的代码(虽然是一行,但是通过这一行的数字,你能够知道该方案的摆放方式)


下面先让我们看一下,1~26皇后分别对应多少种方案:
n a(n)
1 1
2 0
3 0
4 2
5 10
6 4
7 40
8 92
9 352
10 724
11 2680
12 14200
13 73712
14 365596
15 2279184
16 14772512
17 95815104
18 666090624
19 4968057848
20 39029188884
21 314666222712
22 2691008701644
23 24233937684440
24 227514171973736
25 2207893435808352
26 22317699616364044
细心的小伙伴是不是发现a(n) 并不是随着n的增大严格递增的?这个就很有意思了,有兴趣的可以想想怎么回事

第1个代码(可以将棋盘打印出来的C++程序):

因为注释比较多,你可以先粘到你的编译环境中查看:

#include<iostream>                  //该棋盘摆放方式是一列一列摆的,按照1~N的顺序,并在当前列找到合适摆放的行#include<cmath>#define N 8                         //这个N是可以改的,不多太大可能会炸 using namespace std;  bool matrix[N + 1][N + 1] = {0};                                    //用这样一个布尔类型的二维数组模拟棋盘 bool IsLegal(bool matrix[N + 1][N + 1], const int &i, const int &j)//  判断前面的i-1个棋子与matrix[i][j]是否冲突,i为1时合法  {        for (int m = 1; m <= i - 1; m++) {                      //m用来遍历行,遍历到i-1行,因为之前的棋子已经摆放到i-1行了,这是判断摆放第i行上的棋子是否合法         for (int n = 1; n <= N; n++) {                      //n用来遍历列,一共有8列             if (matrix[m][n] == 1) {                  if ( n == j || abs(i - m) == abs(j - n) )   //  n == j表示第j列上之前已经放上一个棋子了,所以是违法的,不能摆放到第j列,返回false;abs(i - m) == abs(j - n)表示两个棋子在同一斜线上,返回false                     return false;              }          }      }      return true;  }  void Print(bool matrix[N + 1][N + 1])  //这个函数用来打印最后摆放好的棋盘 {      static int count = 1;      printf("Case %d:\n", count++);      for (int i = 1; i <= N; i++) {          for (int j = 1; j <= N; j++) {              matrix[i][j] == 1 ? printf("%c ", 2) : printf(". "); //2对应的ASCII符号是一个黑色的笑脸,666了老铁 ,这个三目运算符不会的自己百度哦         }          cout << endl;      }      cout << endl;  }  void Trial(const int i) //进入本函数时,在N*N的棋盘前i-1行已放置了互不攻击的i-1个棋子,现从第i行起继续为后续棋子选择合适位置,这个函数是用递归写的,我着重说一下,递归是对自身的调用,当递到最后一步满足一定的条件时进行相应的操作 {      if (i > N)   //  很明显刚开始i = 1,N = 8不可能执行这一步操作 ,那什么时候执行呢?从后面的语句中,我们能够发现每次对自身的调用时都会对i+1,那么迟早i会=N,进而将最后的棋盘打印出来         Print(matrix);    else          for (int j = 1; j <= N; j++) {  //j代表列,i代表行,这个程序是一行一行地摆放棋子的            matrix[i][j] = 1;           //摆过棋子的地方赋值为1             if ( IsLegal(matrix, i, j) )  //判断所摆棋子的位置是否与前面摆的棋子冲突,如果不冲突,就摆下一行,即执行语句 Trial(i + 1);                Trial(i + 1);              matrix[i][j] = 0;  //这里写 matrix[i][j] = 0;而不写else matrix[i][j] = 0;的原因是(回溯在这里体现了出来) ,如果我写成else matrix[i][j] = 0那么回溯的时候 ,就不会把matrix[i][j] 变成0,因为在之前的判断中它是合法的,只不过在后面的摆放中没有找到合法的方案,而写成matrix[i][j] = 0的话,只要你拐了回来,就说明这条路你走不通,回来的时候你得把之前走过的路再变回去,才不会影响你之后的操作         }  }  int main(void)  {      Trial(1);  //刚开始从第一行开始摆放棋子     return 0;  }  ----------

第2个代码(较快计算3~32皇后问题的方案个数的C++代码):

这个代码,位运算用得简直就是漂亮:
这个八皇后问题其实也是用到了回溯法,只不过是位运算,所以就显得比数组运算效率高好多,这是目前我所见到的最优质的八皇后问题的解法,以下是我给大家的注释 (据说这个算法的作者是在96年,将此程序贴在了北京某BBs上),写得特别美 ,这个代码以一个变量的二进制位作为棋盘,突出了算法的深邃所在

因为注释比较多,你可以先粘到你的编译环境中查看:

#include<iostream> #include<cstdlib>#include<ctime>using namespace std;//这个代码是一行一行的找摆放棋子的合适位置,上面那个代码是一列一列找合适的位置 ,二进制码的位,对应的是列,若是1,表示该列上已经摆放过数了,而且是从棋盘右上角开始的long sum = 0, upperlim = 1;void test(long row, long ld, long rd)           //row表示已经有皇后的行(二进制位上为1的),ld、rd分别表示已经有皇后的斜列(这两个的作用是为了保证不产生在同一斜列的情况),每一位表示该列中可以摆放皇后的位置,1表示可以摆放,0表示不可以摆放。row在每查找下一列时不用改动,ld、rd需要分别左移和右移一位。{    if (row != upperlim){                       //当所有行都有皇后时说明摆放完毕        long pos = upperlim & ~(row | ld | rd); //pos的每一位表示该列中可以摆放皇后的位置,1表示可以摆放,0表示不可以摆放,upperlim & ~(row | ld | rd),我们以row为例row二进制码为1的表示已经摆上了棋子的行,那么~row对应的1就是没摆上棋子的行,再&upperlim(注意upperlim的每一位都是1),那么所得的pos是仅考虑row这个因素得到的哪一行是空着的,pos的二进制哪一位就是1         while (pos) {                           //如果pos中还有可以摆放的位置,并且将该行所有的能摆放棋子的列都遍历一遍             long p = pos & -pos;                 //p为pos中最末一个可以摆放的位置,这个大家就要看负数的二进制位里的补码和反码的概念了             pos -= p;                           //将p从可摆放位置去掉,就是说将pos中p这个位置去掉             test(row + p, (ld + p) << 1, (rd + p) >> 1); //寻找下一行的摆放位置。(ld + p) << 1, (rd + p) >> 1作用是为了保证不产生在同一斜列的情况(具体情况还是自己调试一下比较好,我暂时还不是很清楚,哪位大佬指点一下呗)         }    }    else sum++;}int main(){    time_t tm;                                  //用来记录运行时间     int n;                                      //16皇后和15皇后的运算时间差得挺多的    cin>>n;     tm = time(0);    if ((n < 1) || (n > 32)){                   //你要是敢算32,我估计你得等到白头,也不一定能出来         cout<<" 只能计算1-32之间"<<endl;        exit(-1);    }    cout<<n<<" 皇后:"<<endl;    upperlim = (upperlim << n) - 1;             //这里就是该程序只能算 n =(1~32)的原因,(注意位运算,不懂的最好自己先百度一下)意思是说将1用位运算左移n位减去1,这时upperlim的二进制码的1~(n-1)位将全为 1。     test(0, 0, 0);                              // 表示从 upperlim的二进制位的最右位开始放棋子,即棋盘的右上角开始摆放的     cout<<"共有"<<sum<<"种排列,计算时间"<< (int) (time(0) - tm)<<"秒"<<endl;    return 0; }

第3个代码(Python实现,一共10行,哈哈哈):

#-*-coding:UTF-8-*-                     #这个程序是一列一列寻找棋子的摆放位置的,和第一个代码在原理上相似,列是按0~n-1的顺序一列一列走的,并在当前列找到合适摆放的行def queen(A,cur=0):                     #cur表示该找第cur列棋子的位置(从第0列开始到第n-1列结束)    if cur == len(A): print A ;return   #如果所有的列上都有棋子,就打印棋盘    for col in range(len(A)):           #col每次都遍历0到n-1行,找出符合条件的位置        A[cur],flag = col,True          #A[cur] = col 表示第cur列第col行摆放棋子,flag标志是否合法,默认合法        for row in range(cur):          #row用来遍历之前已经摆上棋子的列,判断新摆上的棋子与之前的棋子是否冲突            if A[row] == col or abs(col-A[row])==cur - row:flag=False;break  #abs(col-A[row])==cur - row(用来判断是否在一条斜线上,我在棋盘上试了试,发现这个公式还真能判断两个棋子是否在一条斜线上),A[row] == col用来判断是否在同一行,第一个代码中也有这步操作,如果新摆上的棋子违法就标记为false        if flag:queen(A,cur+1)          #如果合法就找下一列摆放棋子的位置,不合法就看下一行摆上棋子是否合法n = (int)(input("请输入你要打印几皇后问题? "))queen([None]*n)                                 #传递一个列表实参这个可不是空列表,而是长度为n的每个元素都为None的列表

大佬要是有所收获,就帮忙顶一下吧,谢谢(^_^)∠※