解决无返途能力的迷宫


11

我需要编写一个解决迷宫的程序。迷宫具有图结构,其中每个节点-一些房间和边缘-出口到其他房间:

在此处输入图片说明

规格:

  • 我们从一个随机的房间开始。
  • 迷宫有死角,出口为0或很少。
  • 我们对所有迷宫一无所知,只知道当前房间的数量和其中的门列表。
  • 当我们进入新房间时,我们不知道我们来自哪里(当前房间的哪扇门将我们引回到先前的房间)。
  • 我们可以识别何时到达出口。
  • 我们从当前房间移到每个房间的任何可用步骤。
  • 例如,如果我们在6号房间,我们就不能跳楼开始。就像机器人运动一样。
  • 我们确切知道当前房间的ID。我们知道当前房间(并非在所有迷宫中)每个门的ID。
  • 我们控制机器人。

我查看了所有已知的算法,但是它们都至少需要其他功能才能返回上一个房间。根据规范,我们不能使用任何搜索最短路径的算法(实际上,我不需要最短路径),因为我们只知道当前房间。我们不能使用左手(右手)跟随算法,因为不知道出口的方向。可能唯一符合规格的解决方案是在每个房间选择随机退出,以期能找到一些退出时间。

有什么建议如何以更聪明的方式解决这种迷宫?


2
这是作业吗?
MichaelHouse

2
这是家庭作业的一部分。(我应该以某种方式标记吗?)。所有房间都有唯一的ID。
Kyrylo M

2
在附图中房间的编号是否如此?也许是关于迈向更高的数字?(或更低,具体取决于您从哪里开始)
MichaelHouse

2
出口清单是否总是顺序相同?在这种情况下,我们可以随时创建地图吗?我在房间5,进入房间列表中的第二个房间,找到房间4。所以现在我知道房间5-> 2(房间4)。
MichaelHouse

4
@archer,尽管重复发布可能会为您提供更多答案-现在,想要该问题答案的其他人必须找到两个不同的站点,并提供不同的答案。这就是规则要求单个问题的原因,以使其他人更容易获得帮助。
独眼巨人

Answers:


3

嗯,您知道实际房间的数量。这样就可以建立一个数据结构。我猜出口列表中没有附带房间号。但是,在随机选择一个之后,至少您知道存在连接。

假设您在4号房中,然后选择三个随机出口之一。现在系统告诉您,您在6号房间,只剩下一个出口。您选择了它,然后回到了房间4。至此,您已经收集了有关迷宫结构的一些信息。现在,您选择另一个出口并在房间5中结束。有关房间4的Moe信息(一个出口到6,一个出口到5)。

您可以选择一个特定的出口吗?他们是否编号过,比如说如果您在4号房间中选择退出,那么您总是以6号结尾?否则,您至少可以找到可能的路线。

好的,请阅读您的评论。因此,如果出口具有ID,并且这些ID静态链接到某个房间(即使您不知道从哪个房间开始),则只需选择一个新ID,并记住出口/房间的连接并记住已经尝试过哪个出口即可。尝试未尝试的出口,直到您搜索了每个房间。

这样实际上很简单。几步之后,您应该具有或多或少完整的连接列表。仅当您进入新房间时,您才能(几步走)随机跑动。但是,每走一步,您就会获得更多信息,并且每当您进入之前访问过的房间时,您都可以做出更明智的决定(例如,当您找到第4个房间的死胡同时,因为它没有未经测试的出口,因此不再进行测试)。

编辑 的想法是先采取随机步骤,然后记录您所描述的信息(Dan的描述更为简洁)。如果您发现自己所在的房间没有退出通道,则可以使用任何已知的探路者找到通往您尚未探索的退出通道的最短路径。

并非100%肯定,但是我认为Peter Norvig在他的书“人工智能:一种现代方法”中写了一个类似的问题(Wumpus迷宫)。虽然,如果我没记错的话,它与寻找路径无关,而与系统可以获取的有关邻近房间的某些信息的决策有关。

