八皇后问题

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

 

                                                                                                                                                          
     国际象棋棋盘                                                     8皇后可行方案之一

                    图1 国际象棋棋盘                                                                             图2 一种可行的8皇后摆放方案

     

 

     首先,可归纳问题的条件为,8皇后之间需满足:

             1.不在同一行上

             2.不在同一列上

             3.不在同一斜线上

             4.不在同一反斜线上

 

     这为我们提供一种遍历的思路,我们可以逐行或者逐列来进行可行摆放方案的遍历,每一行(或列)遍历出一个符合条件的位置,接着就到下一行或列遍历下一个棋子的合适位置,这种遍历思路可以保证我们遍历过程中有一个条件是绝对符合的——就是下一个棋子的摆放位置与前面的棋子不在同一行(或列)。接下来,我们只要判断当前位置是否还符合其他条件,如果符合,就遍历下一行(或列)所有位置,看看是否继续有符合条件的位置,以此类推,如果某一个行(或列)的所有位置都不合适,就返回上一行(或列)继续该行(或列)的其他位置遍历,当我们顺利遍历到最后一行(或列),且有符合条件的位置时,就是一个可行的8皇后摆放方案,累加一次八皇后可行方案的个数,然后继续遍历该行其他位置是否有合适的,如果没有,则返回上一行,遍历该行其他位置,依此下去。这样一个过程下来,我们就可以得出所有符合条件的8皇后摆放方案了。这是一个深度优先遍历的过程,同时也是经典的递归思路。

     

      接下来,我们以逐列遍历,具体到代码,进一步说明。首先,从第一列开始找第一颗棋子的合适位置,我们知道,此时第一列的任何一个位置都是合适的,当棋子找到第一个合适的位置后,就开始到下一列考虑下一个合适的位置,此时,第二列的第一行及第二行显然就不能放第二颗棋子了,因为其与第一个棋子一个同在一行,一个同在一条斜线上。第二列第三行成为第二列第一个合适的位置,以此类推,第三列的第5行又会是一个合适位置,这个过程中,我们注意到,每一列的合适位置都是受到前面几列的位置所影响,归纳如下:

       

      假设前面1列的棋子放在第3行,那当前列不能放的位置就一定是3行,2行,4行。因为如果放在这三行上就分别跟前一列的棋子同在一行、同在斜线、同在反斜线上,不符合我们的要求。现在我们用cols数组来表示8个列棋子所放的行数,数组下标从0开始,其中数组下标表示列数,数组的元素值表示该列棋子所在行数,当前列为N(N>=0,N<8),即cols[N-1]=3,则有:

                          

                      cols[N] != cols[N-1](=3,表示不在同一行)

                     

                      cols[N] != cols[N-1]-1(=3-1=2,表示不在同一斜线上)

                     

                      cols[N]!=cols[N-1]+1(=3+1,表示不在同一反斜线上)

 

      这里我们注意到,如果N-2列存在的话,那么我们还要考虑当前列N不与N-2列的棋子同行,同斜线,同反斜线。把当前列N的前面的某一列设为m,则m的所有取值为{m>=0,m<N}的集合,故又可在上面式子的基础,归纳为如下:

                   

                    cols[N] != cols[m](与第m列的棋子不在同一行)

                   

                    cols[N] != cols­­[m] -(N-m)(>=0 ,与第m列的棋子不在同一斜线上)

                   

                    cols[N] != cols­­[m] + (N-m)  (<=8-1,与第m列的棋子不在同一反斜线上)          

为什么cols[m]-(N-m)代表正斜线:前后元素相加之后,顶格或者触底,回归到了同行问题上;反斜线同理。

 

           具体到代码,很显然,取m的所有值只需要一句循环,同时我们为每一列定义一个长度为8的布尔数组row[],下标同样是从0开始,我们规定当row[i]=true时,表示该列第i行不能放棋子。这样我们就能写成下列程序段了:

         

       

       

[java] view plain copy

  1. boolean[] rows = new boolean[8];  
  2.          
  3.   
  4.       for(int m=0;m<N;i++){  
  5.          
  6.   
  7.          rows[cols[m­]]=true;//当前列N的棋子不能放在前面列m的棋子所在行。  
  8.           
  9.   
  10.          int d = N-m;  
  11.   
  12.          
  13.   
  14.         //该句用于设置当前列N的棋子不能放在前面列m的棋子的斜线上  
  15.   
  16.          
  17.   
  18.         if(cols­­-d >= 0)rows[cols­-d]=true;   
  19.   
  20.        
  21.   
  22.        // 该句用于设置当前列N的棋子不能放在前面列m的棋子的反斜线上  
  23.   
  24.         
  25.   
  26.         if(cols+d <=8-1)rows[cols­+d]=true;    
  27.   
  28.   }   
  29.   
  30.          

           

 

 

       好了,到此为止,我们程序的核心内容都具备了,一个基于深度优先的遍历流程和一个判断位置是否合适的算法。下面贴出运行后算出的所有可行方案(即92种,“+”号代表空棋位,“0”代表皇后所在位置),源码(注源码变量名定义与上述略有不同,打印效果也不是图片所显示的效果,代码有做些微改动)。

       打印结果

 

 

[java] view plain copy

  1. public class Queen8 {  
  2.     public static int num = 0//累计方案总数  
  3.     public static final int MAXQUEEN = 8;//皇后个数,同时也是棋盘行列总数  
  4.     public static int[] cols = new int[MAXQUEEN]; //定义cols数组,表示8列棋子摆放情况  
  5.     public Queen8() {  
  6.        //核心函数  
  7.       getArrangement(0);  
  8.       System.out.print("/n");  
  9.       System.out.println(MAXQUEEN+"皇后问题有"+num+"种摆放方法。");  
  10.     }  
  11.       
  12.     public void  getArrangement(int n){  
  13.      //遍历该列所有不合法的行,并用rows数组记录,不合法即rows[i]=true  
  14.      boolean[] rows = new boolean[MAXQUEEN];  
  15.      for(int i=0;i<n;i++){  
  16.         rows[cols[i]]=true;  
  17.         int d = n-i;  
  18.         if(cols[i]-d >= 0)rows[cols[i]-d]=true;  
  19.         if(cols[i]+d <= MAXQUEEN-1)rows[cols[i]+d]=true;   
  20.         
  21.      }  
  22.      for(int i=0;i<MAXQUEEN;i++){  
  23.        //判断该行是否合法    
  24.        if(rows[i])continue;  
  25.        //设置当前列合法棋子所在行数  
  26.        cols[n] = i;  
  27.        //当前列不为最后一列时  
  28.        if(n<MAXQUEEN-1){  
  29.          getArrangement(n+1);  
  30.        }else{  
  31.   
  32.         //累计方案个数  
  33.          num++;  
  34.          //打印棋盘信息  
  35.          printChessBoard();  
  36.        }   
  37.         
  38.         
  39.      }  
  40.        
  41.     }  
  42.     public void printChessBoard(){  
  43.          
  44.        System.out.print("第"+num+"种走法 /n");  
  45.          
  46.        for(int i=0;i<MAXQUEEN;i++){  
  47.          for(int j=0;j<MAXQUEEN;j++){  
  48.            if(i==cols[j]){  
  49.              System.out.print("0 ");  
  50.            }else  
  51.              System.out.print("+ ");  
  52.          }  
  53.          System.out.print("/n");  
  54.        }  
  55.          
  56.     }  
  57.     public static void main(String args[]){  
  58.       Queen8 queen = new Queen8();  
  59.     }  
  60.      
  61. }  

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值