Risolvere labirinto senza capacità di ritorno


11

Devo scrivere un programma che risolva il labirinto. Il labirinto ha una struttura grafica, in cui ciascun nodo - una certa stanza e bordi - esce ad altre stanze:

inserisci qui la descrizione dell'immagine

Specifica:

  • Partiamo da una stanza a caso.
  • Il labirinto ha vicoli ciechi, 0 o poche uscite.
  • Non sappiamo nulla di tutto il labirinto, solo il numero della stanza attuale e l'elenco delle porte da esso.
  • Quando entriamo nella nuova stanza non sappiamo da dove veniamo (quale porta della stanza attuale ci riporta alla stanza precedente).
  • Possiamo riconoscere quando raggiungiamo l'uscita.
  • Ad ogni passo ci spostiamo dalla stanza corrente ad alcune porte disponibili da essa.
  • Se ad esempio siamo nella stanza 6, non possiamo semplicemente saltare per iniziare. È come il movimento di un robot.
  • Sappiamo esattamente l'ID della stanza attuale. Sappiamo ID di ogni porta nella stanza corrente (non in tutti i labirinti).
  • Controlliamo il robot.

Ho guardato tutti gli algoritmi conosciuti, ma tutti richiedono almeno un'ulteriore capacità di tornare alla stanza precedente. Secondo le specifiche non possiamo usare alcun algoritmo che cerchi il percorso più breve (in realtà, non ho bisogno del più breve), perché conosciamo solo la stanza attuale. Non possiamo usare la mano sinistra (destra) seguendo gli algoritmi, perché non conosciamo la direzione delle uscite. Probabilmente, l'unica soluzione che soddisfa le specifiche è scegliere l'uscita casuale in ogni stanza nella speranza che venga trovata un'uscita temporale ...

Qualche suggerimento su come risolvere un tale labirinto in modo più intelligente?


2
Sono compiti?
MichaelHouse

2
Fa parte dei compiti. (Devo contrassegnarlo in qualche modo?). Tutte le camere hanno un ID univoco.
Kyrylo M,

2
Le stanze sono numerate come tali nel disegno? Forse qualcosa sul passaggio a numeri più alti? (o inferiore a seconda di dove hai iniziato)
MichaelHouse

2
Gli elenchi delle uscite sono sempre nello stesso ordine? Come in, potremmo creare una mappa mentre andiamo? Sono nella stanza 5 e vado nella seconda stanza nell'elenco delle stanze, trovo la stanza 4. Quindi ora conosco quella stanza 5-> 2 (stanza 4).
MichaelHouse

4
@archer, mentre il doppio post potrebbe darti più risposte - ora, qualcun altro che vuole una risposta alla domanda, deve trovare due siti diversi, con diversi set di risposte. Ecco perché le regole richiedono singole domande, per facilitare la ricerca di altre persone.
Ciclope,

Risposte:


3

Hmm, conosci il numero della stanza reale. Quindi puoi costruire una struttura di dati. Immagino che l'elenco delle uscite non contenga i numeri delle camere. Ma dopo averne scelto uno a caso, sai almeno che esiste una connessione.

Supponi di essere nella stanza 4 e scegli una delle tre uscite casuali. Ora il sistema ti dice che sei nella stanza 6 e ti rimane solo un'uscita. Lo scegli e torni nella stanza 4. A questo punto hai raccolto alcune informazioni sulla struttura del labirinto. Ora scegli un'altra uscita e termina nella stanza 5. Informazioni sulla stanza 4 (un'uscita su 6, un'uscita su 5)

Puoi scegliere un'uscita specifica? sono numerati, ad esempio se nella stanza 4 scegli l'uscita uno finisci sempre con 6? Altrimenti puoi almeno scoprire i possibili percorsi.

Ok, leggi il tuo commento. Quindi se le uscite hanno ID e quelle sono staticamente collegate a una stanza (anche se non sai quale per iniziare) basta sceglierne una nuova e ricordare le connessioni di uscita / stanza e ricordare quale uscita è già stata provata. Prova le uscite non provate fino a quando non hai cercato ogni singola stanza.

