回溯: N皇后问题

目录

回溯 (Back Tracking)

N皇后问题 (N Queens)

解决思路

四皇后 – 回溯法

四皇后 – 剪枝(Pruning)

八皇后 – 回溯法流程

N皇后问题 – 实现

N皇后问题 – 优化1

N皇后问题 – 优化2


回溯 (Back Tracking)

回溯可以理解为: 通过选择不同的岔路口来通往目的地(找到想要的结果)
每一步都选择一条路出发, 能进则进, 不能进则退回上一步(回溯), 换一条路再试
树、图的深度优先搜索(DFS)、八皇后、走迷宫都是典型的回溯应用

不难看出来,回溯很适合使用递 

N皇后问题 (N Queens)

把八皇后为例解析: 

在8x8格的国际象棋上摆放八个皇后,使其不能互相攻击:任意两个皇后都不能处于同一行、同一列、同一斜线上
请问有多少种摆法?
         

解决思路

四皇 回溯法

在解决八皇后问题之前, 可以先缩小数据规模, 看看如何解决四皇后问题

步骤: 

  1. 每一行只能摆放一个皇后, 在摆放Q1时, 可以先放到(0,0)位置
  2. 开始在第二行寻找可摆放Q2皇后的位置, Q2摆放在(2,1)位置 
  3. 开始在第三行寻找可摆放Q3皇后的位置
  4. 在第三行中没有可摆放Q3皇后的位置, 那么, 需要回溯到寻找Q2皇后位置的步骤, 重新将Q2摆放到(3,1)位置
  5. 在第三行寻找可摆放Q3皇后的位置,  Q3摆放在(1,2)位置
  6. 开始在第四行寻找可摆放Q4皇后的位置
  7. 在第四行没有可摆放Q4皇后的位置,需要回溯到寻找Q3皇后位置的步骤; 第三行没有其他可摆放Q3的位置, 那么回溯到寻找Q2皇后位置的步骤;第二行没有其他可摆放Q2的位置, 那么回溯到寻找Q1皇后位置的步骤 . . .
  8. . . . . . . 

四皇 剪枝(Pruning)

在前一行皇后位置确认后, 在下一行寻找摆放皇后位置时, 可以根据前一个皇后位置的特性(不能处于同一行, 同一列, 同一斜线), 跳过一些位置的摆放尝试

八皇 回溯法流程

N皇后问题  实现

/**
 * N皇后问题
 */
public class NQueens {

    public static void main(String[] args) {
        new NQueens().placeQueens(8);
    }

    int[] queens;  //存放已摆放皇后的位置
    int ways = 0;  //有多少种摆法
    
    public int placeQueens(int n){
        queens = new int[n];
        if(n <= 0) return 0;
        place(0); //从第 0 行开始摆放皇后
        System.out.println(n+"皇后共有种"+ways+"摆法");
        return ways;
    }

    public void place(int row){
        if(row == queens.length){ //最后一个皇后摆放完成
            ways++;
            return ;
        }
        for (int col = 0; col < queens.length; col ++) {
            if(isValid(row, col)){
                queens[row] = col; //第row行皇后摆放坐标
                place(row + 1); //开始摆放下一行
            }
        }
    }

    /**
     * 检查目标位置是否可以摆放皇后
     * @param row
     * @param col
     * @return
     */
    private boolean isValid(int row, int col) {
        for (int i = 0; i < row; i++) {  //检查所有已摆放的皇后位置
            if(queens[i] == col) return false; //目标位置处于已摆放皇后的同一列位置
            if(row - i == Math.abs(col - queens[i])) return false;
        }
        return true;
    }
}

N皇后问题  优化1

/**
 * N皇后问题
 */
public class NQueens {
    public static void main(String[] args) {
        new NQueens().placeQueens(4);
    }
    boolean[] cols; //标记某一列是否存在皇后

    boolean[] leftTop; //左上角->右下角 对角线是否存在皇后

    boolean[] rightTop; //右上角->左下角 对角线是否存在皇后

    int ways = 0; //总共存在多少中摆法

    public int placeQueens(int n){
        cols = new boolean[n];
        leftTop = new boolean[(n << 1) -1];
        rightTop = new boolean[leftTop.length];

        if(n <= 0) return 0;
        place(0); //从第 0 行开始摆放皇后
        System.out.println(n+"皇后共有种"+ways+"摆法");
        return ways;
    }

    public void place(int row){
        if(row == cols.length){  //最后一个皇后摆放完成
            ways ++;
            return ;
        }
        for (int col = 0; col < cols.length; col ++) {
            int ltIndex = row - col + cols.length -1;
            int rtIndex = row + col;

            if(cols[col]) continue;  //目标位置所处列已皇后
            if(leftTop[ltIndex] ) continue;  //目标位置所处斜线已存在皇后
            if(rightTop[rtIndex]) continue;  //目标位置所处斜线已存在皇后

            cols[col] = true;
            leftTop[ltIndex] = true;
            rightTop[rtIndex] = true;

            place(row + 1);

            //开始摆放下一行, 回溯
            cols[col] = false;
            leftTop[ltIndex] = false;
            rightTop[rtIndex] = false;
        }
    }
}

N皇后问题  优化2

/**
 * N皇后问题
 */
public class NQueens3 {
    public static void main(String[] args) {
        new NQueens3().placeQueens();
    }
    /**
     * 标记某一列是否存在皇后(0000 0111: 1表示已存在皇后, 0表示不存在皇后)
     * (1) 如何判断第 3列是否存在皇后
     *   1 < 3 ==> 0000 1000
     *   0000 0111 & 0000 1000 ==> 0000 0000 == 0
     *   0000 1111 & 0000 1000 ==> 0000 1000 != 0
     *  结果是否为 0; 如果为 0, 说明目标列不存在皇后; 如果不为 0, 说明目标列存在皇后
     *
     * (2) 如何将第 3 列标识为已存在皇后
     *  0000 0111 | 0000 1000 ==> 0000 1111
     *
     * (3) 如何将第 3 列标识为不存在皇后
     *  ~ 0000 1000 ==> 1111 0111
     *  0000 1111 & 1111 0111 ==> 0000 0111
     */
    byte cols;
    /**
     * 左上角->右下角 对角线是否存在皇后 (0110 0100 1110 0000: 1表示已存在皇后, 0表示不存在皇后, 只需要15位, 所以最高位为0)
     */
    short leftTop;
    /**
     * 右上角->左下角 对角线是否存在皇后 (0110 0100 1110 0000: 1表示已存在皇后, 0表示不存在皇后, 只需要15位, 所以最高位为0)
     */
    short rightTop;

    int ways = 0; //总共存在多少中摆法

    public int placeQueens(){
        place(0); //从第 0 行开始摆放皇后
        System.out.println("8皇后共有种"+ways+"摆法");
        return ways;
    }

    public void place(int row){
        if(row == 8){  //最后一个皇后摆放完成
            ways ++;
            return ;
        }
        for (int col = 0; col < 8; col ++) {
            int colIndex = 1 << col;
            int ltIndex = 1 << (row - col + 7);
            int rtIndex = 1 << (row + col);

            if((cols & colIndex) != 0) continue;
            if((leftTop & ltIndex) != 0) continue;
            if((rightTop & rtIndex) != 0) continue;

            cols |= colIndex;
            leftTop |= ltIndex;
            rightTop |= rtIndex;

            place(row + 1);

            //开始摆放下一行, 回溯
            cols &= ~colIndex;
            leftTop &= ~ltIndex;
            rightTop &= ~rtIndex;
        }
    }
}

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值