八皇后问题(递归回溯求解)

10 篇文章 0 订阅
10 篇文章 0 订阅

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

è¿éåå¾çæè¿°

本算法的思路是按行来规定皇后位置,第一行放置一个皇后,第二行放置一个皇后, 第N行也放置一个皇后… 这样, 可以保证每行都有一个皇后,那么各行的皇后应该放置在那一列呢, 算法通过循环来完成,在循环的过程中, 一旦找到一个合适的列,则该行的皇后位置确定,则继续进行下一行的皇后的位置的确定。由于每一行确定皇后位置的方式相似,所以可以使用递归法。一旦最后 一行的皇后位置确定,则可以得到一组解。找到一组解之后, 之前确定皇后应该放置在哪一列的循环其实才进行了一轮循环的, 算法通过该循环遍历所有的列,以此确定每一行所有可能的列的位置。在从一轮循环进入下一轮循环之前,算法需要清除在上一轮被标记为不可放置皇后的标记,也就是回溯。因为进入下一轮循环之后,同一行的皇后的列的位置会发生了变化,之前被标记为不可放置皇后的列和正反对角线位置都已经失效

代码实现

package com.gt.java.algorithm;

/**
 * 八皇后问题
 */
public class EightQueens {
    private int QUEEN_COUNT = 0;//皇后的默认数量
    private int[][] chessboard;//分配8*8的数组,充当棋盘,存放皇后
    private int recordCount = 0;//记录皇后的放置方法的总数
    private int[] queenPlace;//对于索引n,queenPlace[n]表示第n行的皇后位置是queenPlace[n]列

    public EightQueens(int n) {
        this.QUEEN_COUNT = n;
        this.recordCount = 0;
        this.chessboard = new int[QUEEN_COUNT][QUEEN_COUNT];
        this.queenPlace = new int[QUEEN_COUNT];
    }

    public void putQueen(){
        putQueen(0);
    }

    /**
     * 放置皇后
     * @param row 行
     */
    private void putQueen(int row) {
        //column表示列标,该层循环的作用是用于询问第row行,第column列是否可以放置皇后
        for (int column = 0;column<this.QUEEN_COUNT;column++){

            if(chessboard[row][column]==0){//表示第row行,第column列可以放置皇后
                //该层for循环的作用是使其正下方和斜下方的位置不可放置元素(将元素置1)
                for (int nextRow = row+1;nextRow<QUEEN_COUNT;nextRow++){
                    //将正下方的元素置1
                    chessboard[nextRow][column]++;
                    //将其正斜线位置的元素置1
                    int obliqueColumn = column-nextRow+row;
                    if (obliqueColumn>=0){
                        chessboard[nextRow][obliqueColumn]++;
                    }
                    //将其反斜线位置的元素置1
                    obliqueColumn = column+nextRow-row;
                    if (obliqueColumn<this.QUEEN_COUNT){
                        chessboard[nextRow][obliqueColumn]++;
                    }
                }
                //记录下第row行第column列放置了皇后
                queenPlace[row] = column;

                //如果各行都放置了皇后,也就说明皇后已放满,打印出皇后的布局
                if (row == this.QUEEN_COUNT-1){
                    printQueen(++this.recordCount);
                }else {// 否则递归继续排列下一行皇后
                    putQueen(row+1);
                }
                //回溯,使得回溯,使得在第row行的皇后不放在第column列,那放置在那一列?
                //答案是通过该算法的最外层循环,利用最外层for循环将皇后放在这一行的其他列
                for (int rows = row+1;rows<this.QUEEN_COUNT;rows++){
                    //既然第row行、第column列不放置皇后了,
                    // 则需要恢复正下方的不可用标记,将不同行的同一列的非零标记还原,也即恢复该位置的正下面的标记
                    //恢复正下方的标记
                    chessboard[rows][column]--;
                    //恢复其正斜线方向位置的元素
                    int obliqueColumn = column-rows+row;
                    if (obliqueColumn>=0){
                        chessboard[rows][obliqueColumn]--;
                    }
                    //恢复其反斜线方向位置的元素
                    obliqueColumn = column+rows-row;
                    if (obliqueColumn<this.QUEEN_COUNT){
                        chessboard[rows][obliqueColumn]--;
                    }

                }

            }
        }
    }

    /**
     * 打印皇后布局
     * @param size
     */
    private void printQueen(int size) {
        System.out.println(this.QUEEN_COUNT+"皇后的第"+size+"个解是:");
        System.out.println();
        for (int row = 0;row<this.QUEEN_COUNT;row++){
            for (int column = 0;column<this.QUEEN_COUNT;column++){
                System.out.print(queenPlace[row]==column?"1":"0");
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        EightQueens eightQueens = new EightQueens(8);
        eightQueens.putQueen();
    }
}

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值