web-dev-qa-db-fra.com

Théorie de la programmation: résoudre un labyrinthe

Quelles sont les façons possibles de résoudre un labyrinthe?
J'ai deux idées, mais je pense qu'elles ne sont pas très élégantes.

Situation de base: Nous avons une matrice, et les éléments de cette matrice sont ordonnés de manière à représenter un labyrinthe, avec une entrée et une sortie.

Ma première idée a été d'envoyer un robot à travers le labyrinthe, en suivant un côté, jusqu'à ce qu'il soit hors du labyrinthe. Je pense que c'est une solution très lente.

Le second passe à travers chaque élément successif marqué d'un 1, vérifie où il peut aller (haut, droit, bas, gauche) choisit une voie et y continue son chemin. C'est encore plus lent que le premier.

Bien sûr, c'est un peu plus rapide si je fais les deux robots multi-thread à chaque jonction, mais ce n'est pas non plus la meilleure façon.

Il doit y avoir de meilleures solutions pour envoyer un bot à travers un labyrinthe.

MODIFIER
Premièrement: Merci pour les bonnes réponses!

La deuxième partie de ma question est: Que faire dans le cas où nous avons un graphe multidimensionnel? Y a-t-il des pratiques spéciales pour cela, ou la réponse de Justin L. est-elle également utilisable pour cela?
Je pense que ce n'est pas le meilleur moyen pour cette affaire.

La troisième question:
Lequel de ces algorithmes de résolution de labyrinthe est/est le plus rapide? (Purement hypothétique)

67
Max Ruf

Vous pouvez considérer votre labyrinthe comme un arbre.

 A 
/\ 
/\ 
 BC 
/\/\ 
 DEFG 
/\\
 HIJ 
/\ 
 LM 
/\ 
 ** O 
 
 (Qui pourrait éventuellement représenter) 
 
 START 
 + + --- + --- + 
 | A C G | 
 + --- + + + + 
 | D B | F | J | 
 + --- + --- + + --- + --- + 
 | L H E I | 
 + --- + + --- + --- + 
 | M O | 
 + + --- + 
 FINITION 
 
 (En ignorant l'ordre gauche-droite sur l'arbre) 

Où chaque nœud est une jonction de chemins. D, I, J, L et O sont des impasses et ** est l'objectif. Bien sûr, dans votre arbre réel, chaque nœud a la possibilité d'avoir autant d'enfants trois.

Votre objectif est maintenant simplement de trouver les nœuds à traverser pour trouver l'arrivée. N'importe quel algorithme de recherche d'arbre fera l'affaire.

En regardant l'arbre, il est assez facile de voir votre bonne solution en "remontant" simplement à partir de ** dans la partie la plus profonde de l'arbre:

A B E H M **

Notez que cette approche devient seulement légèrement plus compliquée lorsque vous avez des "boucles" dans votre labyrinthe (c'est-à-dire, quand c'est possible, sans retour en arrière, vous rentrez dans un passage que vous avez déjà traversé ). Consultez les commentaires pour une bonne solution.

Maintenant, regardons votre première solution que vous avez mentionnée, appliquée à cet arbre.

Votre première solution est essentiellement une Recherche en profondeur d'abord , ce qui n'est vraiment pas si mal. C'est en fait une assez bonne recherche récursive. Fondamentalement, il dit: "Prenez toujours l'approche la plus à droite en premier. S'il n'y a rien, revenez en arrière jusqu'au premier endroit, vous pouvez aller tout droit ou à gauche, puis répéter.

Une recherche en profondeur recherche l'arborescence ci-dessus dans cet ordre:

A B D (backtrack) E H L (backtrack) M ** (backtrack) O (backtrack thrice) I
(backtrack thrice) C F (backtrack) G J

Notez que vous pouvez vous arrêter dès que vous trouvez le **.

Cependant, lorsque vous codez réellement une recherche en profondeur, l'utilisation de la programmation récursive rend tout beaucoup plus facile. Même les méthodes itératives fonctionnent également, et vous n'avez jamais à programmer explicitement comment revenir en arrière. Consultez l'article lié pour les implémentations.

