确定骑士是否可以穿过二维数组中的所有单元格 - 如果可以,打印板

Determining if a knight can pass through all cells in a 2d array - if so print board

我需要关于这个名为“Knight Path”的问题的建议。 给定一个 n*n 板,其单元格初始化为 0,我需要确定,给定任意骑士的位置,骑士是否可以恰好通过板上的每个单元格一次,骑士访问过的每个单元格都将被标记为计数器,从 1 - n^2 开始计数。 如果有可能,我需要打印电路板。我需要打印所有有效的电路板。 不知棋规者,马可上下一格,横二格,或纵二格,横一格。

例如给定一块 5*5 的板,从 (0,0) 开始,该方法应打印:

{{1,16,11,6,21},
{10,5,20,15,12},
{17,2,13,22,7},
{4,9,24,19,14},
{25,18,3,8,23}};

以上输出将是少数输出之一,因为考虑到不同的初始位置可能有不同的其他方法。 我写了下面的代码,但它没有打印任何东西。我需要找出这里的逻辑缺陷,这样我才能让它发挥作用。

public class KnightDemo {

    static int counter = 1;
    public static void KnightPath(int[][] b, int i, int j) {
        b[i][j] = counter;
        if (counter == b.length * b[0].length) {
            printMatrix(b);
            return;
        } else {
            counter++;

            if (isValid(b, i - 1, j + 2) && b[i - 1][j + 2] == 0) {
                KnightPath(b, i - 1, j + 2);
            } else {
                return;
            }
            if (isValid(b, i - 2, j + 1) && b[i - 1][j + 1] == 0) {
                KnightPath(b, i - 2, j + 1);
            } else {
                return;
            }
            if (isValid(b, i - 1, j - 2) && b[i - 1][j - 2] == 0) {
                KnightPath(b, i - 1, j - 2);
            } else {
                return;
            }
            if (isValid(b, i - 2, j - 1) && b[i - 2][j - 1] == 0) {
                KnightPath(b, i - 2, j - 1);
            } else {
                return;
            }
            if (isValid(b, i + 2, j - 1) && b[i + 2][j - 1] == 0) {
                KnightPath(b, i + 2, j - 1);
            } else {
                return;
            }
            if (isValid(b, i + 1, j - 2) && b[i + 1][j - 2] == 0) {
                KnightPath(b, i + 1, j - 2);
            } else {
                return;
            }
            if (isValid(b, i + 1, j + 2) && b[i + 1][j + 2] == 0) {
                KnightPath(b, i + 1, j + 2);
            } else {
                return;
            }
            if (isValid(b, i + 2, j + 1) && b[i + 2][j + 1] == 0) {
                KnightPath(b, i + 2, j + 1);
            } else {
                return;
            }

        }
    }

    public static boolean isValid(int[][] a, int i, int j) {
        if (i > a.length - 1 || i < 0 || j > a[0].length - 1 || j < 0) {
            return false;
        }
        return true;
    }

    public static void main(String[] args) {
        int[][] b = new int[5][5];
        for (int i = 0; i < b.length; i++) {
            for (int j = 0; j < b[0].length; j++) {
                KnightPath(b, i, j);
            }
        }
    }
    
    public static void printMatrix(int[][] matrix) {
        for (int[] rows: matrix) {
            StringBuilder buff = new StringBuilder();
            buff.append("[");
            for (int i = 0; i < rows.length; i++) {
                int value = rows[i];
                buff.append(value);
                if (i < rows.length - 1) {
                    buff.append(", ");
                }
            }
            buff.append("]");
            System.out.println(buff.toString());
        }
    }
}

输出为

[1, 2, 3, 4, 5]
[6, 7, 8, 9, 10]
[11, 12, 13, 14, 15]
[16, 17, 18, 19, 20]
[21, 22, 23, 24, 25]

根据 OP 在评论部分的解释,目标是绘制出骑士从棋盘上的某个位置可以采取的所有可能路径。基本上,给定骑士的位置b[i][j],计算所有合法路径。