In questo modo è in realtà semplice. Dopo alcuni passaggi dovresti avere un elenco di connessioni più o meno completo. Solo quando entri in una nuova stanza puoi (per alcuni passaggi) correre in modo casuale. Ma ad ogni passo ottieni maggiori informazioni e ogni volta che entri in una stanza visitata in precedenza, puoi prendere una decisione più intelligente (non testare di nuovo la camera 6 senza uscita, ad esempio quando torni a 4, poiché non ha uscite non testate).

Modifica L'idea è di fare prima passi casuali e registrare le informazioni che trovi come ho descritto (la descrizione di Dan è più concisa). Se ti trovi in ​​una stanza senza uscite sconosciute, puoi utilizzare qualsiasi pathfinder noto per trovare il modo più breve per una stanza con uscite che non hai ancora esplorato.

Non sicuro al 100%, ma penso che Peter Norvig abbia scritto di un problema simile (il labirinto di Wumpus) nel suo libro "Intelligenza artificiale: un approccio moderno". Tuttavia, se ricordo bene, si trattava meno di trovare percorsi e di prendere decisioni su alcune informazioni che il sistema poteva ottenere sulle stanze vicine.

Modificare:

No, non è solo casuale. Tenendo traccia delle uscite già provate si rimuove la ridondanza non necessaria. In realtà non è nemmeno necessario scegliere a caso.

Supponi di iniziare nella stanza 4. Informazioni che ottieni: 3 uscite A, B, C Scegli sempre la prima uscita ormai inutilizzata. Quindi inizia con A. Termina nella stanza 6. Ora ricordi 4A => 6 (e usato) nella stanza 6 ottieni l'informazione 1 uscita A. Ancora una volta scegli la prima inutilizzata (e in questo caso solo esci) Torna nella stanza per sapere 6A => 4 (e non uscire più nella stanza 6) Ora scegli la prossima uscita B e raggiungi la stanza 5 ...

Prima o poi avrai cercato in tutte le stanze in quel modo. Ma in una questione sistematica.

L'unica ragione per ciò di cui avresti bisogno per trovare un percorso è quando ti trovi in ​​una stanza in cui tutte le uscite sono già esplorate. Quindi vorrai trovare un modo diretto per la prossima stanza con uscite inesplorate per andare avanti con la tua ricerca. Quindi il trucco principale è meno sapere quale uscita conduce a quale stanza (anche se questo può essere utile) ma tenere traccia delle uscite non ancora provate.

In questo modo, ad esempio, puoi evitare di correre in cerchio tutto il tempo, il che sarebbe un rischio per un approccio puramente casuale.

Nel tuo labirinto di esempio questo molto probabilmente non importerebbe molto. Ma in un grande labirinto con molte stanze e connessioni e forse complicate stanze circolari disposte questo sistema garantisce che troverai l'uscita con il minor numero di prove possibile.

(Penso che sia probabilmente lo stesso sistema creato da Byte56 in Gamers)


Sì, posso scegliere l'uscita specifica (porta) fuori dalla stanza. Hanno ID univoci. Quindi, il tuo suggerimento è di esplorare prima l'intero labirinto e quindi utilizzare un algoritmo noto?
Kyrylo M,

Ho iniziato a scrivere il programma e ho ricevuto una nuova domanda ... Per prima cosa esploro tutte le stanze per costruire una struttura con tutte le connessioni. Il secondo passo sarà trovare il percorso. Ma smetterò di costruire quando verranno aggiunte tutte le connessioni. Ma in questo modo raggiungerò comunque l'uscita ... Quindi questo è solo un algoritmo di direzione casuale scelto ...
Kyrylo M

10

Riconosco che probabilmente hai ottenuto l'essenza di altre risposte, ma è stata una domanda divertente e mi è venuta voglia di scrivere un po 'di codice Python. Questo è il mio approccio orientato agli oggetti. Il rientro definisce l'ambito.

Rappresentazione grafica

Il grafico può essere facilmente memorizzato come chiave, dizionario dei valori in cui la chiave è l'id della room e il valore è una matrice delle room a cui conduce.

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

Interfaccia agente

Innanzitutto dovremmo pensare a quali informazioni l'agente dovrebbe essere in grado di apprendere dall'ambiente e alle operazioni che dovrebbe essere in grado di eseguire. Ciò semplifica la riflessione sull'algoritmo.

In questo caso l'agente dovrebbe essere in grado di interrogare l'ambiente per l'id della stanza in cui si trova, dovrebbe essere in grado di ottenere un conteggio delle porte nella stanza in cui si trova ( nota che questo non è l'id delle stanze le porte portano a! ) e dovrebbe essere in grado di muoversi attraverso una porta specificando un indice di porta. Qualsiasi altra cosa che un agente conosce deve essere capito dall'agente stesso.

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

Conoscenza dell'agente

Quando l'agente entra per la prima volta nella mappa, conosce solo la quantità di porte nella stanza e l'id della stanza in cui si trova attualmente. Avevo bisogno di creare una struttura che memorizzasse le informazioni che l'agente aveva appreso, ad esempio quali porte non erano state attraverso, e dove le porte conducono a ciò è stato attraversato.

Questa classe rappresenta le informazioni su una singola stanza. Ho scelto di memorizzare le porte non visitate come a sete le porte visitate come a dictionary, dove la chiave è l'ID della porta e il valore è l'id della stanza a cui conduce.

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

Algoritmo dell'agente

  • Ogni volta che l'agente entra in una stanza, cerca nel suo dizionario di conoscenza informazioni sulla stanza. Se non ci sono voci per questa stanza, ne crea una nuova RoomKnowledgee la aggiunge al dizionario della conoscenza.

  • Verifica se la stanza corrente è la stanza target, in tal caso poi ritorna.

  • Se ci sono porte in questa stanza che non abbiamo visitato, passiamo attraverso la porta e riponiamo dove conduce. Quindi continuiamo il ciclo.

  • Se non c'erano porte non visitate, torniamo indietro attraverso le stanze che abbiamo visitato per trovarne una con porte non visitate.

La Agentclasse eredita dalla AgentInterfaceclasse.

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)

Funzioni di supporto

Ho dovuto scrivere una funzione che avrebbe trovato una chiave in un dizionario con un valore, dato che quando facciamo il backtrack conosciamo l'id della stanza che stiamo cercando di raggiungere, ma non quale porta usare per raggiungerla.

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

analisi

Ho testato tutte le combinazioni di posizione iniziale / finale nella mappa indicata sopra. Per ogni combinazione stampa le stanze visitate.

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

Appunti

Il backtracking non è molto efficiente - nel peggiore dei casi potrebbe passare attraverso ogni stanza per raggiungere una stanza adiacente, ma il backtracking è abbastanza raro - nei test sopra riportati fa solo tre passi indietro. Ho evitato di inserire la gestione delle eccezioni per mantenere il codice conciso. Tutti i commenti sul mio Python sono stati apprezzati :)


Risposta monumentale! Purtroppo non possono essere due risposte :( Ho già scritto il programma in C # e in genere ho usato quasi le stesse idee. Per il backtracking è stato utilizzato l'algoritmo di ricerca della profondità del respiro.
Kyrylo M

4

In sostanza, hai un grafico direzionale, in cui ogni stanza collegata è collegata da due passaggi sconosciuti, uno in entrambe le direzioni. Di 'che inizi nel nodo 1, e le porte Aed Besci. Non sai cosa c'è dietro ogni porta, quindi scegli semplicemente la porta A. Si arriva a camera 2, che ha le porte C, De E. Ora sai che la porta Aconduce da una stanza 1all'altra 2, ma non sai come tornare indietro, quindi scegli casualmente la porta C. Torni in camera 1! Ora sai come spostarti tra le stanze 1e 2. Continua ad esplorare attraverso la porta sconosciuta più vicina fino a trovare l'uscita!


4

Dato che le stanze sono sempre nello stesso ordine negli elenchi delle uscite, possiamo fare una rapida mappa delle stanze mentre cerchiamo un'uscita.

Il mio pseudo codice è in qualche modo Javaish, mi dispiace, lo sto usando molto ultimamente.

Unvisited_Rooms è una hashmap che contiene un ID stanza e un elenco di indici delle stanze non mappate O un array 2d, qualunque cosa funzioni.

Immagino che l'algoritmo potrebbe andare in questo modo:

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)

Dovrai usare uno dei comuni finder di percorsi dei nodi nelle stanze PathTo () attraverso la "mappa". Speriamo che sia abbastanza chiaro per iniziare qualcosa.


Ecco un voto, @ Byte56 - che compensa i 2/3 del segno di spunta che hai perso. :)
Ciclope,

2

Non sono troppo chiaro sulle tue esigenze, ma se è consentito quanto segue, potrebbe essere un algoritmo semplice da seguire. Probabilmente un bug lì dentro, soprattutto perché utilizza una funzione ricorsiva. Inoltre, controlla se una porta conduce alla stanza da cui provieni, ma non so come possa essere gestito un percorso circolare di tre stanze, potrebbe continuare a girare per sempre in quelle tre stanze. Potrebbe essere necessario aggiungere una cronologia per assicurarsi che nessuna stanza venga controllata due volte. Ma leggendo la tua descrizione potrebbe non essere consentito.

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

[Modifica] Aggiunto "id" al controllo precedente e aggiornato per rendere più orientato agli oggetti.


1
Non so ad ogni passo da dove vengo. Quindi, questo algoritmo non risolve il compito. Ma grazie per averci provato.
Kyrylo M,

3
Quindi stai dicendo che NON SEI PERMESSO di conoscere la stanza precedente? O che NON conosci la stanza precedente? Il codice sopra ti tiene traccia delle stanze precedenti. Se non ti è permesso sapere, non penso che esista una soluzione valida se non quella di iterare casualmente il labirinto per il numero di tentativi "x" e se non riesci a trovare un'uscita, puoi presumere che il labirinto sia irrisolvibile .
Doug.McFarlane,

1
"Non lo so". Ho cercato di nuovo il codice. Il secondo problema è che l'utilizzo della ricorsione è problematico. Immagina di essere dentro un tale labirinto. Come useresti l'algoritmo ricorsivo per trovare l'uscita?
Kyrylo M

Inoltre, cosa succede se inizi nella stanza 6? curRoom.doors <= 1, quindi la funzione ritorna immediatamente, sapendo che è in un vicolo cieco, ma pensando che ha già esplorato l'intero labirinto.
dlras2,

Questo è vicino, ma farà esplodere la pila se ci sono cicli nel grafico di lunghezza maggiore di due.
munifico

1

La risposta breve è una ricerca approfondita con backtracking. Se preferisci, puoi fare la prima cosa, ma il tuo piccolo robot farà molto di più camminando avanti e indietro.

Più concretamente, supponiamo che ci vengano dati:

// 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();

Per trovare l'uscita, abbiamo solo bisogno di:

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());
  }
}

Chiama escape()con l'ID della stanza iniziale e questo sposterà il robot all'uscita (chiamando moveTo()).


Penso che escape(int startingRoom)dovrei chiamareescape(Stack<int> path)
CiscoIPPhone

1
Penso che if (path.contains(door)) continue;violi i suoi requisiti: l'agente in realtà non sa se una porta riconduce a una stanza in cui è già stato a meno che non passi attraverso la porta.
CiscoIPPhone

Grazie, risolto! Sì, ora che guardo i requisiti, il problema sembra un po 'sospetto. Se non riesci a tornare indietro, il meglio che puoi sperare è una passeggiata casuale.
munifico
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.