Une autre façon de rechercher un arbre est la solution Breadth-First , qui recherche à travers les arbres par la profondeur. Il recherche dans l'arborescence ci-dessus dans cet ordre:

A (next level) B C (next level) D E F G (next level)
H I J (next level) L M (next level) ** O

Notez qu'en raison de la nature d'un labyrinthe, la largeur en premier a une quantité moyenne de nœuds beaucoup plus élevée qu'il vérifie. La largeur en premier est facilement mise en œuvre en ayant une file d'attente de chemins à rechercher, et chaque itération en faisant sortir un chemin d'une file d'attente, en "l'explosant" en obtenant tous les chemins dans lesquels il peut se transformer après une étape et en mettant ces nouveaux chemins à la fin de la file d'attente. Il n'y a pas de commandes explicites de "niveau supérieur" à coder, et celles-ci étaient juste là pour aider à la compréhension.

En fait, il y a un tout liste étendue de façons de rechercher un arbre . Je viens de mentionner les deux moyens les plus simples et les plus directs.

Si votre labyrinthe est très, très long et profond, et a des boucles et des fous, et est compliqué, je suggère le A * = algorithme, qui est l'algorithme de recherche de cheminement standard de l'industrie qui combine une recherche en largeur d'abord avec une heuristique ... un peu comme une "recherche en largeur intelligente en premier".

Cela fonctionne essentiellement comme ceci:

  1. Mettez un chemin dans une file d'attente (le chemin où vous ne faites qu'un pas directement dans le labyrinthe). Un chemin a un "poids" donné par sa longueur actuelle + sa distance en ligne droite de la fin (qui peut être calculée mathématiquement)
  2. Pop le chemin avec le poids le plus bas de la file d'attente.
  3. "Explosez" le chemin dans chaque chemin qu'il pourrait être après une étape. (c.-à-d., si votre chemin est Droite Gauche Gauche Droite, alors vos chemins éclatés sont R L L R R et R L L R L, sans inclure ceux illégaux qui traversent les murs)
  4. Si l'un de ces chemins a le but, alors la victoire! Autrement:
  5. Calculez les poids des chemins éclatés et remettez-les tous dans la file d'attente (sans inclure le chemin d'origine)
  6. Triez la file d'attente par poids, le plus bas en premier. Répétez ensuite à partir de l'étape 2

Et c'est A * , que je présente spécialement mis en évidence car c'est plus ou moins l'algorithme de pathfinding standard pour all applications de recherche de chemin, y compris le déplacement d'un bord de la carte à un autre tout en évitant les sentiers hors route ou les montagnes, etc. Il fonctionne si bien car il utilise une heuristique de distance la plus courte possible, ce qui lui donne son "intelligence". A * est tellement polyvalent car, étant donné tout problème, si vous disposez d'une heuristique de distance la plus courte possible (la nôtre est facile - la ligne droite), vous pouvez l'appliquer.

[~ # ~] mais [~ # ~] il est très utile de noter que A * est pas votre seule option.

En fait, la catégorie wikipedia d'algorithmes de traversée d'arbres en répertorie 97 à elle seule! (le meilleur sera toujours sur cette page lié plus tôt)

Désolé pour la longueur = P (j'ai tendance à divaguer)

158
Justin L.

Il existe de nombreux algorithmes de résolution de labyrinthes:

http://en.wikipedia.org/wiki/Maze_solving_algorithm

http://www.astrolog.org/labyrnth/algrithm.htm#solve

Pour un robot, algorithme de Tremaux semble prometteur.

13
willoller

Une approche intéressante, du moins je l'ai trouvée intéressante, consiste à utiliser des automates cellulaires. Bref, une cellule "espace" entourée de 3 cellules "mur" se transforme en cellule "mur". À la fin, les seules cellules spatiales restantes sont celles sur le chemin de la sortie.

Si vous regardez l'arbre que Justin a mis dans sa réponse, vous pouvez voir que les nœuds foliaires ont 3 murs. Taillez l'arbre jusqu'à ce que vous ayez un chemin.

11
Andre Artus

C'est l'un de mes algorithmes préférés jamais ...

1) Move forward
2) Are you at a wall?
2a) If yes, turn left
3) Are you at the finish?
3a) If no, go to 1
3b) If yes, solved
4
Nate Noonen

