在某平台上看到一个八皇后问题,很好奇就稍微了解了一下,然后也研究了一下他的实现算法。

题目

八皇后问题,一个古老而著名的问题,是回溯算法的典型案例。该问题由国际西洋棋棋手马克斯·贝瑟尔于 1848 年提出:在 8×8 格的国际象棋上摆放八个皇后,使其不能互相攻击,即任意两个皇后都不能处于同一行、同一列或同一斜线上,问有多少种摆法。

方法:回溯法

回溯法(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。

解题思路

由题干我们可以知道每一行最多只能有一个皇后,所以我们可以定义出这样的一个规则,现在第一行第一个位置放置一个皇后,然后遍历行,在行中进行列遍历,找到满足条件的,以此类推,假如在第四行就已经没有路可走那么回到第三行,重新进行选择一个位置再进行遍历。

具体思路参见:https://juejin.im/post/5accdb236fb9a028bb195562

解题步骤

1.确定不允许落子规则

  1. 同一列不能有皇后;
  2. 已经落子的行的左上角和右上角不能有皇后;

首先定义一个二维数组chessboard[row][col]第一个维度是行第二个维度是列,注意不要带入坐标轴哦,行列表示跟坐标轴是相反的!

八皇后落子规则示意图
八皇后落子规则示意图

如图,假设蓝色三角就是第二行皇后放置的位置,我们需要计算在他左右斜上方以及当前列上方是否有皇后,代码如下:

public boolean isSatisfy(int row, int col) {
        //检查列是否有皇后
        for (int i = 0; i < row; i++) {
            if (chessboard[i][col] == 1) {
                return false;
            }

        }

        //检查左上对角线是否有皇后
        for (int m = row - 1, n = col - 1; m >= 0 && n >= 0; m--, n--) {
            if (chessboard[m][n] == 1) {
                return false;
            }
        }

        //检查右上对角线是否有皇后
        for (int m = row - 1, n = col + 1; m >= 0 && n < MAX; m--, n++) {
            if (chessboard[m][n] == 1) {
                return false;
            }
        }

        return true;

    }

2.回溯递归

解决了判别问题我们就要进行整个算法的核心,回溯递归:

public void putQueen(int row) {
        if (row >= MAX) {
            printAnswer();
            return;
        }
        for (int col = 0; col < MAX; col++) {
            if (isSatisfy(row, col)) {
                chessboard[row][col] = 1;
                putQueen(row + 1);
                chessboard[row][col] = 0;
            }
        }

    }

上面的代码也就几行看起来很简单,但是理解起来可能就不一定那么简单了!

先知道递归什么时候结束,八行八列都被遍历则循环结束。

首先从第0行第0列开始我们的第一次遍历,然后开始从第二行找到合适的位置,如果很巧找到了,进入第三行的列遍历,假设一切顺利的话我们输出了第一种解法,然后开始遍历第0行第1列,如果还是那么恰好以此类推输出结果。

但是往往天不遂人愿,走着走着路就没了,那么就需要回到上一层。

例子:假设row = 6 的时候,假如chessboard [6][0]放置了一个皇后(row=6,col=0);

那么接下来执行putQueen(6+1)也就是第七行的遍历;

假如遍历第七行所有列都没法满足条件以进入第八行遍历,那么会执行chessboard[row][col] = 0; (row=6,col=0),将无法进入下一行的皇后位置清除;

此时col自增为1,然后执行isSatisfy(row,col) (row=6,col=1)如果能放置的话,重复上边的动作,如果不能放置的话继续自增col,放置到第6行的下一列;

如果第六行每一列都没法得到解决方案,那么说明第五行的某一列(假设是0列)是死路,进行第五行第二列的尝试遍历。

如此层层递归,最终得到了所有的解决方案!

完整代码


/**
 * @author chenly
 * @create 2020-05-10 19:56
 */
public class Queen {
   public static final int MAX = 8;
   private int[][] chessboard = new int[8][8];
   //解法的数量
   private int count = 0;

   public boolean isSatisfy(int row, int col) {
      //检查列是否有皇后
      for (int i = 0; i < row; i++) {
         if (chessboard[i][col] == 1) {
            return false;
         }

      }

      //检查左上对角线是否有皇后
      for (int m = row - 1, n = col - 1; m >= 0 && n >= 0; m--, n--) {
         if (chessboard[m][n] == 1) {
            return false;
         }
      }

      //检查右上对角线是否有皇后
      for (int m = row - 1, n = col + 1; m >= 0 && n < MAX; m--, n++) {
         if (chessboard[m][n] == 1) {
            return false;
         }
      }

      return true;

   }

   public void putQueen(int row) {
      if (row >= MAX) {
         printAnswer();
         return;
      }
      for (int col = 0; col < MAX; col++) {
         if (isSatisfy(row, col)) {
            chessboard[row][col] = 1;
            putQueen(row + 1);
            chessboard[row][col] = 0;
         }
      }

   }

   private void printAnswer() {
      count++;
      System.out.println("第 " + count + " 种解:");
      for (int i = 0; i < MAX; i++) {
         for (int j = 0; j < MAX; j++) {
            System.out.print(chessboard[i][j] + " ");
         }
         System.out.println();
      }
   }

}
public class Main {

    public static void main(String[] args) {
        Queen queen=new Queen();

        queen.putQueen(0);

    }


}

参考:

https://juejin.im/post/5c8f139fe51d4513b907261d

https://juejin.im/post/5accdb236fb9a028bb195562