Java решая лабиринт с проблемой рекурсии

У меня есть присвоение, где я, как предполагается, могу отобразить путь лабиринта от входа до выхода, и я заставил это работать в известной степени, но когда лабиринт становится более сложным с тупиками и таким, программа входит в бесконечную рекурсию. Если бы Вы могли бы дать мне какую-либо справку для указания на меня в правильном направлении, это очень ценилось бы.

Mu текущая теория может быть найден в классе Помещения.

Вот класс Помещения, где ссылки на каждую комнату, подключающую лабиринт, хранятся, отчасти как связанный список, связанный в 6 направлениях, севере, юге, востоке, западе, и вниз.

import java.util.ArrayList;

public class OurRoom
{
    private OurRoom exits[];
    private String name;
    private static ArrayList<OurRoom> list;

    public OurRoom()
    {
        this(null);
    }

    public OurRoom(String name)
    {
        this.name = name;
        this.list = new ArrayList<OurRoom>();
        exits = new OurRoom[Direction.values().length];
        for(OurRoom exit : exits)
        {
            exit = null;
        }
    }       


    public void connectTo(OurRoom theOtherRoom, Direction direction)
    {
        exits[direction.ordinal()] = theOtherRoom;
        theOtherRoom.exits[direction.getOpposite().ordinal()] = this;
    }

    public OurRoom getExit(Direction direction)
    {
        return exits[direction.ordinal()];
    }

    public boolean lookExit(Direction direction)
    {
        return exits[direction.ordinal()] != null;
    }

    public String getName() {
        return name;
    }

    public OurRoom solveRecursively(OurRoom exit) {
        list.add(this);
        if(this == exit) {
            return this;
        }else { 
            OurRoom temp = null;            
            if(lookExit(Direction.east)) {
                temp = exits[Direction.east.ordinal()].solveRecursively(exit);              
            }   
            else if(lookExit(Direction.up)) {
                temp = exits[Direction.up.ordinal()].solveRecursively(exit);
            }
            else if(lookExit(Direction.south)) {
                temp = exits[Direction.south.ordinal()].solveRecursively(exit);             
            }           
            else if(lookExit(Direction.down)) {
                temp = exits[Direction.down.ordinal()].solveRecursively(exit);
            }
            else if(lookExit(Direction.west)) {
                temp = exits[Direction.west.ordinal()].solveRecursively(exit);      
            }   
            else if(lookExit(Direction.north)) {
                temp = exits[Direction.north.ordinal()].solveRecursively(exit); 
            }
            return temp;
        }
    }

    public ArrayList<OurRoom> getList() {
        return list;
    }

}

Вот перечисление Направления

public enum Direction
{
    north, south, east, west, up, down;

    public Direction getOpposite()
    {
        switch(this)
        {
            case north:
                return south;
            case south:
                return north;
            case east:
                return west;
            case west:
                return east;
            case up:
                return down;
            case down:
                return up;
            default:
                return this;
        }
    }
}

И вот пример того, как лабиринт создается.

import java.util.ArrayList;
import java.util.Iterator;

public class OurMaze
{
    private OurRoom entrance, exit;

    public OurMaze()
    {
        this(1);
    }

    public OurMaze(int mazeNumber)
    {
        entrance = null;
        exit = null;
        switch(mazeNumber)
        {
        case 0:
            break;
        case 1:
            this.buildMaze1();
            break;             
        default:
        }
    }

    public OurRoom getEntrance()
    {
        return entrance;
    }

    public OurRoom getExit()
    {
        return exit;
    }

    public Iterator<OurRoom> findPathRecursively() {
        entrance.solveRecursively(exit);
        ArrayList<OurRoom> list = entrance.getList();       
        return list.iterator();
    }

    private void buildMaze1()
    {
        OurRoom room1, room2;

        room1 = new OurRoom("Room 1");
        room2 = new OurRoom("Room 2");
        room1.connectTo(room2, Direction.north);

        entrance = room1;
        exit = room2;
    }

    public static void main(String[] args) {
        OurMaze maze = new OurMaze(1);      
    }
}
5
задан Nikita Rybak 12 July 2010 в 00:12
поделиться

4 ответа