Que diriez-vous de créer un graphique à partir de votre matrice et d'utiliser l'algorithme de recherche en premier, la recherche en profondeur ou l'algorithme Dijkstras?

4
Kungi

Ceci est une représentation très simple pour simuler un labyrinthe en C++ :)

#ifndef vAlgorithms_Interview_graph_maze_better_h
#define vAlgorithms_Interview_graph_maze_better_h

static const int kMaxRows = 100;
static const int kMaxColumns = 100;

class MazeSolver
    {
private:
    char m_matrix[kMaxRows][kMaxColumns]; //matrix representation of graph
    int rows, cols; //actual rows and columns

    bool m_exit_found;
    int m_exit_row, m_exit_col;
    int m_entrance_row, m_entrance_col;

    struct square //abstraction for data stored in every verex
        {
        pair<int, int> m_coord; //x and y co-ordinates of the matrix
        square* m_parent; //to trace the path backwards

        square() : m_parent(0) {}
        };

    queue<square*> Q;

public:
    MazeSolver(const char* filename)
        : m_exit_found(false)
        , m_exit_row(0)
        , m_exit_col(0)
        , m_entrance_row(0)
        , m_entrance_col(0)
        {
        ifstream file;
        file.open(filename);

        if(!file)
            {
            cout << "could not open the file" << endl << flush;
            // in real world, put this in second phase constructor
            }
        init_matrix(file);
        }
    ~MazeSolver()
        {
        }
    void solve_maze()
        {
        //we will basically use BFS: keep pushing squares on q, visit all 4 neighbors and see
        //which way can we proceed depending on obstacle(wall)

        square* s = new square();
        s->m_coord = make_pair(m_entrance_row, m_entrance_col);

        Q.Push(s);

        while(!m_exit_found && !Q.empty())
            {
            s = Q.front();
            Q.pop();

            int x = s->m_coord.first;
            int y = s->m_coord.second;
            //check if this square is an exit cell
            if(x == m_exit_row && y == m_exit_col)
                {
                m_matrix[x][y] = '>'; // end of the path
                m_exit_found = true;
                //todo: try breaking? no= queue wont empty
                }
            else
                {
                //try walking all 4 neighbors and select best path
                //NOTE: Since we check all 4 neighbors simultaneously,
                //      the path will be the shortest path
                walk_path(x-1, y, s);
                walk_path(x+1, y, s);
                walk_path(x, y-1, s);
                walk_path(x, y+1, s);
                }
            } /* end while */

        clear_maze(); //unset all previously marked visited shit

        //put the traversed path in maze for printing
        while(s->m_parent)
            {
            m_matrix[s->m_coord.first][s->m_coord.second] = '-';
            s = s->m_parent;
            } /* end while */
        }

    void print()
        {
        for(int i=0; i<rows; i++)
            {
            for(int j=0; j<cols; j++)
                cout << m_matrix[i][j];
            cout << endl << flush;
            }
        }

private:
    void init_matrix(ifstream& file)
        {
        //read the contents line-wise
        string line;
        int row=0;
        while(!file.eof())
            {
            std::getline(file, line);
            for(int i=0; i<line.size(); i++)
                {
                m_matrix[row][i] = line[i];
                }
            row++;
            if(line.size() > 0)
                {
                cols = line.size();
                }
            } /* end while */
        rows = row - 1;

        find_exit_and_entry();
        m_exit_found = false;
        }

    //find and mark ramp and exit points
    void find_exit_and_entry()
        {
        for(int i=0; i<rows; i++)
            {
            if(m_matrix[i][cols-1] == ' ')
                {
                m_exit_row = i;
                m_exit_col = cols - 1;
                }
            if(m_matrix[i][0] == ' ')
                {
                m_entrance_row = i;
                m_entrance_col = 0;
                }
            } /* end for */
        //mark entry and exit for testing
        m_matrix[m_entrance_row][m_entrance_col] = 's';
        m_matrix[m_exit_row][m_exit_col] = 'e';
        }

    void clear_maze()
        {
        for(int x=0; x<rows; x++)
            for(int y=0; y<cols; y++)
                if(m_matrix[x][y] == '-')
                    m_matrix[x][y] = ' ';
        }
        // Take a square, see if it's the exit. If not, 
        // Push it onto the queue so its (possible) pathways
        // are checked.
    void walk_path(int x, int y, square* parent)
        {
        if(m_exit_found) return;
        if(x==m_exit_row && y==m_exit_col)
            {
            m_matrix[x][y] = '>';
            m_exit_found = true;
            }
        else
            {
            if(can_walk_at(x, y))
                {
                //tag this cell as visited
                m_matrix[x][y] = '-';

                cout << "can walk = " << x << ", " << y << endl << flush;

                //add to queue
                square* s = new square();
                s->m_parent = parent;
                s->m_coord = make_pair(x, y);
                Q.Push(s);
                }
            }
        }

    bool can_walk_at(int x, int y)
        {
        bool oob = is_out_of_bounds(x, y);
        bool visited = m_matrix[x][y] == '-';
        bool walled = m_matrix[x][y] == '#';

        return ( !oob && !visited && !walled);
        }
    bool is_out_of_bounds(int x, int y)
        {
        if(x<0 || x > rows || y<0 || y>cols)
            return true;
        return false;
        }
    };


