Java:如何实现N-Queens?

时间:2023-02-06 11:06:50

I'm studying N-Queens to implement it on my own, and came across the following implementation with the rules:

我正在研究N-Queens我自己实现它,并且通过规则遇到了以下实现:

The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other. Given an integer n, return all distinct solutions to the n-queens puzzle.

n-queens难题是将n个皇后放置在n×n棋盘上的问题,使得没有两个皇后互相攻击。给定整数n,返回n-queens拼图的所有不同解。

Each solution contains a distinct board configuration of the n-queens' placement, where 'Q' and '.' both indicate a queen and an empty space respectively.

每个解决方案都包含n-queens位置的独特电路板配置,其中“Q”和“。”两者分别表示女王和空白。

For example, There exist two distinct solutions to the 4-queens puzzle:

例如,4皇后拼图有两种截然不同的解决方案:

[
 [".Q..",  // Solution 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // Solution 2
  "Q...",
  "...Q",
  ".Q.."]
]

And implementation (the idea is to remember the busy columns and diagonals and recursively try to put the queen into the next row):

和实现(想法是记住繁忙的列和对角线,并递归尝试将女王放入下一行):

public class Solution {

    private void helper(int r, boolean[] cols, boolean[] d1, boolean[] d2, 
                        String[] board, List<String[]> res) {
        if (r == board.length) res.add(board.clone()); //HERE
        else {
            for (int c = 0; c < board.length; c++) {
                int id1 = r - c + board.length, id2 = 2*board.length - r - c - 1;//HERE
                if (!cols[c] && !d1[id1] && !d2[id2]) {
                    char[] row = new char[board.length];
                    Arrays.fill(row, '.'); row[c] = 'Q';
                    board[r] = new String(row);
                    cols[c] = true; d1[id1] = true; d2[id2] = true;
                    helper(r+1, cols, d1, d2, board, res);
                    cols[c] = false; d1[id1] = false; d2[id2] = false;
                }
            }
        }
    }

    public List<String[]> solveNQueens(int n) {
        List<String[]> res = new ArrayList<>();
        helper(0, new boolean[n], new boolean[2*n], new boolean[2*n], 
            new String[n], res);
        return res;
    }
}

