3

Suppose I have the following maze: (not formatted properly)

#########################################
S... #... # # #... #
###.# ##### #.#.### # ### # ###.#.# #####
#...# #...# #.#...# # # #.#.# #...#
#.#####.#.###.###.##### ##### #.#.###.#.#
#.....#.#..... #...# #.#.....#.#
# ###.#.####### ###.###########.#######.#
# #.#.# # #...#......... # #.#
### #.#.# ### #######.#.########### # #.#
# # #.#.# # # # #...# # # .#
# # #.#.# # ### # # ##### ### # #######.#
# #...# # # # #                        .E
#########################################

S represents start of the maze and E represents end of the maze. I have two given classes; Maze and Cell . I have to build the following recursive helper method to find the solution of the maze:

  -findPath(currentMaze:Maze, current:Cell, path:ArrayList<Cell>):ArrayList<Cell

This method recursively finds a path from the start of the currentMaze to its end that goes through the current Cell. The path is an ArrayList of the sequence of cells that was followed to get from the start of the maze to the current cell (i.e., the path explored so far). In order to avoid paths that are longer than needed, the algorithm should avoid revisiting cells already in this path. The algorithm should return null if there is no path from current to the end that only goes through each Cell at most once. Otherwise, it should return the complete path from the start of the maze to the end as a sequence of Cells in an ArrayList. You must implement this as a recursive algorithm. In order to explore all paths through neighbors that have not yet been visited, you will want to make use of Maze’s getNeighbors.

To build this recursive method, I'm given the following methods:

+getStartCell():Cell Returns the start Cell of the maze
+getEndCell():Cell Returns the end Cell of the maze


 +getNeighbors(currentCell:Cell):
ArrayList<Cell>
Returns a list of all the cells that are connected to
currentCell. If there is a wall between
currentCell and its neighbor, it is not added to this
collection. 

So far this is what I did:

 private static ArrayList <Cell> findPath(Maze currentMaze,Cell current,ArrayList <Cell> path){
   // Base Case
   if (current == currentMaze.getEndCell()) {
    return path;
   }
 if(currentMaze.getNeighbors(current).size()!=0)
currentMaze.getStartCell();
currentMaze.getNeighbors(current);
currentMaze.getEndCell();
}
}

I'm really struggling to build this method.