void run_test_graph_maze_better()
        {
        MazeSolver m("/Users/vshakya/Dropbox/private/graph/maze.txt");
        m.print();
        m.solve_maze();
        m.print();
        }


#endif
3
Viren

Juste une idée. Pourquoi ne pas y jeter des robots à la mode de Monte Carlo. Appelons la première génération de bots gen0. Nous ne gardons que les bots de gen0 qui ont des routes continues de cette façon:
- du début à un certain point
ou -de quelque point jusqu'à la fin

Nous exécutons une nouvelle génération de robots dans de nouveaux points aléatoires, puis nous essayons de connecter les routes des robots de génération 1 à celles de génération0 et de voir si nous obtenons une route continue du début à la fin.

Donc, pour genn, nous essayons de nous connecter avec les robots sous forme gen0, gen1, ..., genn-1.

Bien sûr, une génération ne dure qu'un temps limité possible.

Je ne sais pas si le teint de l'algorithme se révélera pratique pour les petits ensembles de données.
L'algorithme suppose également que nous connaissons les points de départ et d'arrivée.


quelques bons sites d'idées:
http://citeseerx.ist.psu.edu/
http://arxiv.org/

2
19021programmer

Si le robot peut garder une trace de son emplacement, afin qu'il sache s'il s'est déjà rendu à un emplacement, la recherche en profondeur d'abord est l'algorithme évident. Vous pouvez montrer par un argument contradictoire qu'il n'est pas possible d'obtenir de meilleures performances dans le pire des cas que la recherche en profondeur d'abord.

Si vous avez à votre disposition des techniques qui ne peuvent pas être implémentées par des robots, la recherche en largeur peut être meilleure pour de nombreux labyrinthes, tout comme l'algorithme de Dijkstra pour trouver le chemin le plus court dans un graphique.

1
Norman Ramsey

J'ai eu un problème similaire dans l'un de mes cours universitaires. Sci. cours. La solution que nous avons trouvée était de suivre le mur de gauche (le mur de droite fonctionnera aussi bien). Voici un pseudocode

While Not At End
    If Square To Left is open,
        Rotate Left
        Go Forward
    Else
        Rotate Right
    End If
Wend

C'est essentiellement ça. La partie complexe consiste à garder une trace de la direction de votre orientation et à déterminer la position de la grille sur votre gauche en fonction de cette direction. Cela a fonctionné pour tous les cas de test auxquels je me suis opposé. Assez intéressant, la solution Professeurs allait dans le sens de:

While Not At End
    If Can Go North
        Go North
    ElseIf Can Go East
        Go East
    ElseIf Can Go South
        Go South
    ElseIf Can Go West 
        Go West
    EndIf
Wend

