【问题标题】:N-queens puzzle in Java with 1D arrayJava中的N皇后谜题与一维数组
【发布时间】:2016-02-12 20:19:55
【问题描述】:

我正在解决一个在初级程序员中似乎有点名气的问题,即 8 个皇后谜题。我已经看到使用 2D 数组、递归等方法解决这个问题的几种方法,但是这个问题是 CS 课程书中介绍 1D 数组的章节中给出的作业,因此解决这个问题的可用技术是有限的。

我使用的过程是首先创建一个大小为 64 的一维数组,这使得可以放置从索引 0 到 63 的皇后的位置。然后生成一个随机位置索引,并进行测试以检查如果有任何皇后攻击这个位置。如果这个位置没有被任何皇后攻击,则通过设置board[position] = true 放置一个皇后。当放置一个皇后时,queenCount 会递增,并重复此过程,直到放置了 8 个皇后。

如果放置皇后的方式无法放置 8 个,则棋盘会在 1 毫秒后通过执行时间检查重置,并重新尝试放置 8 个皇后。充其量我可以放置 7 个皇后,但最后剩下的一个永远不会放置。每次尝试都会被打印出来,并与queenCount 一起打印。是否可以使用这种方法,还是死路一条?

下面的代码示例:

package ch7;

public class Chapter_07_E22_EightQueens64bool {

    public static void main(String[] args) {

        int queenCount = 0;
        int attemptCount = 0;
        boolean[] board = new boolean[8 * 8];
        final long TIME_LIMIT = 1; //Milliseconds

        long startTime = System.currentTimeMillis();
        while (queenCount < 8) {

                int position = placeQueen(board.length);

                if(checkPosition(position, board) && !board[position]) {
                    board[position] = true;
                    queenCount++;
                }

                long timeCheck = System.currentTimeMillis();
                if (timeCheck - startTime > TIME_LIMIT) {
                    clearBoard(board);
                    queenCount = 0;
                    startTime = System.currentTimeMillis();
                }         
            System.out.println("Attempt #" + ++attemptCount);
            System.out.println(queenCount + " queens placed.");
            printBoard(board);
        }   
    }      

    public static void printBoard(boolean[] board) {

        for (int i = 0; i < board.length; i++) {

            if (board[i])
                System.out.print("|Q");
            else
                System.out.print("| ");

            if ((i + 1) % 8 == 0)
                System.out.println("|");    
        }
    }

    public static int placeQueen(int boardSize) {
        return (int)(Math.random() * boardSize);
    } 

    public static boolean[] clearBoard(boolean[] board) {

        for (int i = 0; i < board.length; i++)
            board[i] = false;

        return board;

    }

    public static boolean checkPosition(int position, boolean[] board) {

        return checkTop(position, board) && checkBottom(position, board) && checkLeft(position, board) &&
               checkRight(position, board) && checkTopLeft(position, board) && checkTopRight(position, board) &&
               checkBottomLeft(position, board) && checkBottomRight(position, board);
    }

    public static boolean checkTop(int position, boolean[] board) {
        // Checks each field above the current position while i >= 8  
        for (int i = position; i >= 8; i -= 8) {
            if (board[i - 8])
                    return false;  
        }
        return true;                
    }

    public static boolean checkBottom(int position, boolean[] board) {
        // Checks each field below the current position while i <= 55;
        for (int i = position; i <= 55; i += 8) {
            if (board[i + 8])
                    return false;
        }
        return true;                
    }

    public static boolean checkRight(int position, boolean[] board) {
        // Checks each field to the right of the current position while i % 8 < 7
        for (int i = position; i % 8 < 7; i += 1) {
            if (board[i + 1])
                return false;

        }
        return true;                
    }

    public static boolean checkLeft(int position, boolean[] board) {
        // Checks each field to the left of the current position while i % 8 != 0
        for (int i = position; i % 8 != 0; i -= 1) {
            if (board[i - 1])
                return false;  
        }
        return true;                
    }

    public static boolean checkTopLeft(int position, boolean[] board) {
        // Checks each field top left of the current position while i >= 9
        for (int i = position; i >= 9; i -= 9) {
            if (board[i - 9])
                return false;   
        }
        return true;                
    }

    public static boolean checkTopRight(int position, boolean[] board) {
        // Checks each field top right of the current position while i >= 7   
        for (int i = position; i >= 7; i -= 7) {
            if (board[i - 7])
                return false;   
        }
        return true;                
    }

