6
\$\begingroup\$

I have given an assignment where I have to escape a labyrinth. The goal is to find the shortest way. I have done some research and there seem to be two strategies to solve the problem: the Depth-first search and Breadth-first search, where the first starts at the root (Starting point in maze) and explores as far as possible along each branch before backtracking and the second begins at a root node and inspects all the neighboring nodes. Then for each of those neighbor nodes in turn, it inspects their neighbor nodes which were unvisited, and so on.

I have implemented both algorithms (non-recursive implementations) that will go on until they find the end:

/**
 * A non-recursive implementation of DFS
 * @param maze
 */
void solveUsingDepthFirst(IMaze maze) {
    Stack<IMazePosition> candidates = new Stack<IMazePosition>();
    //insert start position
    candidates.push(maze.getStartPosition());

    IMazePosition currentPosition;
    IMazePosition nextPosition;
    while (!candidates.empty()) {
        currentPosition = candidates.pop();

        if (maze.isMazeSolved(currentPosition)) break;

        //mark the current position
        maze.markPosition(currentPosition, maze.getPathMark());
      // maze.printMaze();
        // Check for possible ways to go
        nextPosition = currentPosition.north();
        if (maze.canMove(nextPosition)) candidates.push(nextPosition);
        nextPosition = currentPosition.east();
        if (maze.canMove(nextPosition)) candidates.push(nextPosition);
        nextPosition = currentPosition.south();
        if (maze.canMove(nextPosition)) candidates.push(nextPosition);
        nextPosition = currentPosition.west();
        if (maze.canMove(nextPosition)) candidates.push(nextPosition);
    }

    System.out.println(!candidates.empty() ? "Done" : "Sorry, could not do it");
    maze.printMaze();
}

/**
 * A non-recursive implementation of BFS
 * @param maze
 */
void solveUsingBreadthFirst(IMaze maze) {
    LinkedList<IMazePosition> candidates = new LinkedList<IMazePosition>();
    //insert start position
    candidates.add(maze.getStartPosition());

    IMazePosition currentPosition;
    IMazePosition nextPosition;
    while (!candidates.isEmpty()) {
        currentPosition = candidates.removeFirst();

        if (maze.isMazeSolved(currentPosition)) break;

        //markPosition the current position
        maze.markPosition(currentPosition, maze.getPathMark());
     //   maze.printMaze();

        // Check for possible ways to go
        nextPosition = currentPosition.north();
        if (maze.canMove(nextPosition)) candidates.add(nextPosition);
        nextPosition = currentPosition.east();
        if (maze.canMove(nextPosition)) candidates.add(nextPosition);
        nextPosition = currentPosition.south();
        if (maze.canMove(nextPosition)) candidates.add(nextPosition);
        nextPosition = currentPosition.west();
        if (maze.canMove(nextPosition)) candidates.add(nextPosition);
    }

    System.out.println(!candidates.isEmpty() ? "Done" : "Sorry, could not do it");
    maze.printMaze();
}

The map (or the maze) is represented as char[][]. The MazePosition is a representation of coordinates:

public MazePosition(int x, int y) {
    this.x = x;
    this.y = y;
}

Now with that code, I have all the possible paths from start to end that I was able to find until I found the end for the first time - is it fair to assume that the shortest path is amongst them? Given that I have the possible paths, how would I go with finding the shortest? And, is the code for path generation any good at all? Can I optimize any of the routines I already have.

Also, as far as I know, there are no "holes" in the maze, which means I can always stick to the wall.

\$\endgroup\$
  • 3
    \$\begingroup\$ Welcome to Code Review! You have a quite nice question here, but it would be even more amazing and awesome if you could provide a short and simple compilable example with an example maze! \$\endgroup\$ – Simon Forsberg May 20 '14 at 21:27
7
\$\begingroup\$

Reviewing Code:

Names

When naming classes in hierarchies, share the naming conventions used in the standard libraries. Therefore, your top level interface should be Maze, not IMaze. Likewise, IMazePosition.

Repetition

You are repeating yourself both small and large here.

Small: notice that you repeat yourself for each of the cardinal directions (north, south, east, west). That code is screaming for a loop:

for(MazePosition nextPosition : crntPosition.getNeighbors()) {
    if (maze.canMove(nextPosition)) candidates.add(nextPosition);
}

Large: notice that solveUsingDepthFirst and solveUsingBreadthFirst are the same except that they order the points differently. This suggests that you have overlooked an abstraction: a SearchOrder.

public interface SearchOrder {
    boolean isEmpty();
    MazePosition next();
    void add(MazePosition candidate);
}

public class DepthFirstSearchOrder implements SearchOrder {
   Stack<MazePosition> stack = new Stack<MazePosition> ();

   // implement the interface accessing this.stack 
}

public class BreadthFirstSearchOrder implements SearchOrder {
   // notice that candidates is a List<>, because the interface
   // really doesn't care about the implementation.
   List<MazePosition> candidates = new LinkedList<MazePosition> ();

   // implement the interface using this.candidates
}

void solveUsingBreadthFirst(Maze maze) {
    solve(maze, new BreadthFirstSearchOrder());
}

void solve(Maze maze, SearchOrder order) {
    // ...
}

Missing Requirements

It looks like you aren't keeping track of the moves you have made; your existing example seems to tell you if a solution exists, but it doesn't actually report what the solution is. This may or may not be a flaw - depending on the requirements.

Also, it's not fair to assume that your shortest solution in among them. Imagine a maze that is a big ring, with the exit right next to the start - your depth first search can go the wrong way around the ring, giving you the longest solution instead of the shortest. (The first result found in a breadth first search will be the shortest).

What you are missing is the ability to measure how far you have traveled when you reach the exit. So you need something to count the number of moves you have made, so that you can tell if the current path you are taking has improved on your best so far.

If you had such a thing, you could (by generalizing it further) abandon search branches when they are guaranteed to be worse than the solution you have already found....

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.