【问题标题】:N-Queens not printing all solutionsN-Queens 不打印所有解决方案
【发布时间】:2019-07-12 07:12:20
【问题描述】:

我是递归和回溯的新手。我正在尝试完成 N-Queen 问题以打印所有解决方案而不仅仅是 1 并理解这些概念。

我认为我已经部分正确地实现了算法,因为我得到了一些解决方案,但没有全部打印出来。我的代码是用 Java 编写的。

  • 对于 N = 4 的值,我得到 2 个解决方案 - 这是正确的
  • 对于 N = 5 的值,我得到 5 个解决方案 - 实际上有 10 个
  • 对于 N= 8 的值,我没有打印出来

我不知道自己犯了什么错误。我的想法是意识到第一个皇后必须在第一行,第二个在第二行等等。我必须弄清楚哪一列是合适的,显然要注意对角线来放置皇后。

感谢您为我指明正确方向的任何帮助。我的代码在下面,我尝试添加 cmets 以帮助理解。

public class nQueens {

    static class Queen {
        public Queen( int row, int column) {
            this.row = row;
            this.column = column;
        }
        int row = -1;
        int column = -1;
    }

    static ArrayList<Queen> queens = new ArrayList<Queen>();

    public static void main(String argv[]) {
        int n = 5;
        int[][] chessBoard = new int[n][n];
        int placed = 0;
        solve(n, chessBoard, placed);
    }

    public static void solve(int n, int[][] chessBoard, int placed) {
        // this means that all the queens have been placed
        if (placed == n) {
            System.out.println("**** Solution *****");
            for (int i = 0; i < n; i++) {
                for (int j = 0; j < n; j++) {
                    System.out.print(chessBoard[i][j] + " ");
                }
                System.out.println();
            }
        } else {
            // we know that each queen can be placed on each row
            int i = placed;
            // iterate through the columns
            for (int j = 0; j < n; j++) {
                if (chessBoard[i][j] != 1) {
                    if (isSafe(i, j)) {
                        chessBoard[i][j] = 1;
                        Queen queen = new Queen( i, j);
                        queens.add(queen);
                        placed = placed + 1;
                        // solve for the remaining number of queens
                        solve(n, chessBoard, placed);
                        // un-mark the spot
                        chessBoard[i][j] = 0;
                        // remove the placed queen
                        queens.remove(queens.size() - 1);
                        placed = placed - 1;
                    }
                }
            }
        }
    }

    public static boolean isSafe(int row, int column) {
        // this means that there are no queens on the board
        if (queens.isEmpty()) {
            return true;
        } else {
            for (int i = 0; i < queens.size(); i++) {
                // same column
                if (queens.get(i).column == column) {
                    return false;
                }
                // check diagonal
                int slope = Math.abs((queens.get(i).row - row) / (queens.get(i).column - column));
                if (slope == 1) {
                    return false;
                }
            }
        }
        return true;
    }
}

【问题讨论】:

  • 如果我正确理解你的代码,你只在棋盘上设置了一个皇后,即你标记一个字段。但是你在哪里标记新女王可以到达的领域,即哪些领域被其他女王阻止? IE。如果将第一个皇后设置为 (1,1),则必须将行 (1,x)、列 (x,1) 和对角线 (x,x) 中的所有字段标记为“阻塞”。然后递归不能将第二个皇后放在 (2, 1), (2, 2) 而是从 (2, 3) 开始。要解决您的问题,您需要克隆当前棋盘布局并将其传递给solve(n+1)
  • 但是你在哪里标记新女王可以到达的领域,即哪些领域被其他女王阻止? -- 所以我克服这个问题的方法是,如果我放置第一个皇后,那么我知道第二个皇后必须在第二排上,因为第一排被挡住了。
  • 当你输入solve()else 分支时,你(a)制作一个新的棋盘,(b)在每次循环迭代中你复制传入的内容 chessboard to that new chessboard, (c) 你调用一个函数,将新皇后放在新棋盘上(它还需要填充行、列和对角线!),(d) 你调用 solve() 并使用新的棋盘。 IE。每个递归级别都有自己的自己的棋盘可以处理,并且有一个传入的棋盘从调用者那里进行初始化(即,皇后已经被更高的递归级别放置在棋盘上)。
  • 好的,让我消化一下您在这里想说的话。再次感谢您。
  • 当我再次阅读你的代码时,我开始怀疑:你为什么要为你选择的算法保留一个棋盘? (a)您没有在棋盘中标记皇后阻止了哪些字段,因此您的条件 chessBoard[i][j] != 1 将始终返回 true 并调用 isSafe(),(b)isSafe() 仅使用皇后列表,(c)您的解决方案 print 使用棋盘,但可以只使用皇后列表。由于您的代码必须始终调用 isSafe() 来确定位置是否“空闲”,因此它的效率也低于将状态保持在棋盘中的解决方案。

