4

I'm trying to solve a maze using recursion. It's declared Cell [][] maze.

public class Cell {
    private Wall left;
    private Wall right;
    private Wall up;
    private Wall down;
    private boolean end;

    // Setters and getters not shown
}

If there is no Wall for some side of the cell then it has value null, else it refers to a Wall object. Wall references are consistent: Both cells adjacent to single wall refer to it with the appropriate fields. If a wall is missing, then both adjacent cells have corresponding null entries. Here is the search:

public boolean solveMaze(Cell[][] maze, int i, int j) {

    if (maze[i][j].isEnd()){
        System.out.println(maze[i][j].toString());
        return true;
    }
    if (maze[i][j].getDown() == null) {
        return solveMaze(maze, i, j + 1); 
    }
    if (maze[i][j].getUp() == null) {
        return solveMaze(maze, i, j - 1) ;
    }
    if (maze[i][j].getLeft() == null) {
        return solveMaze(maze, i - 1, j);
    }
    if (maze[i][j].getRight() == null) {
        return solveMaze(maze, i + 1, j) ;  
    }
    return false;
}

I'm getting a Stack Overflow error. What is wrong with my recursion stop condition?

Update:

With your highly appreciated help I solved this problem: This is correct solution which works flawless:

public boolean solveMaze(Cell[][] maze, int i, int j){

    if (maze[i][j].isEnd()){
        System.out.println("Maze Exit :["+i+","+j+"]" );
        return true;
    }

    if (maze[i][j].isVisited()){
        return false;
    }

    maze[i][j].setVisited(true);

    if ((maze[i][j].getButtom() == null) ){
        if (solveMaze(maze,i,j+1)==true)
            return true;
    }

    if ((maze[i][j].getUp() == null) ){
    if ( solveMaze(maze,i,j-1) ==true )
        return true;
    }

    if ((maze[i][j].getLeft() == null)){
        if (solveMaze(maze,i-1,j))
            return true;
    }

    if ((maze[i][j].getRight() == null)){
        if (solveMaze(maze,i+1,j)) 
            return true;
    }

    maze[i][j].setVisited(false);
    return false;
} 

may be it will be helpful for any body in the future.

danny.lesnik
  • 18,479
  • 29
  • 135
  • 200
  • Should "getButton()" be "getBottom()"? – DGH Jul 13 '12 at 21:13
  • 1
    @millimoose it's a interview question apparently. Can those how answer it get offered the job? – MadProgrammer Jul 13 '12 at 21:14
  • 1
    @millimoose, I don't understand why are you calling it homework, I gave my solution which is not working I need help. I'm sorry, but your comment is not in the right place!!! – danny.lesnik Jul 13 '12 at 21:25
  • @user992484, this is not interview question I marked it as interview question, because such questions are often asked in interviews. Anyway I removed that tag. – danny.lesnik Jul 13 '12 at 21:27
  • ;) not probs. Nice question though, look forward to the answer – MadProgrammer Jul 13 '12 at 22:02
  • @danny.lesnik I didn't say it's one, I asked whether it is because it kind of looked like one. I'm perfectly willing to take your word on that. (For whatever that's worth to anybody.) – millimoose Jul 13 '12 at 22:38

5 Answers5

8

If the maze has a cycle, the solver can run around this cycle forever, which will cause the stack overflow you're seeing. You need a way of determining when you're seeing a maze square that's already been seen. In this case you should backtrack immediately.

This can be done either with a boolean flag visited in each cell initially set to false and then set true for each square you search, or you can maintain a separate Set of (i,j) pairs that have been searched, which is initially empty.

NB: Your use of i and j is unconventional. If someone else wrote the maze reading code with the conventional usage, this could be causing a problem. In math, i is usually used for the row number and j for the column. With this convention your wall tests do not agree with your increments and decrements. Missing the bottom wall would require you to increment i for example.

Gene
  • 46,253
  • 4
  • 58
  • 96
  • and what should I do if I get dead-end and I need to comeback to previous cell, but it's already marked as visited? – danny.lesnik Jul 13 '12 at 23:18
  • 1
    Hello @danny.lesnik. This algorithm will always find a path if one exists. It may not find the shortest path. In fact it's unlikely to do so. It's called depth first search. – Gene Jul 14 '12 at 01:25
6

It seems to me like you're running in circles in your solver method.
I suggest you familiarize yourself with Breadth-First Search, which is often used for not too big state-search problems.

If you have some "knowledge", a heuristic, on how to search the maze then you might also take a look at A-Star Search


What BFS could do in your case is the following: (BTW, be nice and use appropriate construtors, getters and setters)

public class Cell {
    public int x;
    public int y;
    public Cell parent;

