简体   繁体   English

使Java递归迷宫求解器更高效

[英]Make Java recursive maze solver more efficient

Update 更新

I was able to get my algorithm working by increasing the thread size to a few gigabytes and was able to solve a 1803x1803 maze in a second or two. 我可以通过将线程大小增加到几GB来使我的算法正常工作,并且在一两秒钟之内就能解决1803x1803迷宫问题。

1803x1803

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

I started teaching myself recursion yesterday in Java. 我昨天开始用Java教自己的递归。 I created an algorithm that takes a photo of a maze and solves it. 我创建了一种算法,可以为迷宫拍照并解决。 However, I get a stack overflow answer when doing mazes that are larger than about 200x200 px because I think the stacks of this algorithm get too long. 但是,当做大于200x200 px的迷宫时,我会得到堆栈溢出的答案,因为我认为此算法的堆栈变得太长。 How can I better this algorithm so that I can input images up to possibly 1000x1000? 如何改善此算法,以便可以输入最大1000x1000的图像?

Additionally, can you tell me what kind of algorithm I am currently using? 另外,您能告诉我我目前使用哪种算法吗? I believe this is either DFS, but I am unsure. 我相信这是DFS,但我不确定。

Please explain why your solution is more efficient and the idea that it uses. 请解释为什么您的解决方案更有效,以及使用的想法。

This is the main class for solving 这是解决问题的主要方法

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;
    }       
}

This is the class that runs the maze 这是跑迷宫的课

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");
        }
    }
}

Generates correct maze with start and end point 生成带有起点和终点的正确迷宫

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;
    }

}

Example output of a 203 x 203 maze 203 x 203迷宫的示例输出

203x203迷宫

One simple way that's only slightly more efficient is to not store the path you've followed so far in the stack with recursion. 一种稍微有效的简单方法是不使用递归将到目前为止已遵循的路径存储在堆栈中。 Instead store the path you've followed so far in either a java.util.BitSet (where you store each path pixel in element y*width + x of the BitSet ) or you can simply use the red area of the picture that you've colored in to store the path. 取而代之的是将您到目前为止遵循的路径存储在java.util.BitSet (在其中将每个路径像素存储在BitSet y*width + x元素中),或者您可以简单地使用图片的红色区域上色以存储路径。

This avoids stack overflows. 这样可以避免堆栈溢出。

The basic algorithm is to start at the start point and go in one of the four cardinal directions unless you've already visited that direction (either trying it and finding it a dead end or having come from that direction to get here). 基本算法是从起点开始,并沿四个主要方向之一进行,除非您已经访问过该方向(尝试​​过并找到一个死胡同,或者从该方向来到这里)。 When you go in a direction, you do the same thing there. 当您朝某个方向行驶时,您在那做同样的事情。 It's a simple nonrecursive loop. 这是一个简单的非递归循环。

When you hit a dead end, you figure out how you got there originally by checking all four directions from where you are to see where the path came from. 当您走到尽头时,可以通过检查所有四个方向(从何处看)来弄清楚最初是如何到达那里的。 You remove the red from the spot where you're standing and go back in the direction you came from. 您将红色从站立的位置上移开,然后沿原来的方向返回。 If there is no red path in any direction, you're at the starting point again and you've tried everything, so there's no solution to the maze. 如果在任何方向上都没有红色路径,那么您将再次处于起点,并且您已尝试了一切,因此无法解决迷宫问题。

When you backtrack, you try the next direction you haven't tried yet at the older square on the path until all directions are dead ends. 当您回溯时,您可以在路径上的较旧的正方形上尝试尚未尝试的下一个方向,直到所有方向都死胡同为止。

If you ever reach the end point, you're done. 如果您到达终点,那就完成了。


Here's some pseudocode that can't generally handle cycles (paths that go in a "circle"), that's grossly inefficient (for example, it should use a BitSet instead of a boolean[][] ), and that probably has some bugs, but it gives the general idea: 这是一些通常无法处理循环的伪代码(路径在“圆圈”中),效率极低(例如,它应使用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;
            }
        }
    }
}

If you want to handle cycles properly, make path an int[][] and store the move number instead of true so that you know which path is older. 如果要正确处理循环,请将path设为int[][]并存储移动号而不是true,以便您知道哪个路径更旧。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM