【问题标题】:Recursive algorithm with Depth First Search深度优先搜索的递归算法
【发布时间】:2016-04-20 00:06:30
【问题描述】:

假设我有以下迷宫:(格式不正确)

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

S 代表迷宫的开始,E 代表迷宫的结束。我有两个给定的课程; Maze 和 Cell 。我必须构建以下递归辅助方法来找到迷宫的解决方案:

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

这个方法 递归地找到一条从 currentMaze 的起点到终点的路径,该路径通过 当前单元格。路径是一个 ArrayList 的单元格序列,从 迷宫的起点到当前单元格(即到目前为止探索的路径)。为了避开路径 比需要的时间长,算法应该避免重新访问已经在这个路径中的单元格。这 如果没有从当前到结束的路径,算法应该返回 null 每个 Cell 最多一次。否则,它应该返回从迷宫开始的完整路径 到最后作为 ArrayList 中的一系列单元格。 您必须将其实现为递归算法。为了探索所有尚未通过邻居的路径 访问过,你会想要使用 Maze 的 getNeighbors。

为了构建这个递归方法,我得到了以下方法:

+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. 

到目前为止,这就是我所做的:

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

我真的很难构建这个方法。

【问题讨论】:

  • 请问您为什么不只进行迭代深度/呼吸优先搜索?它比递归解决方案更容易实现和更快(假设它不是尾递归,在这种情况下它是等效的)。
  • 要求通过递归 DFS 来实现。
  • 另外,我问了一个类似的问题here。它是用 C# 编写的,但作为另一种 OOP 语言,它并没有什么不同。
  • 是的,很遗憾,我被要求使用递归。

标签: java algorithm recursion


【解决方案1】:

好的,就是这样。您不仅需要 DFS,还需要一种存储找到的路径的方法。

您为findPath 建议的方法签名将不起作用。 path 它的参数是一个列表,它将在遍历时存储所有节点,因为即使它是递归算法,我们也不会在将列表传递给下一个 findPath 调用之前完全复制列表,坦率地说我们不应该这样做可以提高性能并减少内存消耗。

我能想到的最简单的方法是让每个单元格都指向它的父级。父单元格是一个单元格被发现为邻居的单元格。

我们必须对findPath使用以下签名

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

当我们到达End node 时,我们需要返回所有递归,这样状态就必须存储在findPath 之外。

剩下的很简单,我们可以使用下面的算法(是伪代码)

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

注意:此算法不会产生最短路径,只要找到任何路径就会返回。

这是一个实际的 Java 实现。它从文本文件中读取迷宫。

以下是带有示例迷宫文本文件的 Github 链接。

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

注意:您的样本没有解决方案。

样本输入有解

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

输出

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#
#########################################

注意:可能广度优先搜索会得到更好的结果。

【讨论】:

    【解决方案2】:

    问问自己如何找到路径。在每一步都考虑到您到达某个单元格。

    1. 如果我已经通过了这个单元格,例如这个单元格已经在我的路径中,返回(避免循环)
    2. 这个单元格看起来不错,所以将它添加到路径中
    3. 如果该单元格是我的目的地,请将当前路径存储到解决方案路径,保存解决方案,从路径中删除单元格并返回
    4. 对于每个邻居,递归地重新启动邻居上的过程
    5. 从当前路径中移除当前单元格以保持当前路径完整

    类似的东西:

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

    从:开始

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

    最后,solutionsFound 包含解决方案,currentPath 应该为空。

    【讨论】:

    • ps : 这是@Kiro Yakuza 推荐的解决方案,但带有尾递归(解决方案在探索过程中累积)
    猜你喜欢
    • 2015-12-12
    • 2011-07-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-13
    • 1970-01-01
    相关资源
    最近更新 更多