    public static boolean checkBottomRight(int position, boolean[] board) {
        // Checks each field below the current position while i <= 54
        for (int i = position; i <= 54; i += 9) {
            if (board[i + 9])
                return false;    
        }
        return true;                
    }

    public static boolean checkBottomLeft(int position, boolean[] board) {
        // Checks each field below the current position while i <= 56
        for (int i = position; i <= 56; i += 7) {
            if (board[i + 7])
                return false;   
        }
        return true;                
    }

}

【问题讨论】:

  • “while 循环中断”是指无限循环吗?
  • 我想这就是我的意思,程序会尝试 n 次放置 8 个皇后,直到无法再放置。但是,我在输出中看到应该可以再放置 1 或 2 个。
  • 是否有检查以确保皇后不在同一个位置?
  • 我现在添加了一个,但现在它无限循环。似乎第一个解决方案将皇后放置在已经被占用的位置。编辑了代码,因此检查以确认皇后占据同一地点。

标签: java arrays n-queens


【解决方案1】:

首先,大小为 8 的数组就足够了。
数组 index 表示放置女王的 表示

[0, 2, 4, 6, 1, 3, 5, 7] 

表示第一列的皇后放在第一行,第二个皇后放在第三行,第三个皇后放在第五行,依此类推……

因此,当您放置一个新皇后时,请检查您添加它的行是否已经在数组中。这样,您只需要担心对角线碰撞。

解决问题的最简单方法是递归(回溯)。如果不允许这样做,您可以使用 stack 模拟递归。如果这也不允许,您可以使用 8 个 嵌套循环 - 很难看。