标签: java algorithm recursion backtracking n-queens


【解决方案1】:

问题是:

int slope = Math.abs((queens.get(i).row - row) / (queens.get(i).column - column));
if (slope == 1) {
    return false;
}

您将slope 转换为整数。这意味着1.51.3 的斜率变为1 并导致您返回false,即使皇后实际上不在该对角线上。

改为在除法之前转换为浮点数(注意,java 的除法是整数除法,因此您需要先将除数或被除数转换为浮点数才能获得浮点输出)以允许浮点斜率:

float tmp = (queens.get(i).row - row);
float slope = Math.abs(tmp/ (queens.get(i).column - column));
if (slope == 1) {
    return false;
}

【讨论】:

  • 谢谢。我正忙着研究算法,而事实上它是如此微小但又如此重要。谢谢。我一直在研究这个问题,直到那个小小的改动完全解决了它!!!
【解决方案2】:

isSafe()class Queen 的替代解决方案

  • 让棋盘成为一个跟踪状态的类
  • 递归
    • 克隆当前板子状态
    • 设置女王并阻止她可以到达的所有字段
    • 将克隆向下传递到下一行
  • 记住每个皇后的每行列位置

以下是在placer 闭包中传递的通用求解器。通过使用这种方法,可以很容易地为车 (placeRook())、骑士 (placeKnight()) 或主教 (placeBishop()) 使用相同的求解器。

请注意,我的解决方案是用 Groovy 编写的,它也在 JVM 上运行,并且非常接近 Java。所以将算法的精华部分翻译成Java应该是没有问题的。

class ChessBoard {
    int N
    int lastIndex
    private boolean[][] board
    int solutions

    ChessBoard(int n) {
        board     = new boolean[n][n]
        N         = n
        lastIndex = n - 1
        solutions = 0
        this.each { int row, int column -> board[row][column] = true }
    }

    ChessBoard(ChessBoard orig) {
        N         = orig.getN()
        board     = new boolean[N][N]
        lastIndex = N - 1
        solutions = 0
        this.each { int row, int column -> board[row][column] = orig.getField(row, column) }
    }

    void each(Closure c) {
        (0..lastIndex).each { row ->
            (0..lastIndex).each { column -> c(row, column) }
        }
    }

    void print() {
        println " ${'-' * N}"
        (0..lastIndex).each { row ->
            print "|"
            (0..lastIndex).each { column -> print "${board[row][column] ? ' ' : 'X'}" }
            println "|"
        }
        println " ${'-' * N}"
    }

    int getN()                            { return N }
    int getSolutions()                    { return solutions }
    boolean getField(int row, int column) { return board[row][column] }

    void blockField(int row, int column)  {
        if ((row < 0) || (row > lastIndex))
            return
        if ((column < 0) || (column > lastIndex))
            return
        board[row][column] = false
    }

    List<Integer> getFree(int row)            {
        (0..lastIndex).findResults { int column -> board[row][column] ? column : null }
    }

    void placeQueen(int row, int column, boolean all = true) {
        if (all) {
            (0..lastIndex).each { offset ->
                blockField(row,    offset)                // row
                blockField(offset, column)                // column
                blockField(row + offset, column + offset) // diagonals
                blockField(row + offset, column - offset)
                blockField(row - offset, column + offset)
                blockField(row - offset, column - offset)
            }
        } else {
            blockField(row, column)
        }
    }

    // recursive solver
    void solve(ChessBoard previous, List<Integer> columns, int row, Closure placer) {
        List<Integer> free = previous.getFree(row)
        if (row < lastIndex) {
            // recurse
            free.each { column ->
                ChessBoard work = new ChessBoard(previous)
                columns[row] = column
                placer(work, row, column, true)
                solve(work, columns, row + 1, placer)
            }
        } else {
            // solutions
            free.each { column ->
                ChessBoard solution = new ChessBoard(N)
                columns[row] = column
                (0..lastIndex).each { placer(solution, it, columns[it], false) }
                println "Solution #${++solutions}:"
                solution.print()
            }
        }
    }

    // start recursion
    void solve(Closure placer) {
        List<Integer> columns = []
        solve(this, columns, 0, placer)
    }
}

board = new ChessBoard(8)
board.solve { ChessBoard work, int row, int column, boolean all -> work.placeQueen(row, column, all) }
println "Solutions: ${board.getSolutions()}"

试运行:

Solution #1:
 --------
|X       |
|    X   |
|       X|
|     X  |
|  X     |
|      X |
| X      |
|   X    |
 --------
...
Solution #92:
 --------
|       X|
|   X    |
|X       |
|  X     |
|     X  |
| X      |
|      X |
|    X   |
 --------
Solutions: 92

如果我没记错的话,对于 8-Queen 问题来说,92 听起来确实是正确的。但是自从我在学校使用 Pascal 中的迭代方法解决这个问题已经超过 35 年了 :-)


更新改进的解决方案

  • 将原始类拆分为 ChessBoard 用于跟踪状态,Solver 用于算法
  • 王后、白车、主教和骑士的放置器
  • 计算尺寸 1 到 8 的解决方案
  • 为结果生成降价表
class ChessBoard {
    private int         N
    private int         lastIndex
    private boolean[][] state

    ChessBoard(int n) {
        N         = n
        lastIndex = N - 1
        state     = new boolean[N][N]
        (0..lastIndex).each { row ->
            (0..lastIndex).each { column ->
                setField(row, column, true)
            }
        }
    }

    ChessBoard(ChessBoard orig) {
        N         = orig.getN()
        lastIndex = N - 1
        state     = new boolean[N][N]
        (0..lastIndex).each { row ->
            (0..lastIndex).each { column ->
                setField(row, column, orig.getField(row, column))
            }
        }
    }

    int getN() {
        return N
    }

    boolean getField(int row, int column) {
        return state[row][column]
    }

    void setField(int row, int column, boolean free = false)  {
        if ((row < 0) || (row > lastIndex))
            return
        if ((column < 0) || (column > lastIndex))
            return
        state[row][column] = free
    }

    List<Integer> getFree(int row) {
        (0..lastIndex)
            .findResults { int column ->
            getField(row, column) ? column : null
        }
    }

    // for debugging only
    void print() {
        println " ${'-' * N}"
        (0..lastIndex).each { row ->
            print "|"
            (0..lastIndex).each { column -> print "${getField(row, column) ? ' ' : 'X'}" }
            println "|"
        }
        println " ${'-' * N}"
    }
}

class Solver {
    private int   N
    private int   lastIndex
    private int   solutions
    private int[] columns

    Solver(int n) {
        N         = n
        lastIndex = N - 1
        solutions = 0
        columns   = new int[N]
    }

    void printSolution(String label) {
        solutions++
        if (!label)
            return
        println "${N}-${label} solution #${solutions}"
        println " ${'-' * N}"
        (0..lastIndex).each { row ->
            int column = columns[row]
            println "|${' ' * column}X${' ' * (lastIndex - column)}|"
        }
        println " ${'-' * N}"
    }

