Розв’язування лабіринту без можливості повернення


11

Мені потрібно написати програму, яка вирішить лабіринт. Лабіринт має структуру графіків, де кожен вузол - деяка кімната, а краї - виходить до інших приміщень:

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

Специфікація:

  • Починаємо з випадкової кімнати.
  • Лабіринт має тупики, 0 або кілька виходів.
  • Про все лабіринт ми нічого не знаємо , лише кількість поточної кімнати та список дверей.
  • Коли ми входимо в нову кімнату, ми не знаємо, звідки ми прийшли (яка двері з поточної кімнати повертає нас до попередньої кімнати).
  • Ми можемо розпізнати, коли доходимо до виходу.
  • Кожен крок ми переходимо від поточної кімнати до якоїсь доступної двері від неї.
  • Якщо ми, наприклад, у кімнаті 6, ми не можемо просто стрибнути, щоб почати. Це як рух робота.
  • Ми точно знаємо ідентифікатор поточної кімнати. Ми знаємо ідентифікацію кожної двері в поточній кімнаті (не у всіх лабіринтах).
  • Ми управляємо роботом.

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

Будь-яка пропозиція, як вирішити такий лабіринт більш розумним способом?


2
Це домашнє завдання?
MichaelHouse

2
Це частина домашніх завдань. (Чи варто це якось позначити?). У всіх номерах є унікальний ідентифікатор.
Кирило М

2
Чи номери пронумеровані як такі на кресленні? Можливо, щось про рух до більшої кількості? (або нижче, залежно від того, з чого ви почали)
MichaelHouse

2
Чи завжди списки виходів в одному порядку? Як у нас, чи можна створити карту під час руху? Я в кімнаті 5 і заходжу до 2-ї кімнати у списку номерів, знаходжу номер 4. Отже, тепер я знаю, що кімната 5-> 2 (кімната 4).
MichaelHouse

4
@archer, в той час як подвійна проводка може отримати вам більше відповідей - зараз, хто - то , хто хоче дати відповідь на питання, має знайти два різних сайти, з різними наборами відповідей. Ось чому правила хочуть поодинокі запитання, щоб іншим людям було легше отримати допомогу.
Циклопи

Відповіді:


3

Хм, ви знаєте номер фактичної кімнати. Таким чином, ви можете побудувати структуру даних. Я думаю, що до списку виходів немає прикріплених номерів номерів. Але вибираючи один випадковим чином, ви принаймні знаєте, що зв’язок існує.

Скажіть, ви перебуваєте в кімнаті 4 та оберіть один із трьох випадкових виходів. Тепер система каже вам, що ви перебуваєте в кімнаті 6 і у вас залишився лише один вихід. Ви обираєте це і повертаєтесь у номер 4. У цей момент ви зібрали деяку інформацію про структуру лабіринту. Тепер ви вибираєте інший вихід і закінчуєте номер 5. Інформація про номер 4 (один вихід до 6, один вихід до 5)

Ви можете вибрати конкретний вихід? вони пронумеровані, скажімо, якщо у кімнаті 4 ви виберете вихід, який ви завжди закінчуєте в 6? Інакше ви принаймні можете дізнатися про можливі маршрути.

Гаразд, просто читаю вам коментар. Тож якщо у виходів є ідентифікатори, і вони статично пов'язані з кімнатою (навіть якщо ви не знаєте, який для початку), ви просто виберете новий і запам’ятайте з'єднання виходу / кімнати і запам’ятайте, який вихід вже був спробуваний. Спробуйте неперевірені виходи, поки ви не шукали кожну кімнату.

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

Редагувати Ідея полягає в тому, щоб спершу зробити випадкові кроки та записати інформацію, яку ви знайшли, як я описав (опис Дена більш стислий). Якщо ви опинитесь в кімнаті з невідомими виходами, ви можете скористатись будь-яким відомим дорожником, щоб знайти найкоротший шлях до кімнати, в якій є виходи, які ви ще не досліджували.

Не на 100% впевнений, але я думаю, що Пітер Норвіг писав про подібну проблему (лабіринт Вупуса) у своїй книзі "Штучний інтелект: сучасний підхід". Хоча якщо я добре пам’ятаю, це було менше про пошук шляху і більше про прийняття рішень щодо деякої інформації, яку система може отримати про сусідні кімнати.

Редагувати:

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

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

Рано чи пізно ви таким чином обшукали всі кімнати. Але в систематичній справі.

Єдина причина того, що вам знадобиться для пошуку шляху, - це опинитися в приміщенні, де всі виходи вже досліджені. Тоді вам захочеться знайти прямий шлях до сусідньої кімнати з невивченими виходами, щоб піти самостійно зі своїм пошуком. Тому головна хитрість полягає в тому, щоб менше знати, який вихід веде до якої кімнати (хоча це може бути корисним), але слідкувати за ще не перевіреними виходами.

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

У вашому прикладі лабіринт, швидше за все, не має великого значення. Але у великому лабіринті з багатьма кімнатами та сполученнями, а може бути і в складних кільцевих приміщеннях, ця система гарантує, що ви знайдете вихід з якомога менше випробувань.

(Я думаю, що це, мабуть, та сама система, що і Byte56, придумана в Gamers)


Так, я можу вибрати конкретний вихід (двері) з кімнати. Вони мають унікальні посвідчення особи. Отже, ваша пропозиція - спочатку вивчити повний лабіринт, а потім використати будь-який відомий алгоритм?
Кирило М

Я почав писати програму і отримав нове запитання ... Спочатку я вивчу всі кімнати, щоб побудувати структуру з усіма з'єднаннями. Другим кроком буде пошук шляху. Але я припиняю будувати, коли всі з'єднання будуть додані. Але таким чином я все одно дістанусь до виходу ... Тож це лише алгоритм вибору випадкових напрямків ...
Кирило М

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 оцінили :)


Монументальна відповідь! На жаль, не може бути двох відповідей :( Я вже писав програму на C # і зазвичай використовував майже однакові ідеї. Для зворотного відстеження був використаний алгоритм пошуку глибини дихання.
Kyrylo M

4

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


4

Оскільки в списках вихідних кімнат кімнати завжди в одному порядку, ми можемо зробити швидку карту номерів під час пошуку виходу.

Мій псевдо-код дещо яваський, вибачте, останнім часом я його багато використовую.

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 () по всій "карті". Сподіваємось, це достатньо зрозуміло, щоб почати щось робити.


Ось підсумок @ 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

[Редагувати] Додано 'id' до попередньої перевірки та оновлено, щоб зробити більш орієнтованим об'єкт.


1
Я не знаю на кожному кроці, звідки я родом. Отже, ці алгоритми не вирішують завдання. Але дякую за спробу.
Кирило М

3
Отже, ви говорите, що НЕ ДОЗВОЛЕНО знати попередню кімнату? Або що ви не знаєте попередньої кімнати? Код вище відстежує попередні приміщення для вас. Якщо вам не дозволяють знати, я не думаю, що існує правильне рішення, окрім випадкового повторення лабіринту для 'x' кількості спроб, і якщо ви не можете знайти вихід, ви можете вважати, що лабіринт нерозв’язний .
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

Спасибі, виправлено! Так, тепер, коли я дивлюся на вимоги, проблема здається трохи рибкою. Якщо ви не можете відмовитися, найкраще, на що ви можете сподіватися, - це випадкова прогулянка.
чудовий
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.