Ce qui fonctionnera bien pour la plupart des labyrinthes simples, mais échoue sur un labyrinthe qui ressemble à ceci:

SXXXXXXXXXXXXX
   X         X
   X         X
   X         X
 XXX         X
 X X         X
 X XXXXXXXXXXX     XXXE
 X                 X
 XXXXXXXXXXXXXXXXXXX

S et E étant le début et la fin.

Avec tout ce qui ne suit pas le mur, vous finissez par devoir garder une liste des endroits où vous avez été, afin de pouvoir revenir en arrière si nécessaire, lorsque vous tombez dans une impasse, et pour ne pas vous faire prendre. en boucle. Si vous suivez le mur, il n'est pas nécessaire de savoir où vous êtes allé. Bien que vous ne trouviez pas le chemin le plus optimal à travers le labyrinthe, vous le traverserez toujours.

1
Kibbee

il existe de nombreux algorithmes et de nombreux paramètres différents qui spécifient quel algorithme est le meilleur. ce n'est qu'une idée sur un cadre intéressant:

supposons que vous ayez les propriétés suivantes ...

  • vous déplacez un robot et vous voulez minimiser son mouvement, pas son utilisation CPU.
  • ce robot peut soit inspecter uniquement ses cellules voisines, soit regarder le long couloirs voir ou ne pas voir les croisements.
  • il a GPS .
  • il connaît les coordonnées de sa destination.

alors vous pouvez concevoir un A.I. lequel...

  • dessine une carte - chaque fois qu'il reçoit de nouvelles informations sur le labyrinthe.
  • calcule les longueurs minimales connues cheminentre toutes positions non observées (et elle-même et la destination).
  • peut prioriser les positions non observées pour l'inspection en fonction de structures environnantes. (s'il est impossible de rejoindre la destination de toute façon ...)
  • peut hiérarchiser les positions non observées pour l'inspection en fonction de direction et distance jusqu'à la destination.
  • peut hiérarchiser les postes non observés à inspecter en fonction de expérience de la collecte d'informations. (jusqu'où peut-il voir en moyenne et jusqu'où doit-il marcher?)
  • peut prioriser les positions non observées à trouver des raccourcis possibles. (expérience: y a-t-il beaucoup de boucles?)
1
comonad
0
raghavankl

Même réponse que toutes les questions sur le débordement de pile;)

Utilisez vi!

http://www.texteditors.org/cgi-bin/wiki.pl?Vi-Maze

C'est vraiment fascinant de voir un éditeur de texte résoudre un labyrinthe d'ascii, je suis sûr que les gars emacs ont un équivalent ..

0
schemathings

Pas spécifiquement pour votre cas, mais j'ai rencontré plusieurs questions de concours de programmation où j'ai trouvé le algorithme de Lee assez pratique pour coder rapidement. Ce n'est pas le plus efficace dans tous les cas, mais il est facile à démarrer. Voici n J'ai piraté un concours.

0
Jubin Chheda

La meilleure façon de résoudre un labyrinthe est d'utiliser un algorithme de connectivité tel que union-find qui est un algorithme temporel quasi linéaire en supposant que la compression du chemin est effectuée.

Union-Find est une structure de données qui vous indique si deux éléments d'un ensemble sont connectés de manière transitoire.

Pour utiliser une structure de données de recherche d'union pour résoudre un labyrinthe, les données de connectivité du voisin sont d'abord utilisées pour construire la structure de données de recherche d'union. Ensuite, la recherche d'union est compressée. Pour déterminer si le labyrinthe est soluble, les valeurs d'entrée et de sortie sont comparées. S'ils ont la même valeur, ils sont connectés et le labyrinthe est résoluble. Enfin, pour trouver une solution, vous commencez par l'entrée et examinez la racine associée à chacun de ses voisins. Dès que vous trouvez un voisin précédemment non visité avec la même racine que la cellule actuelle, vous visitez cette cellule et répétez le processus.

Le principal inconvénient de cette approche est qu'elle ne vous indiquera pas l'itinéraire le plus court à travers le labyrinthe, s'il y a plus d'un chemin.

0
Tyler Durden