简体   繁体   English

具有深度优先搜索的递归算法

[英]Recursive algorithm with Depth First Search

Suppose I have the following maze: (not formatted properly) 假设我有以下迷宫:(格式不正确)

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

S represents start of the maze and E represents end of the maze. S表示迷宫的开始,E表示迷宫的结束。 I have two given classes; 我有两个给定的课程; Maze and Cell . MazeCell 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. 此方法递归地查找从currentMaze开始到结束的路径,该路径通过当前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 (ie, the path explored so far). 该路径是从迷宫的开始到当前单元(即,到目前为止探索的路径)所遵循的单元序列的ArrayList。 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. 如果没有从当前到最终的路径,该算法应该返回null,该路径最多只能通过每个Cell一次。 Otherwise, it should return the complete path from the start of the maze to the end as a sequence of Cells in an ArrayList. 否则,它应该返回从迷宫开始到结束的完整路径作为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. 为了探索尚未访问的邻居的所有路径,您将需要使用Maze的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. 我真的很难建立这种方法。

Ok here it is. 好的,这是。 You don't only need a DFS but also a way to store the found path. 您不仅需要DFS,还需要存储找到的路径。

The method signature that you have suggested for findPath will not work. 您为findPath建议的方法签名将不起作用。 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. 它的path参数是一个列表,它会在遍历时存储所有节点,因为即使它是一个递归算法,我们也不会在将列表传递给下一个findPath调用之前完全复制列表,坦率地说,我们不应该这样做以提高性能并减少内存消耗。

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 我们必须为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 . 当我们到达End node时,我们需要返回所有递归,以便状态必须存储在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. 这是一个实际的Java实现。 It reads the maze from a text file. 它从文本文件中读取迷宫。

Following is the Github link with the sample maze text files. 以下是带有示例迷宫文本文件的Github链接。

https://github.com/ConsciousObserver/stackoverflow/tree/master/TestMaze 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. 注意:广度优先搜索可能会给出更好的结果。

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. 最后, solutionsFound包含解决方案,currentPath应为空。

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

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