Elijah Dayan
  • 442
  • 6
  • 19
  • 2
    May I ask why you don't just do the an iterative depth/breath first search? It's a lot easier to implement and faster than the recursive solution (granted it is not tail recursive in which case it would be about equivalent). – Bennett Yeo Apr 20 '16 at 00:17
  • doing it through recursive DFS is in the requirement. – Elijah Dayan Apr 20 '16 at 00:18
  • In addition, I asked a similar question [here](http://stackoverflow.com/questions/30472908/grid-exploring-algorithm-with-move-constraints). It's in C#, but being another OOP language, it's really not that different. – Bennett Yeo Apr 20 '16 at 00:21
  • Yes, unfortunately I'm asked to do it using recursion. – Elijah Dayan Apr 20 '16 at 00:22
  • 2
    @Afif check this out: https://courses.cs.washington.edu/courses/cse326/03su/homework/hw3/dfs.html – DarkHorse Apr 20 '16 at 00:28
  • What's the difference between the dot and empty space ? – 11thdimension Apr 20 '16 at 05:20

2 Answers2

1

Ok here it is. You don't only need a DFS but also a way to store the found path.

The method signature that you have suggested for findPath will not work. path argument to it is a list and it will store all the nodes as it traverses, as even if it's a recursive algorithm we are not copying the list entirely before passing it to the next findPath invocation and frankly we shouldn't do that improve performance and reduce the memory consumption.

Easiest way that I can think of doing it is in to have every cell point to it's parent. A parent cell is the cell to which a cell was discovered as a neighbour.

We have to use the following signature for the findPath

List<Cell> findPath(Maze currentMaze, Cell current)

We need to return all the recursions when we have reached the End node so that state has to be stored outside findPath.

Rest is simple, we can use following algorithm (It's pseudo code)

path = null
findPath(maze, startCell)
printPath(maze, path)

findPath(currentMaze, current)
  if curent = endCell
    list = []
    while(current != null)
        list.add(0, current)
        current = current.parent
    path = list
  else if path != null
    current.visitStatus = IN_PROGRESS
    neighbours = getUnVisitedNeighbours(current)
    for each neibhbour in neighbours
        neighbour.parent = current
    findPath(currentMaze, neighbour)

    current.visitStatus = VISITED

printPath(currentMaze, path)
    for each cell in path
        cell.ch = 'O' //since path references are same as maze it will update maze as well

    print maze

Note: This algorithm does not produce shortest path, it returns whenever it can find any path.

Here's an actual Java implementation. It reads the maze from a text file.

Following is the Github link with the sample maze text files.

https://github.com/ConsciousObserver/stackoverflow/tree/master/TestMaze

package com.example;

import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.stream.Stream;

import com.example.TestMaze.Cell.VisitStatus;

public class TestMaze {
    static List<Cell> resultPath = null;

    public static void main(String[] args) {
        String filePath = "maze2.txt";
        Maze currentMaze = new Maze(filePath);

        findPath(currentMaze, currentMaze.startCell);

        if(resultPath == null) {
            System.out.println("\nNo path exists for the Maze");
        } else {
            System.out.println("\nPath size : " + resultPath.size());
            printPathOnMaze(currentMaze, resultPath);
        }
    }

    private static void printPathOnMaze(Maze maze, List<Cell> path) {
        path.stream()
            .filter(cell-> !maze.isStartCell(cell) && !maze.isEndCell(cell))
            .forEach(cell-> cell.setCh('O'));

        maze.printCells();
    }

    private static List<Cell> findPath(Maze currentMaze, Cell current) {
        if(currentMaze.isEndCell(current)) {
            resultPath = new ArrayList<>();
            Cell traversalCell = current;

            while(traversalCell != null) {
                resultPath.add(0, traversalCell);
                traversalCell = traversalCell.getParentCell();
            }
            return resultPath;
        }

        if(resultPath == null) {

            if(Maze.isWall(current)) {
                current.setVisitStatus(VisitStatus.VISITED);
            } else {
                current.setVisitStatus(VisitStatus.IN_PROGRESS);
                List<Cell> neighbourList = currentMaze.getNeighbours(current);

                neighbourList.stream()
                    .filter(cell -> cell.getVisitStatus() == VisitStatus.UNVISITED)
                    .filter(cell -> cell.getVisitStatus() == VisitStatus.UNVISITED)
                    .forEach(neighbour -> {
                        neighbour.setParentCell(current);
                        findPath(currentMaze, neighbour);   
                    });

                current.setVisitStatus(VisitStatus.VISITED);
            }
        }

        return null;
    }

    public static boolean isCellInPath(Cell cell, List<Cell> path) {
        return path.stream().anyMatch(c -> c.getI() == cell.getI() && c.getJ() == c.getJ());
    }

    public static class Cell {
        private int i, j;
        private char ch;

        private Cell parentCell;

        public enum VisitStatus {VISITED, IN_PROGRESS, UNVISITED};

        private VisitStatus visitStatus = VisitStatus.UNVISITED;

        public Cell(int i, int j, char ch) {
            super();
            this.i = i;
            this.j = j;
            this.ch = ch;
        }

        public int getI() {
            return i;
        }

        public int getJ() {
            return j;
        }

        public char getCh() {
            return ch;
        }

        public void setCh(char ch) {
            this.ch = ch;
        }

        public VisitStatus getVisitStatus() {
            return visitStatus;
        }

        public void setVisitStatus(VisitStatus visitStatus) {
            this.visitStatus = visitStatus;
        }

        public Cell getParentCell() {
            return parentCell;
        }

        public void setParentCell(Cell parentCell) {
            this.parentCell = parentCell;
        }
    }

    public static class Maze {
        private Cell[][] grid;
        private Cell startCell;
        private Cell endCell;

        private static final char START_CELL_CHAR = 'S';
        private static final char END_CELL_CHAR = 'E';
        private static final char WALL_CHAR = '#';
        private static final char EMPTY_SPACE_CHAR = '.';

        public Maze(String filePath) {
            grid = createFromFile(filePath);
            printCells();
        }

        public Cell[][] getGrid() {
            return grid;
        }

        public Cell getStartCell() {
            return startCell;
        }

        public Cell getEndCell() {
            return endCell;
        }

        public boolean isStartCell(Cell cell) {
            return startCell.getI() == cell.getI() && startCell.getJ() == cell.getJ();
        }

        public boolean isEndCell(Cell cell) {
            return endCell.getI() == cell.getI() && endCell.getJ() == cell.getJ();
        }

        List<Cell> getNeighbours(Cell cell) {
            List<Cell> neighboursList = new ArrayList<>();
            int mazeHeight = grid.length;
            int mazeWidth = grid[0].length;

            if(cell.getI() - 1 > 0) {
                neighboursList.add(grid[cell.getI() - 1][cell.getJ()]);
            }
            if(cell.getI() + 1 < mazeHeight) {
                neighboursList.add(grid[cell.getI() + 1][cell.getJ()]);
            }
            if(cell.getJ() - 1 > 0) {
                neighboursList.add(grid[cell.getI()][cell.getJ() - 1]);
            }
            if(cell.getJ() + 1 < mazeWidth) {
                neighboursList.add(grid[cell.getI()][cell.getJ() + 1]);
            }
            return neighboursList;
        }

        public static boolean isWall(Cell cell) {
            return cell.getCh() == WALL_CHAR;
        }

        public static boolean isEmptySpace(Cell cell) {
            return cell.getCh() == EMPTY_SPACE_CHAR;
        }

        public void printCells() {
            Stream.of(grid).forEach(row-> {
                Stream.of(row).forEach(cell -> System.out.print(cell.getCh()) );
                System.out.println();
            });

        }

        private Cell[][] createFromFile(String filePath) {
            Cell[][] maze = null;
            try(Scanner scan = new Scanner(Paths.get(filePath)) ) {
                List<Cell[]> list = new ArrayList<>();

                for(int i = 0; scan.hasNext(); i++) {
                    String line = scan.nextLine();
                    char[] chArr = line.toCharArray();
                    Cell[] row = new Cell[chArr.length];

                    for(int j = 0; j < chArr.length; j++) {
                        char ch = chArr[j];
                        Cell cell = new Cell(i, j, ch);
                        row[j] = cell;
                        if(ch == START_CELL_CHAR) {
                            startCell = cell;
                        } else if (ch == END_CELL_CHAR) {
                            endCell = cell;
                        }
                    }

                    list.add(row);
                }

                if(startCell == null || endCell == null) {
                    throw new RuntimeException("Start cell or End cell not present");
                }
                maze = list.toArray(new Cell[][]{});
            } catch(Exception ex) {
                ex.printStackTrace();
            }


            return maze;
        }
    }
}

Note: Your sample does not have a solution.

Sample input which has solution

#########################################
S....#....#.#.#....#.........#..........E
###.#.#####.#.#.###.#.#.#.#.###.#.#.#####
#...#.#...#.#.#...#.#.#.#.#.#.#...#......
#.#####.#.###.###.#####..####.#.#.###.#.#
#.....#.#......#...#.#.#.....#.#.........
#.###.#.#######.###.########.##.#######.#
#.#.#.#.#.#...#..........#.#.#...........
###.#.#.#.###.#######.#.####.######.#.#.#
#.#.#.#.#.#.#.#.#...#.#.#..#.............
#.#.#.#.#.#.###.#.#.#####.###.#.#######.#
#.#.....................................#
#########################################

Output

Path size : 89
#########################################
SOOO.#....#.#.#....#.........#...OOOOOOOE
###O#.#####.#.#.###.#.#.#.#.###.#O#.#####
#OOO#.#...#.#.#...#.#.#.#.#.#.#..O#..OOO.
#O#####.#.###.###.#####..####.#.#O###O#O#
#OOOOO#.#......#...#.#.#.....#.#.OOOOO.O.
#.###O#.#######.###.########.##.#######O#
#.#.#O#.#.#...#..........#.#.#.........O.
###.#O#.#.###.#######.#.####.######.#.#O#
#.#.#O#.#.#.#.#.#OOO#.#.#..#.OOO.......O.
#.#.#O#.#.#.###.#O#O#####.###O#O#######O#
#.#..OOOOOOOOOOOOO.OOOOOOOOOOO.OOOOOOOOO#
#########################################

Note: Probably breadth first search would have given a better result.

11thdimension
  • 10,333
  • 4
  • 33
  • 71
0

Ask yourself how you could find the path. At each step consider you reach some cell.

  1. If I already passed through this cell, eg this cell is already in my path, return (avoid cycles)
  2. This cell looks good, so add it to the path
  3. If that cell is my destination, store the current path to the solution path, save solution, remove cell from path and return
  4. For each neighbor relaunch recursively the procedure on the neighbor
  5. Remove current cell from current path to leave current path intact

Something like that :

private static ArrayList <Cell> findPath(Maze currentMaze,Cell current,ArrayList <Cell> currentPath, ArrayList< ArrayList <Cell> > solutionsFound){
    // step 1
    if (currentPath.exists(current)) {
      return;
    }

    // step 2
    currentPath.add(current);

    // step 3
    if(current == currentMaze.getStartCell()){
      solutionsFound.add(currentPath.clone());
      currentPath.remove(current);
      return;
    }

    // step 4
    ArrayList<Cell> neighbors = currentMaze.getNeighbors(current);
    for(int i=0;i<neighbors.size();i++){
        findPath(currentMaze, neighbors[i], currentPath, solutionsFound);
    }

    // step 5
    currentPath.remove(current);
}

Starting with :

ArrayList< ArrayList <Cell> > solutionsFound = new ArrayList< ArrayList <Cell> >();
ArrayList <Cell> currentPath= new ArrayList <Cell> ();
findPath(currentMaze, currentMaze.getStartCell(), new ArrayList <Cell>, solutionsFound);

At the end, solutionsFound contains solutions and currentPath should be empty.

norisknofun
  • 859
  • 1
  • 8
  • 24
  • ps : this is the solution recommanded by @Kiro Yakuza but recursive with tail recursion (the solutions are accumulated during the exploration) – norisknofun Apr 20 '16 at 10:37