귀환 능력이없는 미로 해결


11

미로를 해결하는 프로그램을 작성해야합니다. 미로는 각 노드 (일부 공간과 가장자리)가 다른 방으로 나가는 그래프 구조를 가지고 있습니다.

여기에 이미지 설명을 입력하십시오

사양:

  • 우리는 임의의 방에서 시작합니다.
  • 미로는 막 다른 길, 0 개 또는 몇 개의 출구가 있습니다.
  • 우리는 모든 미로 에 대해 아무것도 알지 못하고 현재 방의 수와 문 목록 만 알고 있습니다.
  • 우리가 새 방에 들어갔을 때 우리는 어디에서 왔는지 알 수 없습니다 (현재 방에서 어떤 문이 우리를 이전 방으로 다시 연결하는지).
  • 출구에 도달하면 알아볼 수 있습니다.
  • 각 단계는 현재 방에서 사용 가능한 문으로 이동합니다.
  • 예를 들어 우리가 6 번 방에 있다면, 우리는 단지 시작하기 위해 뛰어 넘을 수 없습니다. 로봇의 움직임과 같습니다.
  • 우리는 현재 방의 ID를 정확히 알고 있습니다. 우리는 현재 방에있는 각 문의 ID를 알고 있습니다 (모든 미로가 아님).
  • 우리는 로봇을 제어합니다.

모든 알려진 알고리즘을 보았지만 이전 방으로 돌아가려면 적어도 추가 기능이 필요합니다. 사양에 따르면 현재 방에 대해서만 알고 있기 때문에 가장 짧은 경로를 검색하는 알고리즘을 사용할 수 없습니다 (실제로 가장 짧은 것이 필요하지 않습니다). 출구 방향을 모르기 때문에 왼쪽 (오른쪽) 알고리즘을 사용할 수 없습니다. 아마도 사양을 충족시키는 유일한 솔루션은 어느 시간 종료가 발견되기를 희망하여 모든 방에서 임의의 종료를 선택하는 것입니다 ...

더 똑똑한 방법으로 그러한 미로를 해결하는 방법에 대한 제안이 있습니까?


2
숙제입니까?
MichaelHouse

2
숙제의 일부입니다. (어쨌든 이것을 표시해야합니까?). 모든 객실에는 고유 한 ID가 있습니다.
Kyrylo M

2
객실은 도면과 같이 번호가 매겨져 있습니까? 더 높은 숫자로 이동하는 것에 대해 뭔가? (또는 시작한 위치에 따라 낮음)
MichaelHouse

2
이탈 목록은 항상 같은 순서입니까? 마찬가지로지도를 만들 수 있습니까? 나는 방 5에 있고 방 목록에서 2 번째 방으로갑니다. 방 4를 찾습니다. 이제 저는 방 5> 2 (방 4)를 알고 있습니다.
MichaelHouse

4
@archer는 이중 게시 를 통해 더 많은 답변을 얻을 수 있습니다. 이제는 질문에 대한 답변을 원하는 다른 사람이 서로 다른 답변을 가진 두 개의 다른 사이트 를 찾아야 합니다. 그렇기 때문에 규칙 은 다른 사람이 도움을 받기 쉽도록 단일 질문을 원합니다 .
Cyclops

답변:


3

흠, 당신은 실제 방의 수를 알고 있습니다. 따라서 데이터 구조를 구축 할 수 있습니다. 출구 목록에 방 번호가 붙어 있지 않은 것 같습니다. 그러나 무작위로 하나를 선택한 후에는 연결이 있다는 것을 알고 있습니다.

당신이 방 4에 있다고 가정하고 3 개의 무작위 출구 중 하나를 선택하십시오. 이제 시스템은 6 번 방에 있고 출구가 하나만 남아 있다고 알려줍니다. 당신은 그것을 선택하고 방 4로 돌아 왔습니다.이 시점에서 당신은 미로의 구조에 관한 정보를 모았습니다. 이제 다른 출구를 선택하고 방 5에서 끝납니다. 방 4에 대한 Moe 정보 (한 출구는 6, 한 출구는 5)

특정 출구를 선택할 수 있습니까? 4 번 방에서 6 번으로 끝나는 출구 1을 선택하면 번호가 매겨져 있습니까? 그렇지 않으면 가능한 경로에 대해 적어도 알 수 있습니다.

좋아, 당신이 코멘트를 읽어보십시오. 따라서 출구에 ID가 있고 그 ID가 방에 정적으로 연결되어있는 경우 (시작에 대해 어느 것을 모르더라도) 새로운 것을 선택하고 출구 / 방 연결을 기억하고 어떤 출구가 이미 시도되었는지 기억하십시오. 각 싱글 룸을 검색 할 때까지 시도하지 않은 출구를 시도하십시오.

그렇게하면 실제로 간단합니다. 몇 단계를 거치면 다소 완전한 연결 목록이 나타납니다. 새로운 방에 들어갈 때만 (몇 단계 만) 무작위로 돌아 다닐 수 있습니다. 그러나 모든 단계에서 더 많은 정보를 얻고 이전에 방문한 방에 입장 할 때마다 더 현명한 결정을 내릴 수 있습니다 (예를 들어 막 다른 방이없는 경우 출구 4가없는 경우 막 다른 방 6을 다시 테스트하지 않음).

편집 아이디어는 무작위 단계를 먼저 수행하고 내가 설명한대로 찾은 정보를 기록하는 것입니다 (Dan의 설명이 더 간결합니다). 알 수없는 출구가없는 방에서 자신을 찾으면 알려진 패스 파인더를 사용하여 아직 탐색하지 않은 출구가있는 방으로가는 가장 짧은 방법을 찾을 수 있습니다.

100 % 확실하지는 않지만 Peter Norvig는 "Artificial Intelligence : A Modern Approach"라는 책에서 비슷한 문제 (Wumpus 미로)에 대해 썼습니다. 내가 옳은 것을 기억한다면 그것은 경로 찾기에 관한 것이 아니라 시스템이 이웃 방에 대해 얻을 수있는 일부 정보에 관한 의사 결정에 관한 것이 아닙니다.

편집하다:

아니요, 무작위 일뿐만 아니라 이미 시도한 엑시트를 추적하여 불필요한 중복을 제거합니다. 실제로 당신은 무작위로 선택할 필요조차 없습니다.

당신이 방 4에서 시작한다고 가정하자. 당신이 얻는 정보 : 3 개의 출구 A, B, C 당신은 항상 지금 사용하지 않는 출구로 첫 번째를 선택한다. 방 6에서 끝납니다. 이제 방 6에서 4A => 6 (및 사용됨)을 기억하여 정보 1 출구 A를 얻습니다. 다시 첫 번째 미사용 (이 경우에만 출구)을 선택합니다. 6A => 4를 알기 위해 (6 호실에서 더 이상 출구가 없음) 이제 다음 출구 B를 선택하고 5 호실에 도달하십시오.

조만간 모든 방을 그런 식으로 검색했을 것입니다. 그러나 체계적인 문제에서.

경로 찾기가 필요한 유일한 이유는 모든 출구가 이미 탐색 된 방에서 자신을 발견했을 때입니다. 그런 다음 탐험하지 않은 출구가있는 다음 방으로 직접 이동하여 검색을 할 수 있습니다. 따라서 주요 트릭은 어느 출구가 어느 방으로 연결되는지 아는 것이 적지 만 (이것이 도움이 될 수는 있지만) 아직 시도되지 않은 출구를 추적하는 것입니다.

예를 들어 이런 방식으로 항상 서클에서 달리는 것을 피할 수 있습니다. 순전히 무작위 접근의 위험이 있습니다.

