帰路能力のない迷路を解く


11

迷路を解決するプログラムを書く必要があります。Mazeにはグラフ構造があり、各ノード-一部の部屋とエッジ-が他の部屋に出ます。

ここに画像の説明を入力してください

仕様:

  • ランダムな部屋から始めます。
  • 迷路には行き止まりがあり、出口が0個か数個あります。
  • 私たちはすべての迷路について何も知りません。現在の部屋の数とそこからのドアのリストだけです。
  • 新しい部屋に入るとき、どこから来たのかわかりません(現在の部屋のどのドアから前の部屋に戻るか)。
  • 出口に着いたときに認識できます。
  • 現在の部屋からそこから利用可能なドアに移動する各ステップ。
  • たとえば部屋6にいる場合、ジャンプして開始することはできません。ロボットの動きのようなものです。
  • 現在の部屋のIDを正確に知っています。現在の部屋の各ドアのIDを知っています(すべての迷路ではありません)。
  • ロボットを制御します。

私はすべての既知のアルゴリズムを調べましたが、すべて前の部屋に戻るには、少なくとも追加の機能が必要です。仕様によると、現在の部屋しか把握していないため、最短経路を検索するアルゴリズムを使用することはできません(実際には最短経路は必要ありません)。出口の方向がわからないため、左手(右手)のアルゴリズムは使用できません。おそらく、仕様を満たす唯一の解決策は、いくつかの時間の出口が見つかることを期待して、すべての部屋でランダムな出口を選択することです...

このような迷路をよりスマートな方法で解決する方法はありますか?


2
これは宿題ですか?
MichaelHouse

2
宿題の一部です。(どういうわけかこれをマークする必要がありますか?)。すべての部屋に一意のIDがあります。
Kyrylo M

2
図面では部屋はそのように番号付けされていますか?たぶん、より高い数値への移行について何かありますか?(またはどこから始めたか
によります

2
出口のリストは常に同じ順序ですか?このように、マップを作成することができますか?私は部屋5にいて、部屋のリストの2番目の部屋に移動すると、部屋4が見つかります。これで、部屋5-> 2(部屋4)がわかりました。
MichaelHouse

4
@archer、二重投稿はあなたにもっと多くの答えを与えるかもしれません-今、質問への回答を望んでいる他の誰か異なる答えのセットを持つ2つの異なるサイトを見つけなければなりません。ルールが単一の質問を要求するのはそのためです。
サイクロプス2011

回答:


3

うーん、あなたは実際の部屋の数を知っています。したがって、データ構造を構築できます。出口のリストには部屋番号が付いていないと思います。しかし、ランダムに1つを選択すると、少なくとも、接続があることがわかります。

部屋4にいるとし、3つのランダムな出口から1つを選択します。これでシステムから、部屋6にいて出口が1つだけ残っていることがわかります。それを選択して、部屋4に戻ります。この時点で、迷路の構造に関するいくつかの情報を収集しました。ここで、別の出口を選択して、部屋5で終了します。部屋4の萌え情報(1つの出口から6、1つの出口から5)

特定の出口を選択できますか?それらに番号が付けられていますか?たとえば、部屋4で出口1を選択した場合、常に6で終わりますか?そうでなければ、少なくともあなたは可能なルートについて知ることができます。

わかりました、コメントを読んでください。そのため、出口にIDがあり、それらが部屋に静的にリンクされている場合(たとえ最初の部屋がわからない場合でも)、新しいIDを選択して、出口/部屋の接続を覚え、どの出口がすでに試行されたかを覚えておきます。それぞれの部屋を検索するまで、試していない出口を試してください。

そうすれば、それは実際には簡単です。いくつかの手順を実行すると、多少なりとも完全な接続のリストが表示されます。新しい部屋に入るときだけ、ランダムに(数歩)走り回ることができます。しかし、すべてのステップでより多くの情報が得られ、以前に訪問した部屋に入るたびに、より賢明な決定を下すことができます(テストされていない出口がないため、たとえば4に戻ったときに、行き止まりの部屋6を再度テストしないでください)。

編集 アイデアは、最初にランダムなステップを作成し、私が説明したように見つけた情報をログに記録することです(ダンの説明はより簡潔です)。不明な出口がない部屋にいる場合は、既知のパスファインダーを使用して、まだ探索していない出口がある部屋への最短の道を見つけることができます。

100%確実ではありませんが、Peter Norvigが彼の本「Artificial Intelligence:A Modern Approach」で同様の問題(Wumpus迷路)について書いたと思います。私が正しく覚えているとしたら、それは経路を見つけることではなく、システムが隣接する部屋について得ることができるいくつかの情報に関する意思決定についてでした。

編集:

いいえ、ランダムだけではありません。すでに試行された出口を追跡することにより、不要な冗長性を取り除きます。実際には、ランダムに選択する必要さえありません。

部屋4から始めると仮定します。取得する情報:3つの出口A、B、C常に未使用の出口を最初に選択します。部屋Aから始めます。部屋6で終わります。部屋6で4A => 6(および使用済み)を覚えていると、情報1の出口Aを取得します。ここでも、最初の未使用(この場合は出口のみ)を選択します。部屋に戻ります。 6A => 4を知るため(そして、部屋6にそれ以上の出口がない場合)、次の出口Bを選択して、部屋5に到達します...

遅かれ早かれ、すべての部屋をそのように検索することになります。しかし、体系的な問題では。

経路探索が必要になる唯一の理由は、すべての出口がすでに探索されている部屋にいるときです。次に、未探索の出口のある次の部屋への直接の道を見つけて、自分の検索を手に入れたいと思うでしょう。したがって、主なトリックは、どの出口がどの部屋につながっているのかを知ることではなく(これは役立つかもしれません)、まだ試みられていない出口を追跡することです。

このようにして、たとえば、常に円を描くことを避けることができます。これは、純粋にランダムなアプローチのリスクになります。

あなたの例の迷路では、これはほとんど問題にならないでしょう。しかし、多くの部屋と接続があり、トリッキーな円形に配置された部屋がある大きな迷路では、このシステムは、できるだけ少ない試行で出口を見つけることを保証します。

(おそらく、ゲーマーでByte56が登場したのと同じシステムだと思います)


はい、部屋の外の特定の出口(ドア)を選択できます。一意のIDがあります。だから、あなたの提案は、最初に完全な迷路を探索し、次に既知のアルゴリズムを使用することですか?
Kyrylo M

私はプログラムを書き始め、新しい質問を得ました...最初に、すべての部屋を探索して、すべてのつながりを持つ構造を構築しました。2番目のステップは、パスを見つけることです。しかし、すべての接続が追加されると、構築を停止します。しかし、それでも私はとにかく出口に到達します...したがって、これはランダムな方向アルゴリズムを選択するだけです...
Kyrylo M

10

あなたはおそらく他の答えから要点を理解していると思いますが、それは楽しい質問であり、小さなPythonコーディングを行うように感じました。これが私のオブジェクト指向アプローチです。インデントはスコープを定義します。

グラフ表現

グラフは、キー、ルームディクショナリであるキー、値ディクショナリとして簡単に保存できます。値は、それにつながるルームの配列です。

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

エージェントインターフェース

最初に、エージェントが環境からどのような情報を学習できるか、およびエージェントが実行できる操作について考える必要があります。これにより、アルゴリズムについて簡単に考えることができます。

この場合、エージェントはそれがいる部屋のIDを環境に照会できる必要があります。それは、それがいる部屋のドアの数を取得できるはずですこれは部屋のIDではないことに注意してください) doors lead to!)そして、彼はドアインデックスを指定することでドアを通過できるはずです。エージェントが知っている他のすべては、エージェント自体によって理解する必要があります。

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回しかバックトラックしません。コードを簡潔にするために、例外処理を行わないようにしました。私のPythonへのコメントは感謝します:)


記念すべき答え!悲しいことに2つの答えすることはできません:(私はすでにC#でプログラムを書き、一般的にはほぼ同じアイデアを使用後戻りするために息の深さの検索アルゴリズムを使用した。。
Kyrylo M

4

基本的に、接続されたすべての部屋が2つの未知の通路によって接続されている方向グラフがあります。あなたがノード1から始めてドアABリードアウトするとします。各ドアの向こうに何があるかわからないので、ドアを選ぶだけAです。あなたは部屋に行く2のドアを持っている、CD、とE。ドアAが部屋から部屋1へとつながっている2ことがわかりましたが、戻る方法がわからないので、ランダムにドアを選びますC。部屋に戻ります1!これで、部屋1との間を移動する方法がわかりました2。出口が見つかるまで、最も近い未知のドアを通って探索を続けます!


4

部屋は出口リストで常に同じ順序になっているので、出口を探しながら部屋の簡単なマップを作成できます。

私の疑似コードはややジャワっぽいですが、申し訳ありませんが、最近このコードを頻繁に使用しています。

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()ルームへの共通ノードパスファインダーの1つを使用する必要があります。うまくいけば、何かを始めるのに十分なほど明確です。


@ Byte56-失ったチェックマークの2/3を占める賛成票を次に示します。:)
サイクロプス2011

2

私はあなたの要件についてあまり明確ではありませんが、以下が許可されている場合は、アルゴリズムに従うのは簡単かもしれません。おそらくそこにバグがあります。特に再帰関数を使用しているためです。また、ドアが元の部屋に通じているかどうかをチェックしますが、3つの部屋の円形パスがどのように処理されるかわかりません。これらの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 2011

3
それで、あなたは前の部屋を知ることが許されていないと言っているのですか?それともあなたは前の部屋を知らないのですか?上記のコードは、以前の部屋を追跡します。あなたが知ることが許可されていない場合、迷路を「x」回試行するためにランダムに繰り返す以外に有効な解決策はないと思います。出口が見つからない場合は、迷路は解決できないと考えることができます。
Doug.McFarlane 2011

1
"知りません"。もう一度コードを見ました。2番目の問題は、再帰を使用すると問題が発生することです。あなたがそのような迷路の中にいると想像してみてください。出口を見つけるために再帰アルゴリズムをどのように使用しますか?
Kyrylo M 2011

また、部屋6から始めるとどうなりますか?curRoom.doors <= 1、関数はすぐに戻ります。行き止まりにあることはわかっていますが、迷路全体をすでに探索していると考えています。
dlras2 2011

これは近いですが、グラフに2より大きい長さのサイクルがある場合、スタックを爆破します。
11

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

ありがとう、修正しました!ええ、今私が要件を見ると、問題は少し怪しいようです。バックトラックできない場合は、ランダムウォークが最適です。
11
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.