如果骑士位于 {0, 0},则骑士只有两条合法路径以 {1, 2} 和​​ {2, 1} 结束。这里的想法是在地图中捕获它。然后,移动到棋盘上的下一个位置(即 {1, 0})并重复该过程。由于每个板位置都可以标识为一个整数(counter),我们可以用它来映射路径...

0=[{1, 2}, {2, 1}]
1=[{2, 2}, {1, 3}, {2, 0}]
...
n=[{n^-2 - 3, n^-2 - 2}, {n^-2 - 2, n^-2 - 3}] // last location is always a corner

为简单起见,我决定创建一个 Java record 坐标来存储结束位置的 {x, y} 坐标给定路径,使我的地图 <Integer, Set<Coordinates>>

这里的逻辑很简单。首先,为矩阵中每个对应位置的空列表播种地图。然后,遍历矩阵(二维数组)并计算骑士可以从该位置走的所有合法路径。对于每个合法路径,添加路径结束位置的 Coordinates。我使用 Set 来消除重复坐标。

我的解决方案(可能不是最优的)如下(使用 OP 代码作为基准)- 需要 Java 15 或更高版本到 运行。对于 Java 14 或更早版本,将 Coordinates 替换为长度为 2 的 Integer[],并将坐标存储在其中。

public class KnightDemo {

    static int counter = 0;
    static Map<Integer, Set<Coordinates>> map = new HashMap<>();

    public static void KnightPath(int[][] b, int i, int j) {
        Set<Coordinates> paths = map.get(counter);
        if (isValid(b, i - 1, j + 2)) {
            paths.add(new Coordinates(i - 1, j + 2));
            map.put(counter, paths);
        }
        if (isValid(b, i - 2, j + 1)) {
            paths.add(new Coordinates(i - 2, j + 1));
            map.put(counter, paths);
        }
        if (isValid(b, i - 1, j - 2)) {
            paths.add(new Coordinates(i - 1, j - 2));
            map.put(counter, paths);
        }
        if (isValid(b, i - 2, j - 1)) {
            paths.add(new Coordinates(i - 2, j - 1));
            map.put(counter, paths);
        }
        if (isValid(b, i + 2, j - 1)) {
            paths.add(new Coordinates(i + 2, j - 1));
            map.put(counter, paths);
        }
        if (isValid(b, i + 1, j - 2)) {
            paths.add(new Coordinates(i + 1, j - 2));
            map.put(counter, paths);
        }
        if (isValid(b, i + 1, j + 2)) {
            paths.add(new Coordinates(i + 1, j + 2));
            map.put(counter, paths);
        }
        if (isValid(b, i + 2, j + 1)) {
            paths.add(new Coordinates(i + 2, j + 1));
            map.put(counter, paths);
        }

        counter++;
    }

    public static boolean isValid(int[][] a, int i, int j) {
        return i >= 0 && i < a.length && j >= 0 && j < a[0].length;
    }

    public static void main(String[] args) {
        int[][] b = new int[5][5];
        for (int i = 0; i < b.length; i++) {
            for (int j = 0; j < b[0].length; j++) {
                map.put(counter, new HashSet<>()); // add a new set before calculating paths
                KnightPath(b, i, j);
            }
        }
        map.entrySet().stream().forEach(System.out::println);
    }

    private static record Coordinates(int row, int col) {

        @Override
        public String toString() {
            return "{" + row + ", " + col + "}";
        }
    }
}

程序输出:

0=[{1, 2}, {2, 1}]
1=[{2, 2}, {1, 3}, {2, 0}]
2=[{2, 3}, {1, 4}, {2, 1}, {1, 0}]
3=[{2, 2}, {1, 1}, {2, 4}]
4=[{2, 3}, {1, 2}]
5=[{2, 2}, {0, 2}, {3, 1}]
6=[{2, 3}, {0, 3}, {3, 0}, {3, 2}]
7=[{0, 0}, {3, 3}, {2, 4}, {0, 4}, {3, 1}, {2, 0}]
8=[{0, 1}, {3, 4}, {3, 2}, {2, 1}]
9=[{3, 3}, {2, 2}, {0, 2}]
10=[{1, 2}, {0, 1}, {4, 1}, {3, 2}]
11=[{0, 0}, {3, 3}, {1, 3}, {0, 2}, {4, 0}, {4, 2}]
12=[{0, 1}, {3, 4}, {1, 4}, {0, 3}, {4, 1}, {3, 0}, {1, 0}, {4, 3}]
13=[{1, 1}, {4, 4}, {0, 2}, {0, 4}, {4, 2}, {3, 1}]
14=[{1, 2}, {0, 3}, {4, 3}, {3, 2}]
15=[{2, 2}, {1, 1}, {4, 2}]
16=[{2, 3}, {1, 2}, {1, 0}, {4, 3}]
17=[{1, 1}, {4, 4}, {2, 4}, {1, 3}, {4, 0}, {2, 0}]
18=[{1, 2}, {1, 4}, {4, 1}, {2, 1}]
19=[{2, 2}, {1, 3}, {4, 2}]
20=[{3, 2}, {2, 1}]
21=[{3, 3}, {2, 2}, {2, 0}]
22=[{3, 4}, {2, 3}, {3, 0}, {2, 1}]
23=[{2, 2}, {2, 4}, {3, 1}]
24=[{2, 3}, {3, 2}]

更新:你能在真正的国际象棋游戏中使用它吗?

是的,你可以!假设您使用 blackwhite 作为矩阵的种子。您可以增强逻辑,这样,如果结束位置对应于您的颜色,您就不会添加为有效路径,因为它被您的一个棋子挡住了。

第二次更新:相同的代码,但使用 Coordinate 对象作为键

public class KnightDemo {

    static int counter = 0;
    static Map<Coordinates, Set<Coordinates>> map = new HashMap<>();

    public static void KnightPath(int[][] b, Coordinates coordinates) {

        Set<Coordinates> paths = map.get(coordinates);
        if (isValid(b, coordinates.row() - 1, coordinates.col() + 2)) {
            paths.add(new Coordinates(coordinates.row() - 1, coordinates.col() + 2));
            map.put(coordinates, paths);
        }
        if (isValid(b, coordinates.row() - 2, coordinates.col() + 1)) {
            paths.add(new Coordinates(coordinates.row() - 2, coordinates.col() + 1));
            map.put(coordinates, paths);
        }
        if (isValid(b, coordinates.row() - 1, coordinates.col() - 2)) {
            paths.add(new Coordinates(coordinates.row() - 1, coordinates.col() - 2));
            map.put(coordinates, paths);
        }
        if (isValid(b, coordinates.row() - 2, coordinates.col() - 1)) {
            paths.add(new Coordinates(coordinates.row() - 2, coordinates.col() - 1));
            map.put(coordinates, paths);
        }
        if (isValid(b, coordinates.row() + 2, coordinates.col() - 1)) {
            paths.add(new Coordinates(coordinates.row() + 2, coordinates.col() - 1));
            map.put(coordinates, paths);
        }
        if (isValid(b, coordinates.row() + 1, coordinates.col() - 2)) {
            paths.add(new Coordinates(coordinates.row() + 1, coordinates.col() - 2));
            map.put(coordinates, paths);
        }
        if (isValid(b, coordinates.row() + 1, coordinates.col() + 2)) {
            paths.add(new Coordinates(coordinates.row() + 1, coordinates.col() + 2));
            map.put(coordinates, paths);
        }
        if (isValid(b, coordinates.row() + 2, coordinates.col() + 1)) {
            paths.add(new Coordinates(coordinates.row() + 2, coordinates.col() + 1));
            map.put(coordinates, paths);
        }
    }

    public static boolean isValid(int[][] a, int i, int j) {
        return i >= 0 && i < a.length && j >= 0 && j < a[0].length;
    }

    public static void main(String[] args) {
        int[][] b = new int[5][5];
        for (int i = 0; i < b.length; i++) {
            for (int j = 0; j < b[0].length; j++) {
                Coordinates coordinates = new Coordinates(i, j);
                map.put(coordinates, new HashSet<>());
                KnightPath(b, coordinates);
                counter++;
            }
        }
        map.entrySet().stream().forEach(System.out::println);
    }

    private static record Coordinates(int row, int col) {

        @Override
        public String toString() {
            return "{" + row + ", " + col + "}";
        }
    }
}

输出:

{0, 0}=[{1, 2}, {2, 1}]
{2, 2}=[{0, 1}, {3, 4}, {1, 4}, {0, 3}, {4, 1}, {3, 0}, {1, 0}, {4, 3}]
{4, 4}=[{2, 3}, {3, 2}]
{0, 1}=[{2, 2}, {1, 3}, {2, 0}]
{2, 3}=[{1, 1}, {4, 4}, {0, 2}, {0, 4}, {4, 2}, {3, 1}]
{0, 2}=[{2, 3}, {1, 4}, {2, 1}, {1, 0}]
{2, 4}=[{1, 2}, {0, 3}, {4, 3}, {3, 2}]
{0, 3}=[{2, 2}, {1, 1}, {2, 4}]
{0, 4}=[{2, 3}, {1, 2}]
{3, 0}=[{2, 2}, {1, 1}, {4, 2}]
{3, 1}=[{2, 3}, {1, 2}, {1, 0}, {4, 3}]
{1, 0}=[{2, 2}, {0, 2}, {3, 1}]
{3, 2}=[{1, 1}, {4, 4}, {2, 4}, {1, 3}, {4, 0}, {2, 0}]
{1, 1}=[{2, 3}, {0, 3}, {3, 0}, {3, 2}]
{3, 3}=[{1, 2}, {1, 4}, {4, 1}, {2, 1}]
{1, 2}=[{0, 0}, {3, 3}, {2, 4}, {0, 4}, {3, 1}, {2, 0}]
{3, 4}=[{2, 2}, {1, 3}, {4, 2}]
{1, 3}=[{0, 1}, {3, 4}, {3, 2}, {2, 1}]
{1, 4}=[{3, 3}, {2, 2}, {0, 2}]
{4, 0}=[{3, 2}, {2, 1}]
{4, 1}=[{3, 3}, {2, 2}, {2, 0}]
{2, 0}=[{1, 2}, {0, 1}, {4, 1}, {3, 2}]
{4, 2}=[{3, 4}, {2, 3}, {3, 0}, {2, 1}]
{2, 1}=[{0, 0}, {3, 3}, {1, 3}, {0, 2}, {4, 0}, {4, 2}]
{4, 3}=[{2, 2}, {2, 4}, {3, 1}]

它们的打印顺序不同,但您可以看出坐标 {2, 2} 与前面示例中的 counter==12 是同一组。单元格 {2, 2} 是 top-left.

中的第 13 个单元格

要解决所有路径,您需要重置已用尽的路径的放置值,以便新路径可以访问它们。此外,你的计数器应该反映你走了多深,这样如果你退出尝试另一条路径,你的计数器也应该回滚。我建议将计数器作为参数传递,而不是使用静态计数器。此外,如果您想尝试所有有效的可能性,那么只要一种可能性被认为无效,就需要避免那些 return 语句。

public static void KnightPath(int[][] b, int i, int j, int counter) {
    ...
        if (isValid(b, i - 1, j + 2) && b[i - 1][j + 2] == 0) {
            KnightPath(b, i - 1, j + 2, counter+1);
        }
    ...
    b[i][j] = 0;
}

public static void main(String[] args) {
    ...
            KnightPath(b, i, j, 1);
    ...
}