N皇后问题 - 回溯

简介:

按照国际象棋的规则,皇后可以攻击与之处在同一行或同一列或同一斜线上的棋子。

n皇后问题研究的是如何将n个皇后放置在 n x n 的棋盘上,并且使皇后彼此之间不能互相攻击。

例题:

分析:

当 n = 4时,求解4皇后问题,在4x4的方格中放置皇后,我们可以先在第一个格子放入一个皇后,与此同时,已经不能在其同一行、同一列或同一斜线上再放置皇后。红色格子代表有冲突,不能放置。可以在第二行第3个格子再放置一个皇后,如下图。

        

由上图可知,当放入第二个皇后时第三行已经不能再放置其他皇后,我们需要放4个,显然皇后数量不够。因此我们需要把第二个皇后撤销,放入下一个格子。

      

现在第三行能放第三个皇后了。

        但是此时所有方格全部冲突,也不行。

只能继续撤销找其他位置。依上述方式不断循环。

问题的关键是应该如何标记 当放入一个皇后之后 有冲突的格子。首先,皇后的同一行不需要记录冲突,因为一行只放一个皇后,不会连续放。

列冲突需要记录,可以用一个boolean类型的数组记录一列的冲突。有冲突的格子标记为true.

在 n x n的方格中,一共有 2n - 1 条左(右)斜线。

现在把方格的每一行定义为i , 每一列定义为 j。把当前方格的i 和 j相加(i + j)的结果放入本方格可以发现左斜线上的方格数字都相同,从0到6有七条斜线,因此可以用一个大小为7的boolean型数组来标记冲突区域,如果发现两个方格数字相同则表示冲突。

数字相同的单元格对应一条左(右)斜线。

  (i + j)             (i - j)

右图是i - j 的结果(右斜线),由于右图不太好对应索引,可以换成 n - 1 - (i - j) ,如下图:

 n - 1 - (i - j)

首先,我们先要定义一个二维表格来表示棋盘,用3个一维数组分别表示列冲突,左斜线冲突和右斜线冲突。ca[ ]     cb[ ]      cc[ ] 。当放入一个皇后时,在此方格中填入字符 'Q',表示皇后,' . ' 表示没有皇后。

我们需要处理每一行皇后的放置,如果皇后放置时遇到冲突,需要跳过本次循环,进入下一次循环。这次递归方法中都没有合适的位置来放置,需要撤销上一步的放置,回溯到原来的状态。

皇后放置成功后要标记冲突的区域,标记为true。

因为第一行的每一列也要尝试放置皇后,所以要有一个针对列的循环。

最后考虑递归结束条件:当处理到第n行,表示找到了有效解。

代码实现:
 
package backtracking;


import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class NQueenLeetcode51 {


    public static void main(String[] args) {
        int n = 4;

        boolean[] ca = new boolean[n]; //记录列冲突
        boolean[] cb = new boolean[2 * n - 1]; //左斜线冲突
        boolean[] cc = new boolean[2 * n - 1]; //右斜线冲突

        char[][] table = new char[n][n];

        for (char[] t : table) {
            Arrays.fill(t, '.');
        }

        dfs(0, n, table, ca, cb, cc);

    }

    static void dfs(int i, int n, char[][] table, boolean[] ca, boolean[] cb, boolean[] cc){

        if(i == n){ //当处理到第n行表示找到一个有效解
            System.out.println("===================");
            for (char[] t : table) {
                System.out.println(new String(t));
            }
            return;
        }

        for (int j = 0; j < n; j++) {
            if(ca[j] || cb[i + j] || cc[n - 1 - (i - j)]){
                continue;
            }
            table[i][j] = 'Q';
            ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = true; //记录冲突
            dfs(i + 1, n, table, ca, cb, cc);
            table[i][j] = '.';
            ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = false;
        }
    }

}

Leetcode51题代码:

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

public class NQueenLeetcode51 {

    public static List<List<String>> solveNQueens(int n) {
        List<List<String>> result = new ArrayList<>(n);

        boolean[] ca = new boolean[n]; //记录列冲突
        boolean[] cb = new boolean[2 * n - 1]; //左斜线冲突
        boolean[] cc = new boolean[2 * n - 1]; //右斜线冲突
        char[][] table = new char[n][n];
        for (char[] t : table) {
            Arrays.fill(t, '.');
        }
        dfs(0, n, table, result, ca, cb, cc);
        return result;
    }

    public static void main(String[] args) {
        int count = 0;
        for (List<String> table : solveNQueens(4)) {
            for (String row : table) {
                System.out.println(row);
            }
            count++;
            System.out.println("--------------------- " + count);
        }
    }

    static void dfs(int i, int n, char[][] table, List<List<String>> result, boolean[] ca, boolean[] cb, boolean[] cc){

        if(i == n){ //当处理到第n行表示找到一个有效解
            List<String> list = new ArrayList<>();
            for (char[] t : table) {
                list.add(String.valueOf(t));
            }
            result.add(list);
            return;
        }

        for (int j = 0; j < n; j++) {
            if(ca[j] || cb[i + j] || cc[n - 1 - (i - j)]){
                continue;
            }
            table[i][j] = 'Q';
            ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = true; //记录冲突
            dfs(i + 1, n, table, result, ca, cb, cc);
            table[i][j] = '.';
            ca[j] = cb[i + j] = cc[n - 1 - (i - j)] = false;
        }
    }

}

运行结果:

小结:

解这道题有两个要点:

①:需要准备4个数组来表示棋盘的状态。

②:以行为单位尝试放入皇后,每一行内对不同列的位置进行尝试,最后无论有没有找到解都要在递归结束之后,把棋盘恢复成递归之前的状态。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值