Решающий лабиринт без возвратной способности

11

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

введите описание изображения здесь

Технические характеристики:

  • Начнем со случайной комнаты.
  • Лабиринт имеет тупики, 0 или несколько выходов.
  • Мы ничего не знаем обо всем лабиринте, только номер текущей комнаты и список дверей из него.
  • Когда мы входим в новую комнату, мы не знаем, откуда мы пришли (какая дверь из текущей комнаты ведет нас обратно в предыдущую комнату).
  • Мы можем узнать, когда мы достигнем выхода.
  • Каждый шаг мы перемещаемся из текущей комнаты в какую-то доступную дверь из нее.
  • Например, если мы находимся в комнате 6, мы не можем просто начать прыгать. Это как движение робота.
  • Мы точно знаем идентификатор текущей комнаты. Мы знаем ID каждой двери в текущей комнате (не во всех лабиринтах).
  • Мы контролируем робот.

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

Любое предложение, как решить такой лабиринт в более умном способе?

Кирилло М
источник
2
Это домашнее задание?
MichaelHouse
2
Это часть домашней работы. (Должен ли я это как-то пометить?). Все номера имеют уникальный идентификатор.
Кирилл М
2
Номера обозначены как таковые на чертеже? Может быть, что-то о переходе на более высокие цифры? (или ниже в зависимости от того, с чего вы начали)
MichaelHouse
2
Списки выходов всегда в одном и том же порядке? Как, в, мы могли бы создать карту, как мы идем? Я нахожусь в комнате 5 и иду ко второй комнате в списке комнат, я нахожу комнату 4. Итак, теперь я знаю эту комнату 5-> 2 (комната 4).
MichaelHouse
4
@archer, хотя двойная публикация может дать вам больше ответов - теперь кто- то, кто хочет получить ответ на вопрос, должен найти два разных сайта с разными наборами ответов. Вот почему правила требуют отдельных вопросов, чтобы другим людям было проще получить помощь.
Циклоп

Ответы:

3

Хм, вы знаете номер фактической комнаты. Таким образом, вы можете построить структуру данных. Я полагаю, что к списку выходов не привязаны номера комнат. Но, выбрав случайным образом, вы знаете, по крайней мере, что есть связь.

Допустим, вы находитесь в комнате 4 и выберите один из трех случайных выходов. Теперь система сообщает вам, что вы находитесь в комнате 6 и у вас остался только один выход. Вы выбираете это и возвращаетесь в комнату 4. На данный момент вы собрали некоторую информацию о структуре лабиринта. Теперь вы выбираете другой выход и заканчиваете в комнате 5. Моя информация о комнате 4 (один выход в 6, один выход в 5)

Можете ли вы выбрать конкретный выход? они пронумерованы, скажем, если в комнате 4 вы выбираете выход, который всегда заканчивается 6? В противном случае вы по крайней мере можете узнать о возможных маршрутах.

Хорошо, просто прочитайте ваш комментарий. Поэтому, если у выходов есть идентификаторы, и они статически связаны с комнатой (даже если вы не знаете, какой из них для начала), вы просто выбираете новый и запоминаете соединения выхода / комнаты и помните, какой выход уже был опробован. Попробуйте не проверенные выходы, пока не обыщите каждую комнату.

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

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

Не уверен на 100%, но я думаю, что Питер Норвиг написал о подобной проблеме (лабиринт Wumpus) в своей книге «Искусственный интеллект: современный подход». Хотя, если я правильно помню, речь шла не столько о поиске пути, сколько о принятии решений относительно некоторой информации, которую система могла получить о соседних комнатах.

Редактировать:

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

Предположим, вы начинаете в комнате 4. Информация, которую вы получаете: 3 выхода A, B, C. Вы всегда выбираете первый, который теперь не используется, выход. Итак, начните с A. Вы заканчиваете в комнате 6. Теперь вы помните 4A => 6 (и использовали) в комнате 6, вы получаете информацию 1, выход A. Снова вы выбираете первый неиспользованный (и в данном случае только выход) Назад в комнату. для знания 6A => 4 (и больше никаких выходов в комнате 6) Теперь вы выбираете следующий выход B и достигаете комнату 5 ...