예를 들어 미로에서 이것은별로 중요하지 않을 것입니다. 그러나 많은 방과 연결부 및 까다로운 원형 배열 방이있는 큰 미로 에서이 시스템은 가능한 한 적은 시도로 출구를 찾을 수 있습니다.

(나는 아마도 게이머가 Byte56과 동일한 시스템이라고 생각합니다)


예, 방에서 특정 출구 (문)를 선택할 수 있습니다. 그들은 고유 한 ID를 가지고 있습니다. 따라서 먼저 미로 전체를 탐색 한 다음 알려진 알고리즘을 사용하는 것이 좋습니다.
Kyrylo M

프로그램을 쓰기 시작했고 새로운 질문이 생겼습니다 ... 먼저 모든 방을 탐색하여 모든 연결로 구조를 만듭니다. 두 번째 단계는 경로를 찾는 것입니다. 그러나 모든 연결이 추가되면 구축을 중단합니다. 하지만 그런 식으로 나는 출구에 도달 할 것입니다 ... 그래서 이것은 단지 임의의 방향 알고리즘을 선택하는 것입니다 ...
Kyrylo M

10

나는 당신이 아마 다른 답변에서 요점을 얻었음을 알고 있지만 재미있는 질문이었고 작은 파이썬 코딩을하고 싶다고 느꼈습니다. 이것은 내 객체 지향 접근 방식입니다. 들여 쓰기는 범위를 정의합니다.

그래프 표현

그래프는 키, 룸 ID, 값은 연결된 룸의 배열 인 키 사전으로 쉽게 저장할 수 있습니다.

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

에이전트 인터페이스

먼저 에이전트가 환경에서 배울 수있는 정보와 수행 할 수있는 작업에 대해 생각해야합니다. 이것은 알고리즘에 대한 생각을 단순화합니다.

이 경우 상담원은 자신이있는 방의 ID에 대해 환경을 쿼리 할 수 ​​있어야하며, 그 방에있는 방의 문 수를 얻을 수 있어야합니다 ( 이것은 방의 ID가 아닙니다. 문으로 연결됩니다. ) 문 색인을 지정하여 문을 통과 할 수 있어야합니다. 에이전트가 알고있는 다른 것은 에이전트 자체가 파악해야합니다.

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

요원 지식

상담원이지도에 처음 들어가면 방의 출입문 수와 현재 출입 한 방의 ID 만 알 수 있습니다. 상담원이 배운 정보와 같은 정보를 저장하는 구조를 만들어야했습니다. 그 문으로 통하는 곳이 지나갔습니다.

이 클래스는 단일 방에 대한 정보를 나타냅니다. 방문하지 않은 문을로 set방문하고 방문한 문을로 저장하기로 선택했습니다 dictionary. 여기서 키는 문 ID이고 값은 연결된 방의 ID입니다.

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)

지원 기능

역 추적 할 때 우리가 얻으려고하는 방의 ID를 알고 있지만 그것을 얻는 데 사용할 문이 없기 때문에 값이 주어진 사전에서 키를 찾을 수있는 함수를 작성해야했습니다.

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

노트

역 추적은 매우 효율적이지 않습니다. 최악의 시나리오에서는 모든 방을지나 인접 방으로 갈 수 있지만 역 추적은 매우 드문 경우입니다. 위의 테스트에서는 3 번만 역 추적합니다. 코드를 간결하게 유지하기 위해 예외 처리를 피했습니다. 내 파이썬에 대한 의견은 감사합니다 :)