And my question is (located where commented: //HERE), what's the reason for initializing and how are the following working the way they are: id1 = r - c + board.length, id2 = 2*board.length - r - c - 1; (what do r, id1, and id2 represent?), and what's the following meant for: if (r == board.length) res.add(board.clone());? Examples would really help.

我的问题是(位于注释:// HERE),初始化的原因是什么,以及如何按照它们的方式工作:id1 = r - c + board.length,id2 = 2 * board.length - r - c - 1; (r,id1和id2代表什么?),以下是什么意思:if(r == board.length)res.add(board.clone());?例子真的有帮助。

Thank you in advance and will accept answer/up vote.

提前谢谢你,并接受回答/投票。

EDIT

With input n as 4, would like to System.out.print the answer in the form of :

输入n为4时,想以System.out.print的形式给出答案:

[
 [".Q..",  // Solution 1
  "...Q",
  "Q...",
  "..Q."],

 ["..Q.",  // Solution 2
  "Q...",
  "...Q",
  ".Q.."]
]

How can I do so?

我怎么能这样做?

1 个解决方案

#1


2  

Keeping in mind

牢记

the idea is to remember the busy columns and diagonals and recursively try to put the queen into the next row

我的想法是记住繁忙的列和对角线,然后递归尝试将女王放入下一行

r is the current row, which starts at 0 (helper(0, ...)) and increments in each recursion (helper(r+1, ...)).

r是当前行,从0开始(helper(0,...))并在每次递归中递增(helper(r + 1,...))。

id1 and id2 is a number that identifies \ and / diagonals. For example, the fields on the main \ diagonal 0,0-1,1-2,2-...-7,7 all have the same id1 of 8.

id1和id2是一个标识\和/ diagonals的数字。例如,主\对角线0,0-1,1-2,2 -...- 7,7上的字段都具有相同的id1为8。

cols, d1 and d2 are tracking which columns and diagonals are threatened by the queens so far on the board. If you place a queen at 0,0, then cols[0] (0-th column), d1[8] (8-th \ diagonal) and d2[15] (15-th / diagonal) are all true.

cols,d1和d2正在追踪到目前为止在棋盘上哪些列和对角线受到皇后的威胁。如果你将一个女王放在0,​​0,那么cols [0](第0列),d1 [8](第8个\对角线)和d2 [15](第15个/对角线)都是真的。

This is a recursive function (calls itself). In order for a function to be both recursive and not useless, it always needs to have two different cases: a base case (also called the terminating case), and a general case (also called the recursive case). The first tells you when to stop; the second tells you how to keep going. The first tells you the simplest case; the second tells you how to break a complex case into a simpler one.

这是一个递归函数(调用自身)。为了使函数既递归又无用,它总是需要有两种不同的情况:基本情况(也称为终止情况)和一般情况(也称为递归情况)。第一个告诉你何时停止;第二个告诉你如何继续前进。第一个告诉你最简单的情况;第二个告诉你如何将复杂的案例分解为更简单的案例。

if (r == board.length) res.add(board.clone()); is the terminating case here. It says: "if we've reached the past the last row, this board as it stands now is a solution; add it to the list of results (instead of processing the next row, which wouldn't even exist)".

if(r == board.length)res.add(board.clone());这是终止案例。它说:“如果我们已经到了最后一行,那么现在这个板子就是一个解决方案;将它添加到结果列表中(而不是处理下一行,甚至不存在)”。

clone is used so that a snapshot of the current board is added instead of the reference to the current board itself (otherwise you'd end up with a bunch of references to the last board attempted).

使用clone,以便添加当前板的快照而不是对当前板本身的引用(否则您最终会得到一堆对最后一块板的引用)。

EDIT: To me the derivation of id1 and id2 is kind of intuitive, so I am not sure I can explain it. Just try to calculate it for different fields, and you'll see how they both give a number from 1 to 15 for board size 8. Here's what they look like (in JavaScript, so I can show it here; click the blue "Run code snippet" button):

编辑:对我来说,id1和id2的推导是直观的,所以我不确定我能解释它。只是尝试为不同的字段计算它,你会看到它们如何给出板大小为8的1到15的数字。这是它们的样子(在JavaScript中,所以我可以在这里显示它;点击蓝色“运行代码段“按钮”:

function drawTable(id, size, cb) {
  var $table = $('#' + id);
  var $tr = $('<tr>').appendTo($table);
  $('<th>').text(id).appendTo($tr);
  for (var c = 0; c < size; c++) {
    $('<th>').text(c).appendTo($tr);
  }
  for (var r = 0; r < size; r++) {
    var $tr = $('<tr>').appendTo($table);
    $('<th>').text(r).appendTo($tr);
    for (var c = 0; c < size; c++) {
      var n = cb(r, c, size);
      var $td = $('<td>').text(n).attr('data-d', n).appendTo($tr);
    }
  }
}

var size = 8
drawTable('id1', size, function(r, c, size) { return r - c + size; });
drawTable('id2', size, function(r, c, size) { return 2 * size - r - c - 1; });
th, td {
  text-align: center;
  border: 1px solid black;
  width: 2em;
}
table {
  border-collapse: collapse;
}
#id1 td[data-d="8"] {
  background-color: yellow;
}
#id2 td[data-d="15"] {
  background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="id1"></table>
<br>
<table id="id2"></table>

Yellow cells show you 8th id1 and 15th id2 - the diagonals for the field 0,0. You don't need to check the rows, because the program only ever puts one queen into each row, then goes on to the next one.

黄色单元格显示第8个id1和第15个id2 - 字段0,0的对角线。您不需要检查行,因为程序只会在每行中放入一个后跟,然后转到下一行。

#1


2  

Keeping in mind

牢记

the idea is to remember the busy columns and diagonals and recursively try to put the queen into the next row

我的想法是记住繁忙的列和对角线,然后递归尝试将女王放入下一行

r is the current row, which starts at 0 (helper(0, ...)) and increments in each recursion (helper(r+1, ...)).

r是当前行,从0开始(helper(0,...))并在每次递归中递增(helper(r + 1,...))。

id1 and id2 is a number that identifies \ and / diagonals. For example, the fields on the main \ diagonal 0,0-1,1-2,2-...-7,7 all have the same id1 of 8.

id1和id2是一个标识\和/ diagonals的数字。例如,主\对角线0,0-1,1-2,2 -...- 7,7上的字段都具有相同的id1为8。

cols, d1 and d2 are tracking which columns and diagonals are threatened by the queens so far on the board. If you place a queen at 0,0, then cols[0] (0-th column), d1[8] (8-th \ diagonal) and d2[15] (15-th / diagonal) are all true.

cols,d1和d2正在追踪到目前为止在棋盘上哪些列和对角线受到皇后的威胁。如果你将一个女王放在0,​​0,那么cols [0](第0列),d1 [8](第8个\对角线)和d2 [15](第15个/对角线)都是真的。

This is a recursive function (calls itself). In order for a function to be both recursive and not useless, it always needs to have two different cases: a base case (also called the terminating case), and a general case (also called the recursive case). The first tells you when to stop; the second tells you how to keep going. The first tells you the simplest case; the second tells you how to break a complex case into a simpler one.

这是一个递归函数(调用自身)。为了使函数既递归又无用,它总是需要有两种不同的情况:基本情况(也称为终止情况)和一般情况(也称为递归情况)。第一个告诉你何时停止;第二个告诉你如何继续前进。第一个告诉你最简单的情况;第二个告诉你如何将复杂的案例分解为更简单的案例。

if (r == board.length) res.add(board.clone()); is the terminating case here. It says: "if we've reached the past the last row, this board as it stands now is a solution; add it to the list of results (instead of processing the next row, which wouldn't even exist)".

if(r == board.length)res.add(board.clone());这是终止案例。它说:“如果我们已经到了最后一行,那么现在这个板子就是一个解决方案;将它添加到结果列表中(而不是处理下一行,甚至不存在)”。

clone is used so that a snapshot of the current board is added instead of the reference to the current board itself (otherwise you'd end up with a bunch of references to the last board attempted).

使用clone,以便添加当前板的快照而不是对当前板本身的引用(否则您最终会得到一堆对最后一块板的引用)。

EDIT: To me the derivation of id1 and id2 is kind of intuitive, so I am not sure I can explain it. Just try to calculate it for different fields, and you'll see how they both give a number from 1 to 15 for board size 8. Here's what they look like (in JavaScript, so I can show it here; click the blue "Run code snippet" button):

编辑:对我来说,id1和id2的推导是直观的,所以我不确定我能解释它。只是尝试为不同的字段计算它,你会看到它们如何给出板大小为8的1到15的数字。这是它们的样子(在JavaScript中,所以我可以在这里显示它;点击蓝色“运行代码段“按钮”:

function drawTable(id, size, cb) {
  var $table = $('#' + id);
  var $tr = $('<tr>').appendTo($table);
  $('<th>').text(id).appendTo($tr);
  for (var c = 0; c < size; c++) {
    $('<th>').text(c).appendTo($tr);
  }
  for (var r = 0; r < size; r++) {
    var $tr = $('<tr>').appendTo($table);
    $('<th>').text(r).appendTo($tr);
    for (var c = 0; c < size; c++) {
      var n = cb(r, c, size);
      var $td = $('<td>').text(n).attr('data-d', n).appendTo($tr);
    }
  }
}

var size = 8
drawTable('id1', size, function(r, c, size) { return r - c + size; });
drawTable('id2', size, function(r, c, size) { return 2 * size - r - c - 1; });
th, td {
  text-align: center;
  border: 1px solid black;
  width: 2em;
}
table {
  border-collapse: collapse;
}
#id1 td[data-d="8"] {
  background-color: yellow;
}
#id2 td[data-d="15"] {
  background-color: yellow;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<table id="id1"></table>
<br>
<table id="id2"></table>

Yellow cells show you 8th id1 and 15th id2 - the diagonals for the field 0,0. You don't need to check the rows, because the program only ever puts one queen into each row, then goes on to the next one.

黄色单元格显示第8个id1和第15个id2 - 字段0,0的对角线。您不需要检查行,因为程序只会在每行中放入一个后跟,然后转到下一行。