【问题标题】:Make Java recursive maze solver more efficient让 Java 递归迷宫求解器更高效
【发布时间】:2019-03-02 19:43:51
【问题描述】:

更新

我能够通过将线程大小增加到几 GB 来使我的算法正常工作,并且能够在一两秒内解决一个 1803x1803 的迷宫。

---------------

我昨天开始用 Java 自学递归。我创建了一个算法,可以拍摄迷宫照片并解决它。但是,当我做大于大约 200x200 像素的迷宫时,我得到了堆栈溢出的答案,因为我认为这个算法的堆栈太长了。我怎样才能改进这个算法,以便我可以输入可能高达 1000x1000 的图像?

另外,你能告诉我我目前使用的是哪种算法吗?我相信这要么是 DFS,但我不确定。

请解释为什么您的解决方案更有效以及它使用的想法。

这是解决的主要类

public class BlackWhiteSolver {

static int[][] solutionSet = new int[203][203];
static int width, height;
static String originalImage;
static int correctX, correctY;

public static void convert() {
try {
BufferedImage original = ImageIO.read(new File(originalImage));
int red;
int threshold = 2;
width = original.getWidth();
height = original.getHeight();

    for(int i=0; i<original.getWidth(); i++) {
        for(int j=0; j<original.getHeight(); j++) {
            red = new Color(original.getRGB(i, j)).getRed();
            // 1 = white, 0 = black, 9 = tried, 5 = solved
            if(red > threshold) { solutionSet[i][j] = 1; }
            else { solutionSet[i][j] = 0; }
        }
    }

} catch (IOException e) {e.printStackTrace();}
}

public BlackWhiteSolver(int solvedX, int solvedY, String pic) {
    correctX = solvedX;
    correctY = solvedY;
    originalImage = pic;
}

public boolean solve (int row, int column) {

        boolean completed = false;


        if (validPoint(row, column)) {
            solutionSet[row][column] = 9;

            if (row == correctX && column == correctY) {
                completed = true;
            } else {
                completed = solve (row+1, column);
                if (!completed) {
                    completed = solve (row, column+1);
                }
                if (!completed) {
                    completed = solve (row-1, column);
                }
                if (!completed) {
                    completed = solve (row, column-1);
                }
            }
            if (completed) {
                solutionSet[row][column] = 5;
            }
        }

        return completed;
    }

private boolean validPoint (int row, int column) {

        boolean isValid = false;
        if (row < height-1 && column < width-1 && row >= 1 && column >= 1 ) {
            if (solutionSet[row][column] == 1) {
            isValid = true;
            }
        }

        return isValid;
    }

public static void solvedFile() {
    BufferedImage binarized = new BufferedImage(width, height,BufferedImage.TYPE_3BYTE_BGR);
    int newPixel = 0;
    int rgb = new Color(255, 0, 0).getRGB();
    for(int i=0; i<width; i++){
        for(int j=0; j<height; j++)
    {
        if (solutionSet[i][j] == 0) {
            newPixel = 0;
            newPixel = colorToRGB(1, newPixel, newPixel, newPixel);
        } else if (solutionSet[i][j] == 1 || solutionSet[i][j] == 9) {
            newPixel = 255;
            newPixel = colorToRGB(1, newPixel, newPixel, newPixel);
        } else if (solutionSet[i][j] == 5) {
            newPixel = 16711680;
        }

        binarized.setRGB(i, j, newPixel);
    }
    }

    try { ImageIO.write(binarized, "gif",new File("maze-complete") );} catch (IOException e) {e.printStackTrace();}

}

    private static int colorToRGB(int alpha, int red, int green, int blue) {
        int newPixel = 0;
        newPixel += alpha;
        newPixel = newPixel << 8;
        newPixel += red; newPixel = newPixel << 8;
        newPixel += green; newPixel = newPixel << 8;
        newPixel += blue;
        return newPixel;
    }       
}

这是运行迷宫的类

public class BlackWhiteInterface
{

    public static void main (String[] args) {

        BlackWhiteSolver puzzle = new BlackWhiteSolver(60, 202, "maze-4.gif");

        System.out.println();

        puzzle.convert();

        if (puzzle.solve(0,34)) {
            System.out.println("completed");
            puzzle.solvedFile();
        } else {
            System.out.println("not possible");
        }
    }
}

用起点和终点生成正确的迷宫

public class MazeBuilder {

    static String start = "left";
    static String end = "down";