Рано или поздно вы обыщите все комнаты таким образом. Но в систематическом вопросе.

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

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

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

(Я думаю, что это та же система, что и Byte56 в Gamers)

Торстен Мюллер
источник
Да, я могу выбрать конкретный выход (дверь) из комнаты. У них есть уникальные идентификаторы. Итак, вы предлагаете сначала исследовать полный лабиринт, а затем использовать какой-либо известный алгоритм?
Кирилл М
Я начал писать программу и получил новый вопрос ... Сначала я исследовать все комнаты в структуру сборки со всеми подключениями. Второй шаг будет найти путь. Но я перестану строить, когда все соединения будут добавлены. Но таким образом я достигну выход все равно ... Так что это просто выбор алгоритма случайного направления ...
Кирило M
10

Я понимаю, что вы, вероятно, получили суть из других ответов, но это был забавный вопрос, и я захотел немного поработать над Python. Это мой объектно-ориентированный подход. Отступ определяет область видимости.

Графическое представление

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

map = {
1:[5, 2],
2:[1, 3, 5],
3:[2, 4],
4:[3, 5, 6],
5:[2, 4, 1],
6:[4]
}

Интерфейс агента

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

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

class AgentInterface(object):
    def __init__(self, map, starting_room):
        self.map = map
        self.current_room = starting_room

    def get_door_count(self):
        return len(self.map[self.current_room])

    def go_through_door(self, door):
        result = self.current_room = self.map[self.current_room][door]
        return result

Знание Агент

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

Этот класс представляет информацию об одной комнате. Я решил хранить не посещенные двери как a setи посещенные двери как a dictionary, где ключ - это идентификатор двери, а значение - это идентификатор комнаты, в которую он ведет.

class RoomKnowledge(object):
    def __init__(self, unvisited_door_count):
        self.unvisited_doors = set(range(unvisited_door_count))
        self.visited_doors = {}

Агентный алгоритм

  • Каждый раз, когда агент входит в комнату, он ищет в своем словаре знаний информацию о комнате. Если для этой комнаты нет записей, она создает новую RoomKnowledgeи добавляет ее в свой словарь знаний.

  • Он проверяет, является ли текущая комната целевой комнатой, и если да, то возвращается.

  • Если в этой комнате есть двери, которые мы не посещали, мы проходим через дверь и храним там, куда она ведет. Затем мы продолжим цикл.

  • Если не было не посещенных дверей, мы возвращаемся в комнаты, которые посетили, чтобы найти дверь с не посещенными дверями.

В Agentкласс наследует от AgentInterfaceкласса.

class Agent(AgentInterface):

    def find_exit(self, exit_room_id):
        knowledge = { }
        room_history = [] # For display purposes only
        history_stack = [] # Used when we need to backtrack if we've visited all the doors in the room
        while True:
            room_knowledge = knowledge.setdefault(self.current_room, RoomKnowledge(self.get_door_count()))
            room_history.append(self.current_room)

            if self.current_room==exit_room_id:
                return room_history

            if len(room_knowledge.unvisited_doors)==0:
                # I have destination room id. I need door id:
                door = find_key(room_knowledge.visited_doors, history_stack.pop())
                self.go_through_door(door)
            else:   
                history_stack.append(self.current_room)
                # Enter the first unopened door:
                opened_door = room_knowledge.unvisited_doors.pop()
                room_knowledge.visited_doors[opened_door]=self.go_through_door(opened_door)

Вспомогательные функции

Мне пришлось написать функцию, которая бы находила ключ в словаре с заданным значением, поскольку при возврате мы знаем идентификатор комнаты, в которую мы пытаемся попасть, а не какую дверь использовать для доступа к ней.