    @Override
    public boolean equals(Object obj) {
        // TODO Override equals so it only incudes x and y coorinates, and not parent
        return true;
    }

    @Override
    public int hashCode() {
        // TODO Override hash code as well
        return 0;
    }
}

public Cell seachFor(Cell start, Cell finish) {
    Queue<Cell> open = new LinkedList<>();
    Set<Cell> closed = new HashSet<>();

    open.add(start);

    while (!open.isEmpty()) {
        Cell current = open.poll();
        if (current.equals(finish)) {
            return current;
        }

        closed.add(current);

        for (Cell neighbour : getNeighbours(current)) {
            if (!closed.contains(neighbour)) {
                open.add(neighbour);
            }
        }
    }

    return null;

}

private List<Cell> getNeighbours(Cell current) {
    /* TODO Set the neighbour's "parent"
     * Return valid adjacent neighbour cells
     */
    return null;
}

public Deque<Cell> pathfinder(Cell start) {
    Deque<Cell> path = new ArrayDeque<>();
    path.push(start);

    Cell current = start;

    while (current.parent != null) {
        current = current.parent;
        path.push(current);
    }

    return path;
}

public static void main(String[] args) {
    Cell start = maze.getStart();
    Cell finish = maze.getFinish();
    Deque<Cell> path = pathFinder(searchFor(start, finish))
    while (!path.isEmpty()) {
        Cell current = path.pop();
        maze.moveTo(current);
    }
}

Note that this is a mock code and you need to refine it before it works.

ioreskovic
  • 5,531
  • 5
  • 39
  • 70
  • Thanks I know this algorithm, but I would like to solve it with recursion. – danny.lesnik Jul 13 '12 at 21:57
  • @danny.lesnik A* or BFS can certainly be coded recursively, but BFS on graphs is easier to do correctly iteratively. You could also try depth-first with iterative deepening, which I think it's actually preferrable to implement recursively, will never recurse infinitely, and will find an optimal solution. – millimoose Jul 13 '12 at 22:55
  • 1
    Also if the maze is on an Euclidean plane, and you know where the goal cell is, you always have a heuristic for A*. (The Manhattan distance or the Cartesian distance should work.) – millimoose Jul 13 '12 at 22:57
1

You do not have anything in your code to detect places you've already been before. If your maze contains any passages in which you can go in a circle or loop back to someplace you've been before without backtracking, it will continue to recurse down that same path infinitely.

DGH
  • 11,189
  • 2
  • 23
  • 24
1

your running out of memory allocated on the stack. aka your recursion is multiply too fast where you exceed the resources of the JVM at the given time.

you can increase memory and then kill all the other iterations of this method, or change your algorithim. Changing the stacksize would be a poor solution but it may work even though your code if flawed howto increase the stacksize in eclipse I would suggest marking which areas you already visited

the maze is Cell[][] maze

  public class Cell{
    boolean visited = false;
    Wall left;
    Wall right;
    Wall up;
    Wall bottom;
    boolean end;

    //Settters and Getters
    }

set that visited variable to true each time you hit it and then change your code to

public boolean solveMaze(Cell[][] maze, int i, int j){
        if (maze[i][j].isEnd()){
            System.out.println(maze[i][j].toString());
            return true;
        }
        if (maze[i][j].getButton() == null)&&(maze[i][j].visited==false)){
            return solveMaze(maze,i,j+1); 
        }

        if (maze[i][j].getUp() == null){
        return solveMaze(maze,i,j-1) ;
        }
        // ect 

I wont implement it unless you have issues doing it yourself, with interview questions I think its neat if you solve them yourself with hints that way you get the feeling that you could solve something similar again, where if you just read and understand an answer you may be able to reiterate the answer to the same problem but you may not be able to come up with a novel answer to a similar problem using similar strategies.

Community
  • 1
  • 1
Frank Visaggio
  • 3,642
  • 9
  • 34
  • 71
  • and what should I do if I get dead-end and I need to comeback to previous cell, but it's already marked as visited? Do I need in that case to mark previous cell as unvisited? – danny.lesnik Jul 13 '12 at 22:59
  • I want to say no , I believe at each cell you see which way you can go and recursively check all directions . So the one that makes it will finish . So your starting at one point and going in each direction to find the way out . If this is a game where your only allowed to check one way at a time say for the ACSL then yes you would have to mark them as unvisited or have sort of a minesweeper system where you put how many unexplored paths are at each node and backtrack to one with some unexplored paths . – Frank Visaggio Jul 17 '12 at 05:16
1

First you go down until you reach a wall. Then, one up - then down again. And one up, and one down - until java detects that this is pretty silly and stops with a StackOverFlowError ;)

Andreas Dolk
  • 113,398
  • 19
  • 180
  • 268