    public static void main(String[] args)
    {
        try
        {
            BufferedImage original = ImageIO.read(new File("mazeInput1.gif"));
            BufferedImage binarized = new BufferedImage(original.getWidth(), original.getHeight(),BufferedImage.TYPE_BYTE_BINARY);
            int red;
            int redRightPixel;
            int redUpPixel;
            int newPixel;
            int threshold = 2;

            for(int i=0; i<original.getWidth(); i++)
            {
                for(int j=0; j<original.getHeight(); j++)
                {

                    red = new Color(original.getRGB(i, j)).getRed();
                    int alpha = new Color(original.getRGB(i, j)).getAlpha();
                    if(red > threshold) { newPixel = 255; }
                    else { newPixel = 0; }

                    if (i == 0 || j == 0 || i == original.getWidth()-1 || j == original.getHeight() - 1){
                        newPixel = 0;

                        if (end == "left") {

                        } else if (end == "right") {

                        } else if (end == "up") {

                        } else if (end == "down") {

                        }


    /*if (i == 1 || j == 1 || i == original.getWidth()-2 || j == original.getHeight() - 2 && red > 2) {
        System.out.println("Start Point: (" + i + ", " + j + ")");
    }
    if (i == 0 && j > 0 && j < original.getHeight()-1) {


        redRightPixel = new Color(original.getRGB(i+1, j)).getRed();

        if (i == 0 && redRightPixel > 2) {
            System.out.println("Start Point: (" + i + ", " + j + ")");
            newPixel = 255;
        }
    }*/

    /*if (j == original.getHeight()-1 && i > 0 && i < original.getWidth()-1) {

        redUpPixel = new Color(original.getRGB(i, j-1)).getRed();

        if (redUpPixel > 2) {
            System.out.println("End Point: (" + i + ", " + j + ")");
            newPixel = 255;
        }
    }*/

                    }

                    if (start == "left") {
                        if (i == 1 && j != 0 && j != original.getHeight()-1 && red > 2) {
                            System.out.println("Start Point: (" + i + ", " + j + ")");
                        }
                    } else if (start == "right") {
                        if (i == original.getHeight()-2 && j != 0 && j != original.getHeight()-1 && red > threshold) {
                            System.out.println("Start Point: (" + i + ", " + j + ")");
                        }
                    } else if (start == "up") {
                        if (j == 1 && i != 0 && i != original.getWidth()-1 && red > threshold) {
                            System.out.println("Start Point: (" + i + ", " + j + ")");
                        }
                    } else if (start == "down") {
                        if (j == original.getHeight()-2 && i != 0 && i != original.getWidth()-1 && red > threshold) {
                            System.out.println("Start Point: (" + i + ", " + j + ")");
                        }
                    }

                    if (end == "left") {
                        if (i == 1 && j != 0 && j != original.getHeight()-1 && red > 2) {
                            System.out.println("End Point: (" + i + ", " + j + ")");
                        }
                    } else if (end == "right") {
                        if (i == original.getHeight()-2 && j != 0 && j != original.getHeight()-1 && red > threshold) {
                            System.out.println("End Point: (" + i + ", " + j + ")");
                        }
                    } else if (end == "up") {
                        if (j == 1 && i != 0 && i != original.getWidth()-1 && red > threshold) {
                            System.out.println("End Point: (" + i + ", " + j + ")");
                        }
                    } else if (end == "down") {
                        if (j == original.getHeight()-2 && i != 0 && i != original.getWidth()-1 && red > threshold) {
                            System.out.println("End Point: (" + i + ", " + j + ")");
                        }
                    }


                    newPixel = colorToRGB(alpha, newPixel, newPixel, newPixel);
                    binarized.setRGB(i, j, newPixel);
                }
            }
            ImageIO.write(binarized, "gif",new File("maze-4") );
        }
        catch (IOException e)
        {
            e.printStackTrace();
        }
    }
    private static int colorToRGB(int alpha, int red, int green, int blue) {
        int newPixel = 0;
        newPixel += alpha;
        newPixel = newPixel << 8;
        newPixel += red; newPixel = newPixel << 8;
        newPixel += green; newPixel = newPixel << 8;
        newPixel += blue;
        return newPixel;
    }

}

203 x 203 迷宫的示例输出

【问题讨论】:

  • 试试这个:stackoverflow.com/questions/2127217/…(不是真的重复,但有几个解决方案)
  • 你会不会碰巧有一些指向迷宫的示例链接?我会发布一些代码,但我想先验证它是否确实有效。基本上,您应该将递归 DFS 解决方案转换为显式堆栈。如果您想要最短路径解决方案,请使用 A*。
  • @Shadov 这很有趣,但是在环境中弄乱堆栈大小似乎有点忽略了要点;有一个简单的算法重构应该是这里的第一个资源。
  • @ggorlen 我使用在线生成器来生成我所有的迷宫。我将在此评论中链接它。如果您能找到解决方案,请告诉我! hereandabove.com/maze/mazeorig.form.html
  • 我插入了你所有的代码并使用在线生成器生成了一个图像,但是你的代码在第 203 行抛出了一个 AIOOBE。我使用了正确的尺寸吗?路径宽度:6,墙宽:2。

标签: java recursion depth-first-search maze


【解决方案1】:

一种效率稍高的简单方法是不使用递归将您到目前为止所遵循的路径存储在堆栈中。而是将您到目前为止所遵循的路径存储在java.util.BitSet 中(您将每个路径像素存储在BitSet 的元素y*width + x 中),或者您可以简单地使用您已着色的图片的红色区域用于存储路径。

这样可以避免堆栈溢出。

基本算法是从起点开始并沿四个主要方向之一前进,除非您已经访问过那个方向(尝试​​过并发现它是死胡同,或者从那个方向来到这里) .当你朝一个方向前进时,你会在那里做同样的事情。这是一个简单的非递归循环。

当您遇到死胡同时,您可以通过检查您所在位置的所有四个方向来了解您最初是如何到达那里的,看看路径来自哪里。你把红色从你站立的地方移开,然后回到你来的方向。如果任何方向都没有红色路径,那么你又回到了起点,你已经尝试了一切,所以迷宫没有解决方案。

当您原路返回时,您会在路径上的旧广场尝试您尚未尝试过的下一个方向,直到所有方向都死胡同。

如果你到达终点,你就完成了。


这里有一些伪代码通常不能处理循环(进入“圆圈”的路径),效率极低(例如,它应该使用BitSet 而不是boolean[][]),并且可能有一些错误,但它给出了总体思路:

public class MazeSolver {
    private static enum Direction { UP, RIGHT, DOWN, LEFT }

    // Return array's element is true if that's part of the path
    public static boolean[][] solve(final boolean[][] mazeWallHere,
                                    int x, int y,
                                    final int endX, final int endY) {
        final int width = mazeWallHere.length;
        final int height = mazeWallHere[0].length;

        final boolean[][] path = new boolean[width][height];

        Direction nextDirection = Direction.UP;
        boolean backtrack = false;
        while (true) {
            // If this spot is a dead end in all new directions, head back
            if (backtrack) {
                backtrack = false;

                // Unmark where we are
                path[x][y] = false;

                // Find where we came from and what direction we took to get here
                // Then switch to the next direction
                // If all directions have been tried, backtrack again
                // If we can't backtrack, return null because there's no solution
                // If we went up to get here, go back down and try going right.
                if (y != 0 && path[x][y - 1]) {
                    y--;
                    nextDirection = Direction.RIGHT;
                    continue;
                }
                // If we went right to get here, go back left and try going down.
                else if (x != 0 && path[x - 1][y]) {
                    x--;
                    nextDirection = Direction.DOWN;
                    continue;
                }
                // If we went down to get here, go back up and try going left.
                else if (y < height && path[x][y + 1]) {
                    y++;
                    nextDirection = Direction.LEFT;
                    continue;
                }
                // If we went left to get here, go back right and backtrack again.
                else if (x < width && path[x + 1][y]) {
                    x++;
                    backtrack = true;
                    continue;
                }
                // If we didn't come from anywhere, we're at the starting point
                // All possible paths are dead ends
                else return null;
            }

            // Mark where we are
            path[x][y] = true;

            // If we've solved it, return the solution
            if (x == endX && y == endY) return path;
            // Move unless we:
            //   * hit the edge of the maze
            //   * it's the direction we originally got here from
            //   * hit a wall
            // If we can't go a certain direction, try the next direction
            // If we're out of directions to try, backtrack
            switch (nextDirection) {
                case UP:    if (y == height
                                    || path[x][y + 1]
                                    || mazeWallHere[x][y + 1]) {
                                nextDirection = Direction.RIGHT;
                                continue;
                            }
                            else y++;
                            break;
                case RIGHT: if (x == width
                                    || path[x + 1][y]
                                    || mazeWallHere[x + 1][y]) {
                                nextDirection = Direction.DOWN;
                                continue;
                            }
                            else x++;
                            break;
                case DOWN:  if (y == 0
                                    || path[x][y - 1]
                                    || mazeWallHere[x][y - 1]) {
                                nextDirection = Direction.LEFT;
                                continue;
                            }
                            else y--;
                            break;
                case LEFT:  if (x == 0
                                    || path[x - 1][y]
                                    || mazeWallHere[x - 1][y]) {
                                backtrack = true;
                                continue;
                            }
                            else x--;
                            break;
            }
        }
    }
}

如果您想正确处理循环,请将 path 设为 int[][] 并存储移动编号而不是 true,以便您知道哪个路径更旧。

【讨论】:

  • 我明白你在说什么,但是在找到正确的解决方案之前,这段代码不会变成红色。基本上,它会解决直到找到正确的路径,然后回溯并将其填充为红色。我有点明白你在说什么,你认为你可以写一个例子吗?
  • 在完成之前,您实际上不必写入最终图像。您可以改为在BitSet上“绘图”。我可以写一些伪代码。
猜你喜欢
  • 2014-04-09
  • 1970-01-01
  • 2023-01-09
  • 1970-01-01
  • 1970-01-01
  • 2017-09-12
  • 1970-01-01
  • 1970-01-01
  • 2015-02-03
相关资源
最近更新 更多