IN201 : 5 – Algorithme de Dijkstra Author Public Date : Christophe Garion <[email protected]> : SUPAERO 2A : Résumé Ce TP sert de récapitulatif aux quatre premières séances du cours IN201. Il consiste à concevoir et implanter un système permettant de trouver un plus court chemin dans un graphe en utilisant l’algorithme de Dijkstra. 1 Objectifs Les objectifs de ce TP sont les suivants : – vérifier que vous êtes capable de proposer une solution de conception simple en utilisant le langage UML ; – vérifier que vous êtes capable d’implanter une classe en respectant les principes énoncés en cours ; – vérifier que vous êtes capable de tester une classe via JUnit. 2 Présentation du problème Si une des grandes questions existentielles qui vous taraudent est de savoir comment touver un chemin minimal de votre chambre au Batens histoire de profiter au maximum de votre lit douillet tout en évitant d’arriver en retard, ça tombe bien, ce TP est pour vous. Les problèmes de recherche de plus court chemin sont des problèmes bien connus en mathématiques et en informatique depuis très longtemps. La représentation du chemin à parcourir se fait classiquement en utilisant un graphe composé de nœuds représentant des points géographiques. Les nœuds peuvent être reliés entre eux deux par deux par des arcs, la présence d’un arc entre deux nœuds signifiant que l’on peut passer d’un nœud à l’autre. On place sur chaque arc un poids, représenté par un réel positif, qui représente le coût de passage d’un nœud à l’autre par ce chemin. En utilisant cette modélisation, il existe de nombreux algorithmes permettant de trouver un plus court chemin dans un graphe : algorithme de Dijkstra, A*, algorithmes de programmation dynamique etc [1]. La plupart de ces algorithmes partent d’un nœud de départ et cherchent les nœuds accessibles à partir de ce dernier en suivant les arcs. Notre application a pour but de trouver le plus court chemin entre la R3 et le CI d’après le graphe présenté sur la figure 1. 3 Conception d’une solution On va chercher à modéliser le problème en utilisant une conception orientée objet. On ne s’intéresse absolument pas à l’aspect algorithmique du problème pour l’instant. On propose d’utiliser les classes suivantes pour résoudre le problème : – une classe Noeud qui modélise les nœuds du graphe ; – une classe Arc qui modélise les arcs du graphe ; – une classe Probleme qui modélise un problème particulier, i.e. un graphe avec un nœud de départ et un nœud d’arrivée ; – une classe Solver qui permet de résoudre des problèmes grâce à un algorithme particulier. 1. dans un premier temps, écrire un diagramme UML représentant les différentes relations qui existent entre ces classes ; 2. affiner ce diagramme en utilisant des navigabilités et visibilités et en proposant une vue plus « implantation » de votre solution. On supposera que pour les besoins de l’algorithme de résolution, on a besoin étant donné un nœud donné de connaître les arcs qui partent de ce nœud ; 3. proposer enfin un diagramme de classe UML détaillé faisant apparaître attributs et opérations pour les classes Noeud, Arc, Probleme et Solver. On supposera que les objets créés n’ont pas besoin d’être modifiés au cours du programme. Dans ces diagrammes détaillés, on ne fera pas apparaître les éventuels détails d’implantation en ce qui concerne les multiplicités de type * (choix d’un tableau, d’un objet de type ArrayList etc.). On utilisera la syntaxe UML suivante : nomAttribut : TypeAttribut [n] qui précise que nomAttribut est un attribut « contenant » n objets de type TypeAttribut (cette syntaxe n’impose pas que cet attribut soit ensuite implanté sous la forme d’un tableau). Revision b81747b, Wed Oct 31 23:03:40 2012 +0100, Christophe Garion. 1/4 Muzak 2 3 R2 1 2 RU 2 R1 1 Foyer 5 4 50 Mirage 3 4 Machines à café Parking 25 3 Figure 1 – Où l’on s’aperçoit qu’il ne vaut mieux pas passer par le foyer pour aller en TP d’informatique 4 R3 2 2 25 Machines à café CI 2 Piscine IN201 5 – Algorithme de Dijkstra SUPAERO 2A 2/4 IN201 4 5 – Algorithme de Dijkstra SUPAERO 2A Implantation de la solution Vous allez devoir écrire les quatre classes Noeud, Arc, Probleme et Solver et les tester avec JUnit. Toutes les classes devront appartenir au paquetage fr.supaero.pathfinding. Le solver que vous allez écrire va utiliser l’algorithme de Dijkstra, présenté ci-dessous sur l’algorithme 4.1. La « fonction » distance permet de trouver la distance minimale du nœud de départ à un nœud donné et la « fonction » précédent permet de donner pour chaque nœud le nœud précédent qui assure un plus court chemin. Algorithme 4.1 : L’algorithme de Dijkstra pour calculer le plus court chemin dans un graphe d’un nœud de départ donné à un nœud arrivée donné entrées : un graphe G , un nœud de départ nd et un nœud d’arrivée na sortie : une séquence S représentant le plus court chemin de nd à na dans le graphe 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 noeuds ← ensemble des nœuds du graphe ; pour chaque n ∈ noeuds faire distance [n] ← ∞ ; précédent [n] ← null ; fin distance [nd] ← 0 ; tant que noeuds 6= ∅ faire x ← nœud dans noeuds tel que distance [x ] est le minimum pour les nœuds appartenant à noeuds ; si distance [ x ] = ∞ alors retourner null ; fin si x = na alors construire une séquence S de na à nd en utilisant précédent ; retourner S ; fin enlever x de noeuds ; pour chaque n ∈ noeuds tel que x est lié par un arc d à n faire nouvelle distance ← distance [x ] + valeur de d ; si nouvelle distance < distance [n] alors distance [n] ← nouvelle distance ; précédent [n] ← x ; fin fin fin retourner null ; Un programme applicatif construisant le graphe présenté en figure 1 et demandant le plus court chemin entre le nœud « R3 » et le nœud « CI » vous est fourni sous forme compilée. Une classe de test JUnit de la classe Solver vous est également fournie sous forme compilée. Elle utilise le graphe présenté sur la figure 2 pour tester votre solution. Les différentes méthodes de test de la classe vous permettront de mieux cerner les éventuels problèmes de votre algorithme (voir également la javadoc de la classe). 3/4 IN201 5 – Algorithme de Dijkstra 3 n1 SUPAERO 2A 1 2 n2 1 n3 5 1 n4 n5 1 n6 1 Figure 2 – Le graphe utilisé par la classe de test JUnit de Solver Pour que tout le monde parte de la même solution, nous vous fournissons le diagramme détaillé de chaque classe. Vous devrez implanter vos classes (Noeud, Arc, Probleme, Solver) en respectant ces diagrammes si vous voulez que le programme de test et la classe de test JUnit fournis fonctionnent (n’oubliez pas d’ajouter JUnit comme bibliothèque dans votre projet pour que les tests fonctionnent). Vous trouverez sur le site les squelettes des classes que vous devez développer sous forme d’un autre fichier JAR (lisez http://www.tofgarion.net/lectures/IN201/eclipse.php pour comprendre comment importer des fichiers sources avec Eclipse). Vous disposez également de la classe Solver compilée pour vous permettre d’avancer dans votre TP. N’oubliez pas d’enlever de votre CLASSPATH ou de votre projet Eclipse l’archive JAR la contenant lorsque vous allez développer votre propre classe Solver. Vous pouvez évidemment écrire des spécifications pour préciser le comportement de vos méthodes. Par contre, vous ne pourrez utiliser ni jmlc ni jmlrac, car nous utilisons les types génériques qui ne sont pas compatibles avec JML. En ce qui concerne l’implantation de l’algorithme et lui seulement : – vous devez calculer l’ensemble des nœuds du graphe à partir du problème que vous avez ; – ∞ sera représenté par la constante Double.POSITIVE_INFINITY ; – pour stocker des séquences, on choisira d’utiliser des instances de java.util.ArrayList ; – pour stocker des ensembles, on choisira d’utiliser des instances de java.util.HashSet. Cette classe s’utilise comme ArrayList : – paramètrage de la classe avec le type d’objet contenu (notation java.util.HashSet<Noeud> par exemple) – méthodes add et remove – par contre, pas de méthode permettant de retrouver ou de placer un élément à un index particulier comme pour ArrayList (car il s’agit d’un ensemble) – les « fonctions » distance et précédent seront codées par des tables de correspondance (ou tableaux associatifs) représentées en Java par des objets de type java.util.HashMap<K,V> qui est une collection particulière. Dans une instance de HashMap, on associe des clés de type K à des valeurs de type V. Pour distance, on aura donc une HashMap<Noeud,Double> 1 et pour précédent une HashMap<Noeud,Noeud>. La classe HashMap fournit un certain nombre de méthodes utiles : – put(K key, V value) qui associe l’objet value à la clé key. Si la clé a déjà été utilisée, l’objet associé est remplacé par le nouvel objet – get(K key) qui renvoie un objet de type V correspond à la clé key La documentation javadoc de la classe sur le site de Oracle [2] vous fournira plus de détails. 5 Travail à rendre Vous avez plusieurs documents à rendre : – lorsque vous avez fini votre conception sur papier, vous la remettez à votre PC(wo)man avant d’aller en TP ; – à la fin de la séance, vous renvoyez vos classes à votre PC(wo)man ; – vous renvoyez le TP complet (sans les diagrammes de conception) jeudi soir. Références [1] T.H. Cormen et al. Introduction à l’algorithmique. 2e éd. Dunod, 2004. [2] Java API specifications. url : http://download.oracle.com/javase/6/docs/api/index.html. 1. Double est une classe représentant des réels. Elle permet d’encapsuler le type primitif double dans des objets. 4/4