def find_key(dictionary, value):
    for key in dictionary:
        if dictionary[key]==value:
            return key

тестирование

Я проверил все комбинации начальной / конечной позиции на карте, приведенной выше. Для каждой комбинации распечатывает посещенные комнаты.

for start in range(1, 7):
    for exit in range(1, 7):
        print("start room: %d target room: %d"%(start,exit))
        james_bond = Agent(map, start)
        print(james_bond.find_exit(exit))

Заметки

Возврат не очень эффективен - в худшем случае он может пройти через каждую комнату, чтобы попасть в соседнюю комнату, но откат довольно редко - в приведенных выше тестах откат только три раза. Я избегал включать обработку исключений, чтобы сохранить код кратким. Любые комментарии по моему Python приветствуются :)

CiscoIPPhone
источник
Монументальный ответ! К сожалению, не может быть двух ответов :( Я уже написал программу на C # и в целом использовал почти те же идеи. Для возврата использовался алгоритм поиска по глубине.
Kyrylo M
4

По сути, у вас есть диаграмма направленности, где каждая связанная комната соединена двумя неизвестными проходами - по одному в любом направлении. Допустим , вы начинаете в узле 1, а двери Aи Bвывести. Вы не знаете , что лежит за каждой дверью, так что вы просто выбрать дверь A. Вы получаете в комнату 2, которая имеет двери C, Dи E. Теперь вы знаете , что дверь Aведет из комнаты 1в комнату 2, но вы не знаете , как получить обратно, так что вы случайно выбрать дверь C. Вы получаете обратно в комнату 1! Теперь вы знаете , как получить от комнаты 1и 2. Продолжайте исследовать через ближайшую неизвестную дверь, пока не найдете выход!

dlras2
источник
4

Так как комнаты всегда находятся в одном и том же порядке в списках выхода, мы можем быстро составить карту комнат, ища выход.

Мой псевдокод несколько Javaish, извините, я использую его много в последнее время.

Unvisited_Rooms - это хэш-карта, содержащая идентификатор комнаты и список индексов неотображаемых комнат или массива 2d, что бы ни работало.

Я предполагаю, что алгоритм может пойти что-то вроде этого:

Unvisited_Rooms.add(currentRoom.ID, currentRoom.exits) //add the starting room exits
while(Unvisited_Rooms.Keys.Count > 0 && currentRoom != end) //keep going while there are unmapped exits and we're not at the end
    Room1 = currentRoom
    ExitID = Room1.get_first_unmapped_Room() //returns the index of the first unmapped room
    if(ExitID == -1) //this room didn't have any more unmapped rooms, it's totally mapped
        PathTo(Get_Next_Room_With_Unmapped_Exits) //we need to go to a room with unmapped exits
        continue //we need to start over once we're there, so we don't create false links
    GoToExit(ExitID) //goes to the room, setting current room to the room on the other side
    Room1.Exits[exitID].connection = currentRoom.ID //maps the connection for later path finding
    Unvisited_Rooms[Room1.ID].remove(exitID) //removes the index so we don't worry about it
    if(Unvisited_Rooms[Room1.ID].size < 1) //checks if all the rooms exits have been accounted for
        Unvisited_Rooms.remove(Room1.ID)  //removes the room if it's exits are all mapped
    Unvisited_Rooms.add(currentRoom.ID, currentRoom.unvisited_exits) //adds more exits to the list

If(currentRoom != end && Unvisited_Rooms.Keys.Count < 1)
   print(No exit found!)
else
   print(exit is roomID: currentRoom.ID)

Вам нужно будет использовать один из общих искателей пути узлов к комнатам PathTo () через «карту». Надеюсь, это достаточно ясно, чтобы вы могли начать с чего-то.

MichaelHouse
источник
Вот отзыв, @ Byte56, который составляет 2/3 потерянной галочки. :)
Циклоп
2

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

solved = FALSE

SearchRoom(rooms[0], rooms[0])    // Start at room 1 (or any room)
IF solved THEN
  // solvable
ELSE
  // unsolvable
ENDIF
END

// Recursive function, should run until it searches every room or finds the exit
FUNCTION SearchRoom: curRoom, prevRoom
  // Is this room the 'exit' room
  IF curRoom.IsExit() THEN
    solved = TRUE
    RETURN
  ENDIF

  // Deadend?  (skip starting room)
  IF (curRoom.id <> prevRoom.id) AND (curRoom.doors <= 1) THEN RETURN

  // Search each room the current room leads to
  FOREACH door IN curRoom
    // Skip the room we just came from
    IF door.id <> prevRoom.id THEN
      SearchRoom(door, curRoom)
    ENDIF
    IF solved THEN EXIT LOOP
  NEXT

  RETURN
ENDFUNCTION

[Edit] Добавлен 'id' к предыдущей проверке и обновлен, чтобы сделать объект более ориентированным.

Doug.McFarlane
источник
1
Я не знаю на каждом шагу, откуда я пришел. Таким образом, эти алгоритмы не решают задачу. Но спасибо за попытку.
Кирилл М
3
Значит, вы говорите, что НЕ РАЗРЕШЕНЫ знать предыдущую комнату? Или что ты НЕ знаешь предыдущую комнату? Код выше отслеживает предыдущие комнаты для вас. Если вам не позволено знать, я не думаю, что существует какое-либо правильное решение, кроме случайной итерации лабиринта для числа попыток «х», и если вы не можете найти выход, вы можете предположить, что лабиринт неразрешим. ,
Doug.McFarlane
1
"Я не знаю". Я снова посмотрел код. Вторая проблема заключается в том, что использование рекурсии проблематично. Представьте себе, что вы находитесь внутри такого лабиринта. Как бы вы использовали рекурсивный алгоритм для поиска выхода?
Кирил М
Кроме того, что произойдет, если вы начнете в комнате 6? curRoom.doors <= 1, Поэтому функция возвращает немедленно, зная , что он находится в тупике, но думая , что он уже исследовал полноту лабиринта.
dlras2
Это близко, но оно взорвёт стек, если на графике есть циклы с длиной больше двух.
Великолепно
1

Короткий ответ - поиск в глубину с возвратом. Вы можете сделать ширину, если хотите, но ваш маленький робот будет намного больше ходить туда-сюда.

Конкретнее, предположим, что мы получили:

// Moves to the given room, which must have a door between
// it and the current room.
moveTo(room);

// Returns the list of room ids directly reachable from
// the current room.
getDoors();

// Returns true if this room is the exit.
isExit();

Чтобы найти выход, нам просто нужно:

void escape(int startingRoom) {
  Stack<int> path = new Stack<int>();
  path.push(startingRoom);
  escape(path);
}

boolean escape(Stack<int> path) {
  for (int door : getDoors()) {
    // Stop if we've escaped.
    if (isExit()) return true;

    // Don't walk in circles.
    if (path.contains(door)) continue;

    moveTo(door);
    path.push(door);
    if (escape(path)) return true;

    // If we got here, the door didn't lead to an exit. Backtrack.
    path.pop();
    moveTo(path.peek());
  }
}

Позвоните escape()с идентификатором стартовой комнаты, и робот переместится к выходу (по телефону moveTo()).

необычайно щедрый
источник
Я думаю, что escape(int startingRoom)следует позвонитьescape(Stack<int> path)
CiscoIPPhone
1
Я думаю, что if (path.contains(door)) continue;нарушает его требования - агент на самом деле не знает, ведет ли дверь обратно в комнату, в которой он уже находился, если он не проходит через дверь.
CiscoIPPhone
Спасибо, исправлено! Да, теперь, когда я смотрю на требования, проблема кажется немного подозрительной. Если вы не можете вернуться назад, лучшее, на что вы можете надеяться - это случайная прогулка.
Великолепно