기념비적 답변! 슬프게도 두 가지 답을 할 수는 없습니다. (나는 이미 C #으로 프로그램을 작성했으며 일반적으로 거의 같은 아이디어를 사용했습니다. 역 추적에는 호흡 심도 검색 알고리즘이 사용되었습니다.
Kyrylo M

4

기본적으로 방향 그래프가 있습니다. 연결된 모든 방은 두 방향으로 알 수없는 두 개의 통로로 연결됩니다. 당신이 노드에서 시작하는 말 1, 문 AB리드 아웃. 각 문 너머에 무엇이 있는지 모르므로 문을 선택하십시오 A. 당신은 방에 도착 2문이있다, C, D,와 E. 이제 그 문을 알고 A방에서 리드 1방을 2무작위로 문을 선택할 수 있도록,하지만 당신은 돌아 가야하는 방법을 모른다 C. 당신은 방으로 돌아옵니다 1! 지금 당신은 객실 사이에 도착하는 방법을 알고 12. 출구를 찾을 때까지 가장 가까운 알 수없는 문을 계속 탐색하십시오!


4

출구 목록에서 객실의 순서는 항상 같으므로 출구를 찾는 동안 객실을 빠르게지도로 만들 수 있습니다.

내 의사 코드는 다소 Javaish입니다. 죄송합니다. 요즘 많이 사용하고 있습니다.

Unvisited_Rooms는 룸 ID와 매핑되지 않은 룸의 인덱스 목록 또는 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 () 룸에 공통 노드 경로 찾기 중 하나를 사용해야합니다.


여기에 당신이 잃어버린 확인 표시의 2/3를 구성하는 upBytete @ Byte56이 있습니다. :)
Cyclops

2

귀하의 요구 사항에 대해 너무 명확하지는 않지만 다음이 허용되면 알고리즘을 따르는 것이 간단 할 수 있습니다. 아마도 재귀 함수를 사용하기 때문에 아마도 버그가있을 것입니다. 또한 문이 당신이 온 방으로 이어지는 지 확인하지만, 3 개의 방 원형 경로가 어떻게 처리 될지 모르겠습니다. 그 3 개의 방에서 영원히 계속 반복 될 수 있습니다. 방을 두 번 확인하지 않도록 기록을 추가해야 할 수도 있습니다. 그러나 허용되지 않는 설명을 읽음으로써.

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
나는 내가 어디에서 왔는지 각 단계를 모른다. 따라서이 알고리즘은 작업을 해결하지 못합니다. 그러나 노력해 주셔서 감사합니다.
Kyrylo M

3
당신은 당신이 이전 방을 알 수 없다는 말을하고 있습니까? 아니면 이전 방을 몰라? 위의 코드는 이전 방을 추적합니다. 당신이 알 수 없다면, 'x'번 시도에 대해 미로를 무작위로 반복하는 것 이외의 유효한 해결책이 있다고 생각하지 않으며 출구를 찾을 수 없다면, 미로는 해결할 수 없다고 가정 할 수 있습니다 .
Doug.McFarlane

1
"모릅니다". 나는 다시 코드를 보았다. 두 번째 문제는 재귀 사용에 문제가 있다는 것입니다. 당신이 그런 미로 안에 있다고 상상해보십시오. 재귀 알고리즘을 사용하여 출구를 찾는 방법은 무엇입니까?
Kyrylo M

또한 6 번 방에서 시작하면 어떻게됩니까? curRoom.doors <= 1따라서 함수는 막 다른 길에 있음을 알고 있지만 미로 전체를 이미 탐색했다고 생각하면서 즉시 반환됩니다.
dlras2

이것은 가깝지만 길이가 2보다 큰 그래프에주기가 있으면 스택을 날려 버립니다.
시정촌

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()시작 실의 ID로 전화 하면 로봇이 출구로 이동합니다 ( moveTo()).


escape(int startingRoom)전화해야 한다고 생각 합니다escape(Stack<int> path)
CiscoIPPhone

1
나는 if (path.contains(door)) continue;그의 요구 사항을 위반한다고 생각 합니다. 상담원은 실제로 문이 통과하지 않는 한 문이 이미있는 방으로 이어지는 지 알 수 없습니다.
CiscoIPPhone

고마워요! 예, 이제 요구 사항을 살펴보면 문제가 약간 비린 것처럼 보입니다. 역 추적 할 수없는 경우 무작위로 걷는 것이 가장 좋습니다.
munificent
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.