Другие описали подходящие подходы к решению этой проблемы, но я думаю, что стоит точно указать, почему ваша программа не будет масштабироваться на более сложные лабиринты.

Как намекнул duffymo, проблема в том, что ваш алгоритм не делает никакого обратного пути правильно - когда он берет ветку, которая оказывается тупиковой, и возвращается в предыдущий квадрат, он вообще не запоминает этого. А поскольку он пробует выходы в фиксированном порядке, он всегда будет повторять неудачный выход немедленно.

Посмотрите, как определена функция solveRecursively, и вы увидите, что из любой данной комнаты будет опробовано только одно направление. Если комната имеет выход на восток, то не имеет значения, есть ли в ней другие выходы, поскольку блок if-else никогда их не рассмотрит.

Таким образом, получается, что ваша логика решения потерпит неудачу (т.е. попадет в бесконечный цикл между двумя комнатами) в любом случае, когда правильным решением не будет "первый" выход из каждой комнаты в том порядке, который вы там определили.

(Быстрым решением было бы хранить простой булевский флаг для каждой комнаты/направления. Установите его перед вызовом рекурсивного вызова, тогда, если вы снова окажетесь в этой комнате, вы будете знать, что это направление не сработало, и можете позволить блоку if провалиться, чтобы попробовать один из других выходов. Рефакторинг этого для использования типичной логики BFS, как предлагает Никита, был бы лучше в целом)

2
ответ дан 13 December 2019 в 22:01
поделиться

Готов поспорить, вам нужно какое-нибудь дерево, чтобы отслеживать, где вы были.

Когда рекурсия терпит неудачу, это обычно означает, что человек, пишущий метод, неправильно выразил условие остановки. Какая у вас?

Думаю, это была первая игра, с которой я столкнулся на компьютере. Это был мэйнфрейм IBM в школе, где я получил степень бакалавра. Ввод-вывод был на бумажном телетайпе. Многие соленые слезы пролились на счетные доллары, которые были смыты, играя в эту игру-лабиринт. Безудержное веселье.

1
ответ дан 13 December 2019 в 22:01
поделиться

Вам просто нужно хранить двумерный массив со значениями, указывающими, была ли посещена ячейка или нет: вы не хотите проходить через одну и ту же ячейку дважды.

Кроме этого, это просто поиск по ширине (поиск по глубине тоже подойдет, если не нужен кратчайший путь).

Некоторые ссылки
http://en.wikipedia.org/wiki/Flood_fill
http://en.wikipedia.org/wiki/Breadth-first_search
http://en.wikipedia.org/wiki/Depth-first_search

Пример процедуры поиска.

    void dfs(int i, int j) {
        if cell(i, j) is outside of maze or blocked {
            return
        }
        if visited[i][j] {
            return
        }
        visited[i][j] = true;
        dfs(i + 1, j);
        dfs(i - 1, j);
        dfs(i, j + 1);
        dfs(i, j - 1);
    }

Сам путь можно найти, если, как в случае с visited, для каждой ячейки сохранять ячейку, из которой вы к ней пришли. Таким образом, печать будет выглядеть следующим образом (просто псевдокод).

var end = exit_point;
while (end != start_point) {
   print end;
   end = came_from[end];
}

edit
Приведенный выше код предназначен для двумерного лабиринта, и я только что заметил, что у вас есть трехмерная версия. Но в приведенном примере легко ввести третью координату.
Дайте мне знать, если возникнут какие-либо трудности.

6
ответ дан 13 December 2019 в 22:01
поделиться

При решении лабиринта представьте его как шестизначный граф, где каждый узел представляет собой комнату. и каждое ребро представляет собой движение в одном из шести направлений. Затем вы можете применить некоторые из хорошо известных алгоритмов поиска кратчайших путей.

На этой странице описаны различные решения для поиска путей через графы, которые структурированы как таковые. Ваш граф проще, чем те, которые описывают карты реального мира, поскольку стоимость движения вниз по любому ребру равна стоимости движения по любому другому ребру.

Обязательно посмотрите алгоритм Дейкстры .

1
ответ дан 13 December 2019 в 22:01
поделиться
Другие вопросы по тегам:

Похожие вопросы: