• 2、八皇后问题——回溯法


    八皇后问题是一个古老而著名的问题,是回溯算法的典型例题。该问题是十九世纪著名的数学家高斯1850年提出:在8X8格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

      高斯认为有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列的棋子不在同一反斜线上)          

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

             

           

           

    [java] view plaincopy
     
    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.          

               

    [java] view plaincopy
     
    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. }  

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

         打印结果

    /**
     * 
     */
    package unit1;
    
    /**
     * @author 
     * @version 创建时间:2015-10-30 下午02:55:24 类说明
     */
    public class EightQueensNotRecursive {
        private static final boolean AVAILABLE = true;
        private int squares = 16, norm = squares - 1;
        private int positionInRow[] = new int[squares];
        private int p = -1;
        private boolean[] rows = new boolean[squares];
        private boolean[] column = new boolean[squares];
        private boolean[] leftDiagonal = new boolean[2 * squares - 1];
        private boolean[] rightDiagonal = new boolean[2 * squares - 1];
        private static int howMany = 0;
    
        public EightQueensNotRecursive() {
            // To complete the initialization work for the
            // column,leftDiagonal,rigthDiagonal.
            for (int i = 0; i < squares; i++) {
                rows[i] = AVAILABLE;
                column[i] = AVAILABLE;
                positionInRow[i] = -1;
            }
            for (int i = 0; i < 2 * squares - 1; i++) {
                leftDiagonal[i] = AVAILABLE;
                rightDiagonal[i] = AVAILABLE;
            }
    
        }
    
        public void printResults(int[] columns) {
            int row, col;
            System.out.println("八皇后问题的第 " + howMany + " 种解法");
            System.out.print("八皇后问题的结果为:");
            for (int e : columns) {
                System.out.print(e);
            }
            System.out.println("
    具体的图示如下图所示:");
            for (row = 0; row < squares; row++) {
                for (col = 0; col < squares; col++) {
                    if (col == positionInRow[row]) {
                        System.out.print("@");
                    } else {
                        System.out.print("*");
                    }
                }
                System.out.println();
            }
            System.out.println();
        }
    
        public void putQueen() {
            int row = 0, col;
            while (true) {
                for (col = p + 1; col < squares; col++) {
                    if (rows[row] == AVAILABLE && column[col] == AVAILABLE
                            && leftDiagonal[row + col] == AVAILABLE
                            && rightDiagonal[row - col + norm] == AVAILABLE) {
                        break;
                    }
                }
                // 在当前的行里面找到了可以放置皇后的位置
                if (col < squares) {
                    rows[row] = !AVAILABLE;
                    column[col] = !AVAILABLE;
                    leftDiagonal[row + col] = !AVAILABLE;
                    rightDiagonal[row - col + norm] = !AVAILABLE;
                    positionInRow[row] = col;
                    p = col;
                } else// 如果当前行没办反放置皇后了,那么回溯
                {
                    if (row > 0)// 到前一行
                    {
                        row--;
                        p = positionInRow[row];
                        rows[row] = AVAILABLE;
                        column[p] = AVAILABLE;
                        leftDiagonal[row + p] = AVAILABLE;
                        rightDiagonal[row - p + norm] = AVAILABLE;
                        positionInRow[row] = -1;
                        continue;
                    } else {
                        break;
                    }
                }
                if (row == squares - 1) {
                    howMany += 1;
                    printResults(positionInRow);
                    p = positionInRow[row];
                    rows[row] = AVAILABLE;
                    column[p] = AVAILABLE;
                    leftDiagonal[row + p] = AVAILABLE;
                    rightDiagonal[row - p + norm] = AVAILABLE;
                    positionInRow[row] = -1;
                    continue;
                } else {
                    row++;
                    p = -1;
                    continue;
                }
            }
        }
    
        public static void main(String args[]) {
            EightQueensNotRecursive eightQueens = new EightQueensNotRecursive();
            eightQueens.putQueen();
            System.out.println("皇后问题一共有" + howMany + "种解法");
        }
    
    }
    /**
     * 
     */
    package unit1;
    
    /**
     * @author
     * @version 创建时间:2015-10-30 下午02:41:57 类说明
     */
    public class EightQueensRecursive {
        private static final boolean AVAILABLE = true;
        private int squares = 8, norm = squares - 1;
        private int positionInRow[] = new int[squares];
        private boolean[] column = new boolean[squares];
        private boolean[] leftDiagonal = new boolean[2 * squares - 1];
        private boolean[] rightDiagonal = new boolean[2 * squares - 1];
        private static int howMany = 0;
    
        public EightQueensRecursive() {
            // To complete the initialization work for the
            // column,leftDiagonal,rigthDiagonal.
            for (int i = 0; i < squares; i++) {
                column[i] = AVAILABLE;
                positionInRow[i] = -1;
            }
            for (int i = 0; i < 2 * squares - 1; i++) {
                leftDiagonal[i] = AVAILABLE;
                rightDiagonal[i] = AVAILABLE;
            }
        }
    
        public void putQueen(int row) {
            // 如果前面已经得到了一个可行解
            for (int i = 0; i < squares; i++) {
                if (row > squares - 1)
                    break;
                if (column[i] == AVAILABLE && leftDiagonal[row + i] == AVAILABLE
                        && rightDiagonal[row - i + norm] == AVAILABLE) {
                    positionInRow[row] = i;
                    column[i] = !AVAILABLE;
                    leftDiagonal[row + i] = !AVAILABLE;
                    rightDiagonal[row - i + norm] = !AVAILABLE;
                    if (row < squares - 1) {
                        putQueen(row + 1);
                    } else {
                        howMany += 1;
                        printResults(positionInRow);
                    }
                    column[i] = AVAILABLE;
                    leftDiagonal[row + i] = AVAILABLE;
                    rightDiagonal[row - i + norm] = AVAILABLE;
                }
            }
        }
    
        public void printResults(int[] columns) {
            int row, col;
            System.out.println("八皇后问题的第 " + howMany + " 种解法");
            System.out.print("八皇后问题的结果为:");
            for (int e : columns) {
                System.out.print(e);
            }
            System.out.println("
    具体的图示如下图所示:");
            for (row = 0; row < squares; row++) {
                for (col = 0; col < squares; col++) {
                    if (col == positionInRow[row]) {
                        System.out.print("@");
                    } else {
                        System.out.print("*");
                    }
                }
                System.out.println();
            }
            System.out.println();
        }
    
        public static void main(String args[]) {
            EightQueensRecursive eightQueens = new EightQueensRecursive();
            eightQueens.putQueen(0);
            System.out.println("皇后问题一共找到了 " + howMany + "组解。");
        }
    }
  • 相关阅读:
    在Winform框架界面中改变并存储界面皮肤样式
    基于主从表数据录入的处理
    使用ew完成多场景下内网代理穿透
    内网渗透中的端口转发——工具很全
    内网渗透常见端口转发方式——lcx netsh rinetd warthworm regeorg msf portfwd sccat metasploit socks4a tunna
    内网渗透代理和转发
    内网渗透代理——reGeorg 利用 webshell 建立一个 socks 代理进行内网穿透,本质上就是在webshell上做了一个代理转发而已
    内网渗透代理——内网的防火墙只配置了入站规则比如只有80端口
    web未授权访问漏洞总结——mongodb、redis、memcache、jboss、vnc、docker、zk、rsync
    web未授权访问漏洞总结——非常全而细致 redis、mongodb、jenkins、zk、es、memcache、hadoop、couchdb、docker
  • 原文地址:https://www.cnblogs.com/abc8023/p/4924120.html
Copyright © 2020-2023  润新知