您可以使用一个简单的技巧来改进您的碰撞检查。它是这样工作的 -
假设您的女王 #0 在第 3 行。
她斜向攻击哪些细胞?
在第一列,它是第 2 行和第 4 行(-1+1
在第二列,它是第 1 行和第 5 行(-2+2
第三列是第 0 行和第 6 行(-3+3
所以当你添加一个新的皇后时,你迭代以前的皇后用(newIndex - oldIndex) + oldRow == newRow检查一个对角线,用(newIndex - oldIndex) - oldRow == newRow检查另一个对角线

所以,考虑到这一切,检查功能可能看起来像

boolean canAdd(List<Integer> p, int newRow) {
    if (p.contains(newRow))
        return false;
    int insertIndex = p.size();
    for (int i = 0; i < p.size(); i++) {
        if (p.get(i) + (insertIndex - i) == newRow || p.get(i) - (insertIndex - i) == newRow)
            return false;
    }
    return true;
}

虽然主要的递归函数看起来像

void solve(List<Integer> p, int index) {
    if (index == 8) {
        System.out.println(p);
        return;
    }

    for (int i = 0; i < 8; i++) {
        if (canAdd(p, i)) {
            p.add(i);
            solve(p, index + 1);
            p.remove(p.size() - 1);
        }
    }
}

你可以这样称呼它

solve(new ArrayList<Integer>(), 0);

【讨论】:

  • 在尝试改进我已经在处理的程序之后,我认为它不会起作用。我的解决方案最多可以放置 7 个皇后,但是当最后一个要放置时,没有空闲位置。我在博客上找到了一个解决方案,使用与您描述的方法类似的方法,并且该解决方案有效。我会试着绕过它。感谢您的意见!
  • @Esben86 没问题,如果您有更多问题,请随时提问。
  • 我创建了一个有 8 个槽的数组,其中索引 0 到 7 代表每一行,这些槽应该填充 0 到 7 的值,其中每个值代表我放置的皇后的列在创建数组时,所有插槽的值都为 0,这意味着我已经在第 0 列中排列了 8 个皇后。我认为在放置任何皇后之前板应该是干净的,但是由于数组中的每个插槽都有值默认为0,这是不可能的。目前,第一个皇后永远不能放在 [0] = 0、[1] = 0 等处,因为下面总是有一个皇后。
  • 嗯,我解决这个问题的方法有点不同 - 将第一女王放在任何地方。将第二个皇后放在某个没有被第一个皇后攻击的地方。将第三个皇后放在不会被所有先前放置的皇后攻击的地方。以此类推...这样,您可以确定在放置新皇后之前,船上没有皇后相互攻击,因此您只需检查当前放置的那个...
【解决方案2】:

在解决这个问题几天后,我现在有了一个解决方案,它可以在合理的时间内为 N

  1. For i &lt; N,初始化queens[i] = i。每行只能保存 1 个值,因此无需检查左侧或右侧的碰撞。只要数组中没有重复值,也不会有列冲突。

  2. 使用该方法检查给定点的皇后是否与另一个给定点的皇后共享对角线。该方法检查 x1 和 x0 之间的距离是否等于 y1 和 y0 的距离。如果距离相等,则坐标 (x0,y0) 和 (x1,y1) 共享同一对角线。

  3. 使用另一种方法调用shareDiagonal(int x0, int y0, int x1, int y1) 来检查给定行(例如第 7 行)的皇后是否与第 7 行上方任何行的皇后发生碰撞。如上所述,只有给定行上方的行行被检查。原因是,例如,如果您正在检查 row2 是否存在任何对角线冲突,则在检查具有更高索引值的行时,将显示与第 2 行下方的行的任何冲突。如果第 2 行的皇后与第 4 行的皇后发生碰撞,这将在检查第 4 行和上面的行时显示出来。

  4. 第三种检查方法调用checkRowForCollision(int[] queens, int row),其中遍历每一行,检查上面行的冲突。检查第 1 行是否与第 0 行的皇后发生冲突,第 2 行检查第 0 行和第 1 行是否有任何冲突,第 3 行检查第 0、1 和 2 行是否有任何冲突,依此类推。

  5. 当任何皇后之间存在对角线碰撞时,棋盘会洗牌,直到显示出没有皇后相互攻击的解决方案。

下面的代码示例:

package ch7;

public class Chapter_07_E22_EightQueens {

    static final int N = 8;

    public static void main(String[] args) {

        int[] queens = new int[N];
        int attempts = 0;

        for (int i = 0; i < N; i++) 
            queens[i] = i;

        while (checkBoardForCollision(queens)) {
            shuffleBoard(queens);
            attempts++;
        }
        printBoard(queens);
        System.out.println("Solution found in " + attempts + " attempts");
    }

    public static void printBoard(int[] queens) {

        for (int row = 0; row < N; row++) {
            System.out.printf("%-1c", '|');
            for (int column = 0; column < N; column++) {
                System.out.printf("%-1c|", (queens[row] == column) ? 'Q' : ' ');
            }
            System.out.println();
        }       
    }

    public static boolean shareDiagonal(int x0, int y0, int x1, int y1) {

        int dy = Math.abs(y1 - y0);
        int dx = Math.abs(x1 - x0);

        return dx == dy;
    }

    public static boolean checkRowForCollision(int[] queens, int row) {

        for (int i = 0; i < row; i++) {

            if (shareDiagonal(i, queens[i], row, queens[row]))
                return true;    
        }
        return false;
    }

    public static boolean checkBoardForCollision(int[] queens) {

        for (int row = 0; row < queens.length; row++)
            if (checkRowForCollision(queens, row))
                return true;

        return false;
    }

    public static int[] shuffleBoard(int[] queens) {

        for (int i = queens.length - 1;  i > 0; i--) {

            int j = (int)(Math.random() * (i + 1));

            int temp = queens[i];
            queens[i] = queens[j];
            queens[j] = temp;
        }
        return queens;
    }
}

【讨论】:

    【解决方案3】:

    其中一个问题可能存在于checkTop 方法中。

    public static boolean checkTop(int position, boolean[] board)
    {
        // Checks each field above the current position while i - 8 > - 1
        for (int i = position; i > (i - 8); i -= 8)
        {
            if ((i - 8) > -1)
            {
                if (board[i - 8])
                    return false;
            }
        }
        return true;
    }
    

    在某些情况下,该方法找不到槽 (board[i - 8] = true) 并且 i 达到 7 的值。此后,for loop (i &gt; (i - 8)) 的条件将始终为true,循环内最外层if (if ( (i - 8) &gt; -1) 的条件将始终为false。这会导致程序无限地停留在循环中。

    示例(i 达到 -5):

    i = -5;
    i > ( i - 8) :  -5 > (-5 -8 = -13) (always true) 
    (i - 8) > -1 :  -13 > -1           (false) always false for i <= 7
    

    【讨论】:

    • 感谢您指出这一点。我相信其他一些碰撞检查方法可能存在类似的问题。我会尝试在这些方面做更多的工作。
    猜你喜欢
    • 2013-02-02
    • 1970-01-01
    • 2021-02-02
    • 1970-01-01
    • 2020-04-05
    • 1970-01-01
    • 1970-01-01
    • 2017-12-23
    • 1970-01-01
    相关资源
    最近更新 更多