编辑:

不,不仅是随机的。通过跟踪已经尝试过的出口,可以消除不必要的冗余。实际上,您甚至不需要随机选择。

假设您从房间4开始。获得的信息:3个出口A,B,C您总是选择第一个(现在尚未使用)出口。因此,从A开始。您在房间6结束。现在您记住在房间6中4A => 6(并已使用),您将获得信息1出口A。再次选择第一个未使用的(在这种情况下仅退出)返回房间知道6A => 4(并且房间6中没有其他出口)现在您选择下一个出口B并到达房间5 ...

迟早您将以这种方式搜索所有房间。但是有系统的问题。

您需要进行路径查找的唯一原因是,当您发现自己已进入所有出口的房间时。然后,您将想找到直接进入下一间有未开发出口的房间的方式,以便您进行搜索。因此,主要窍门是少知道哪个出口通向哪个房间(尽管这可能会有所帮助),但要跟踪尚未尝试过的出口。

例如,通过这种方式,您可以避免一直在圈子里奔跑,这对于纯随机方法来说是有风险的。

在您的迷宫示例中,这很可能无关紧要。但是,在一个具有许多房间和连接且可能是棘手的圆形布置房间的大迷宫中,此系统可确保您尝试最少的尝试就能找到出口。

(我认为这可能与游戏玩家想出的Byte56相同)


是的,我可以选择房间外面的特定出口(门)。它们具有唯一的ID。因此,您的建议是先探索整个迷宫,然后再使用任何已知算法?
Kyrylo M

我开始编写程序并遇到一个新问题……首先,我探索所有房间以建立具有所有连接的结构。第二步将是寻找路径。但是添加所有连接后,我将停止构建。但是那样我还是会到达出口...所以这只是一个选择的随机方向算法...
Kyrylo M

10

我认识到您可能还从其他答案中获得了要点,但这是一个有趣的问题,我觉得自己需要做一些Python编码。这是我的面向对象方法。缩进定义范围。

图表示

可以轻松地将图形存储为键,值字典,其中键是房间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。我需要创建一个结构来存储业务代表已经了解的信息,例如尚未访问过的门。穿过,通往那扇门的地方已经穿过。

此类表示有关单个房间的信息。我选择将未访问的门存储为,将已访问的门存储setdictionary,其中键是门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次。我避免放置异常处理以保持代码简洁。我对Python的任何评论表示赞赏:


巨大的答案!可悲的是不能两个答案:(我已经在C#编写的程序,通常几乎使用了相同的想法回溯使用呼吸深度搜索算法。
Kyrylo中号

4

本质上,您有一个方向图,其中每个连通的房间都由两个未知的通道连接-一个方向。假设您从node开始1,然后进入门AB引出。您不知道每扇门之外的东西,因此您只需选择门A。你到房间2,里面有门CDE。您现在知道门A从一个房间1通往另一个房间2,但是您不知道如何返回,因此您随机选择门C。你回到房间1!现在您知道如何在房间1和房间之间走动了2。继续探索最近的未知门,直到找到出口!


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()房间的公共节点路径查找器之一,希望这一点已经足够清楚,您可以开始进行一些工作了。


这是@ 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
我不知道我来自哪里。因此,该算法无法解决任务。但是感谢您的尝试。
Kyrylo M

3
所以您说的是您不允许认识前一个房间吗?还是您不知道前一个房间?上面的代码为您跟踪以前的房间。如果您不知道,我认为除了随机地迷宫迷宫尝试“ x”次尝试之外,没有其他有效的解决方案;如果找不到出口,您可以认为迷宫不可解决。
Doug.McFarlane 2011年

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 2011年

1
我认为这if (path.contains(door)) continue;违反了他的要求-代理商实际上不知道门是否会通回到他已经去过的房间,除非他穿过门。
CiscoIPPhone

谢谢,固定!是的,现在我看了需求,问题似乎有点棘手。如果您无法回溯,那么您可以期望的最好结果就是随机漫步。
优厚
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.