    int getSolutions() {
        return solutions
    }

    void placeQueen(ChessBoard board, int row, int column) {
            // only modify fields from (row+1) downwards
            (1..(lastIndex - row)).each { offset ->
                board.setField(row + offset, column)          // column
                board.setField(row + offset, column + offset) // diagonals
                board.setField(row + offset, column - offset)
            }
    }

    void placeRook(ChessBoard board, int row, int column) {
        // only modify fields from (row+1) downwards
        (1..(lastIndex - row)).each { offset ->
            board.setField(row + offset, column) // column
        }
    }

    void placeBishop(ChessBoard board, int row, int column) {
        // only modify fields from (row+1) downwards
        (1..(lastIndex - row)).each { offset ->
            board.setField(row + offset, column + offset) // diagonals
            board.setField(row + offset, column - offset)
        }
    }

    static void placeKnight(ChessBoard board, int row, int column) {
        // only modify fields from (row+1) downwards
        board.setField(row + 1, column - 2)
        board.setField(row + 1, column + 2)
        board.setField(row + 2, column - 1)
        board.setField(row + 2, column + 1)
    }

    // recursive solver
    void solve(ChessBoard previous, int row, Closure placer, String label) {
        List<Integer> free = previous.getFree(row)
        if (row < lastIndex) {
            // recurse
            free.each { column ->
                ChessBoard work = new ChessBoard(previous)
                columns[row] = column
                placer(this, work, row, column)
                solve(work, row + 1, placer, label)
            }
        } else {
            // solutions
            free.each { column ->
                columns[row] = column
                printSolution(label)
            }
        }
    }

    // start recursion
    int solve(Closure placer, String label = null) {
        solve(new ChessBoard(N), 0, placer, label)
        return solutions
    }
}

Map<String, Closure> placers = [
    'Queens':  { Solver solver, ChessBoard board, int row, int column -> solver.placeQueen(board,  row, column) },
    'Rooks':   { Solver solver, ChessBoard board, int row, int column -> solver.placeRook(board,   row, column) },
    'Bishops': { Solver solver, ChessBoard board, int row, int column -> solver.placeBishop(board, row, column) },
    'Knights': { Solver solver, ChessBoard board, int row, int column -> solver.placeKnight(board, row, column) },
]
Map<String, List<Integer>> solutions = [:]

// generate solutions up to maxN
int     maxN  = 8
boolean print = false
placers
    .keySet()
    .each { String key ->
        Closure placer = placers[key]
        List<Integer> results = []
        (1..maxN).each { N ->
            results.push(new Solver(N).solve(placer, print ? key : null))
        }
        solutions[key] = results
    }

// generate markdown table from solutions
List lines = []
    (0..maxN).each { lines[it] = [it ?: 'Size'] }
[
    'Queens',
    'Rooks',
    'Bishops',
    'Knights',
].each { key ->
    List<Integer> results = solutions[key]
    lines[0].push(key)
    (1..maxN).each { lines[it].push(results[it - 1]) }
}

lines.each { line -> println line.join('|') }
return

结果表:

| Size | Queens | Rooks | Bishops | Knights |
|------|--------|-------|---------|---------|
|   1  |      1 |     1 |       1 |       1 |
|   2  |      0 |     2 |       2 |       4 |
|   3  |      0 |     6 |       5 |       9 |
|   4  |      2 |    24 |      24 |      52 |
|   5  |     10 |   120 |     125 |     451 |
|   6  |      4 |   720 |     796 |    4898 |
|   7  |     40 |  5040 |    5635 |   67381 |
|   8  |     92 | 40320 |   48042 | 1131382 |
|------|--------|-------|---------|---------|

【讨论】:

  • 谢谢斯特凡。我花了一段时间来理解逻辑,但我明白你在说什么。谢谢!
  • 添加了一个改进的解决方案,也解决了其他棋子的问题。
猜你喜欢
  • 2013-03-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多