Dans ce TD, nous allons voir comment utiliser grapheX (le paquet permettant de gérer les graphes) en réalisant la programmation d'un type particulier de graphe, où les sommets représentent des points dans le plan. On peut alors considérer la distance euclidienne entre deux sommets.
Nous travaillerons à cet effet sur des exemples concrets, à savoir l'ensemble des villes et des villages d'un pays. Nous prendrons comme exemple la France et Mayotte. Attention, pour des problèmes de quota disque, les fichiers sont déjà installés sur les stations de l'enseignement et il n'est pas nécessaire de les mettre sur votre compte. Néanmoins, pour ceux qui travaillent sur leur ordinateur personnel, vous pouvez les placer dans votre répertoire TD6 .
Si vous désirez travailler (en dehors du TD) sur d'autres pays ou comprendre le contenu de ces fichiers de données, vous pouvez vous rendre à l'adresse suivante.
grapheX est un paquet développé par F. Morain et par P. Chassignet permettant de travailler sur les graphes. Pour de plus amples informations, vous pouvez vous référer au polycopié du cours. Nous aurons besoin d'utiliser ce paquet tout au long du TD, il vous faut donc l'installer. Pour cela téléchargez l'archive de grapheX sur la page du cours. (Pour les utilisateurs de windows téléchargez simplement grapheX.jar et placez le dans le répertoire INF431) Ensuite, décompressez l'archive, soit à l'aide de l'explorateur de fichier, soit en utilisant la ligne de commande tar -zxvf grapheX-2.1.tgz. Vous devez avoir maintenant un répertoire grapheX-2.1. Placez ce répertoire dans votre répertoire INF431 (la racine pour eclipse). Pour compiler un programme utilisant grapheX vous avez deux possibilités :
Nous allons donc représenter des graphes dont les sommets sont des villes. Pour cela on vous fournit une implémentation de la classe Ville et la représentation d'un ensemble de Ville.
Téléchargez les fichiers Ville.java et EnsembleVille.java permettant de lire les fichiers de données et de représenter les villes. Ces deux fichiers contiennent deux classes qui vous seront utiles :
public class Ville extends Identifiable { // Permet l'affichage de la ville par un appel a System.out.println(ville); public String toString(); // Calcule la distance en metres entre la ville courante et dest public double distance(Ville dest); }
public class EnsembleVille { // Charge les villes dans le fichier nom EnsembleVille(String nom) ... // Nombre de villes dans l'ensemble public int size() ... // Renvoie la ieme Ville de l'ensemble public Ville getVille(int i) ... }
Vous pouvez exécuter la classe EnsembleVille qui contient une méthode main chargeant l'ensemble des villes de Mayotte. Vous serez peut-être amenés à modifier dans cette méthode main le chemin d'accès au fichier mf.txt.
Nous allons maintenant créer la classe GrapheGeometrique permettant de représenter un graphe. Le paquetage grapheX définit une classe abstraite GrapheGenerique. Nous allons en écrire une implémentation. Pour cela, il faut redéfinir a minima les méthodes abstraites présentes dans la classe GrapheGenerique. Nous utiliserons donc le squelette suivant. Si vous utilisez eclipse, il n'est pas nécessaire de copier coller le code et eclipse peut créer cette classe de manière automatique. Il vous suffit de réaliser l'opération décrite dans ce petit film. Il vous suffira simplement de rajouter le champ LinkedList<Ville> sommets; après cette création.
import java.util.Collection; import java.util.LinkedList; import grapheX.Arc; import grapheX.GrapheGenerique; public class GrapheGeometrique extends GrapheGenerique<Ville> { LinkedList<Ville> sommets; public GrapheGeometrique() { // TODO Auto-generated constructor stub } @Override public int taille() { // TODO Auto-generated method stub return 0; } @Override public void ajouterSommet(Ville s) { // TODO Auto-generated method stub } @Override public Collection<Ville> sommets() { // TODO Auto-generated method stub return null; } @Override public void ajouterArc(Ville s, Ville t, int val) { // TODO Auto-generated method stub } @Override public boolean existeArc(Ville s, Ville t) { // TODO Auto-generated method stub return false; } @Override public Collection<Arc<Ville>> voisins(Ville s) { // TODO Auto-generated method stub return null; } @Override public int valeurArc(Ville s, Ville t) { // TODO Auto-generated method stub return 0; } @Override public void enleverArc(Ville s, Ville t) { // TODO Auto-generated method stub } }
public static void test() { // le main prend le fichier mf.txt et affiche les villes EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); // On construit un graphe a partir des 10 premieres villes GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < 10; i++) { g.ajouterSommet(ens.getVille(i)); } Collection<Ville> villes = g.sommets(); System.out.println("Le graphe est de taille "+g.taille()); for (Ville s:villes) { System.out.println(s); } }Vous devez obtenir le résultat suivant :
Le graphe est de taille 10 Accua Latitude: -12.7225 Longitude: 45.0563889 Acoua Latitude: -12.7225 Longitude: 45.0563889 Andrema Latitude: -12.6786111 Longitude: 45.0977778 Apandzo Latitude: -12.8305556 Longitude: 45.1302778 Bambo Est Latitude: -12.9261111 Longitude: 45.1738889 Bambo Ouest Latitude: -12.9219444 Longitude: 45.0872222 Bandraboua Latitude: -12.7016667 Longitude: 45.1205556 Bandrajou Latitude: -12.7452778 Longitude: 45.2186111 Bandrani Latitude: -12.845 Longitude: 45.1019444 Bandrele Latitude: -12.9066667 Longitude: 45.1913889Vous pouvez aussi tester à l'aide de la fonction suivante et en téléchargeant la classe MaFenetre.java.
public static void test2() { // le main prend le fichier mf.txt et affiche les villes EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); MaFenetre f = new MaFenetre(); f.setGraphe(g); }Vous obtiendrez le résultat suivant : Vous pouvez alterner l'affichage des noms des villes en utilisant le menu d'affichage.
Si vous utilisez le fichier fr.txt au lieu de mf.txt vous obtiendrez la carte :
public static void test3() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); for (int i = 0; i < ens.size(); i++) for (int j = i+1; (j < i+3 && j < ens.size()); j++) g.ajouterArc(ens.getVille(i),ens.getVille(j),i); // On met la valeur i aux arcs car cette valeur est utilise par le programe pour determiner la couleur // des arcs for (Arc<Ville> a:g.voisins(ens.getVille(0))) System.out.println(a.origine()+" -- "+a.destination()); System.out.println(g.existeArc(ens.getVille(0), ens.getVille(1))); System.out.println(g.existeArc(ens.getVille(0), ens.getVille(3))); }Vous devez obtenir :
Accua Latitude: -12.7225 Longitude: 45.0563889 -- Andrema Latitude: -12.6786111 Longitude: 45.0977778 Accua Latitude: -12.7225 Longitude: 45.0563889 -- Acoua Latitude: -12.7225 Longitude: 45.0563889 true falseDe manière graphique en utilisant la méthode suivante :
public static void test4() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); for (int i = 0; i < ens.size(); i++) for (int j = i+1; (j < i+3 && j < ens.size()); j++) g.ajouterArc(ens.getVille(i),ens.getVille(j),i); // On met la valeur i aux arcs car cette valeur est utilise par le programe pour determiner la couleur // des arcs MaFenetre f = new MaFenetre(); f.setGraphe(g); }Vous obtenez le résultat :
public static void test5() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); for (int i = 0; i < ens.size(); i++) for (int j = i+1; (j < i+3 && j < ens.size()); j++) g.ajouterArc(ens.getVille(i),ens.getVille(j),i); for (int i = 0; i < ens.size(); i++) if (i+2 < ens.size()) g.enleverArc(ens.getVille(i),ens.getVille(i+2)); MaFenetre f = new MaFenetre(); f.setGraphe(g); System.out.println(g.existeArc(ens.getVille(0), ens.getVille(1))); g.enleverArc(ens.getVille(0), ens.getVille(1)); g.enleverArc(ens.getVille(0), ens.getVille(1)); System.out.println(g.existeArc(ens.getVille(0), ens.getVille(1))); System.out.println(g.existeArc(ens.getVille(0), ens.getVille(2))); System.out.println(g.valeurArc(ens.getVille(2), ens.getVille(3))); System.out.println(g.valeurArc(ens.getVille(0), ens.getVille(2))); }Vous devez obtenir le résultat suivant :
true false false 2 Exception in thread "main" java.lang.Error: Arc inexistant ...
public static void test6() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); g.euclidien(2000); MaFenetre f = new MaFenetre(); f.setGraphe(g); }Vous obtenez :
Qu'obtenez-vous avec une distance de 4000 mètres ? Il est fortement déconseillé d'essayer avec la France car alors le temps de construction avoisinerait l'heure. En effet, l'algorithme est ici quadratique en le nombre de villes (plus de 75000 pour la France). Vous pouvez en revanche prendre un sommet sur 30, par exemple avec le programme suivant :
public static void test7() { // le main prend le fichier fr.txt et affiche les villes EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/fr.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size()/30; i++) g.ajouterSommet(ens.getVille(i*30)); g.euclidien(25000); MaFenetre f = new MaFenetre(); f.setGraphe(g); }Vous obtenez :
Arc<Ville> premierArc(Collection<Arc<Ville>> c) { for (Arc<Ville> a:c) return a; return null; }Vous pouvez tester votre méthode à l'aide de la fonction suivante :
public static void test8() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); for (int i = 0; i < ens.size()/2; i++) g.ajouterArc(ens.getVille(i),ens.getVille(i+1),i); System.out.println(ens.getVille(0)+ " ---> "+g.calculPuits(ens.getVille(0))); System.out.println(ens.getVille(70)+ " ---> "+g.calculPuits(ens.getVille(70))); }Vous devez obtenir :
Accua Latitude: -12.7225 Longitude: 45.0563889 ---> Malamani Latitude: -12.9169444 Longitude: 45.1563889 Manyassini Latitude: -12.8888889 Longitude: 45.1411111 ---> Manyassini Latitude: -12.8888889 Longitude: 45.1411111
public static void test9() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); for (int i = 0; i < ens.size()/2; i++) g.ajouterArc(ens.getVille(i),ens.getVille(i+1),i); System.out.println(ens.getVille(0)+ " ---> "+g.calculPuits(ens.getVille(0))); System.out.println(ens.getVille(70)+ " ---> "+g.calculPuits(ens.getVille(70))); g.fusion(ens.getVille(0),ens.getVille(70)); System.out.println(ens.getVille(0)+ " ---> "+g.calculPuits(ens.getVille(0))); System.out.println(ens.getVille(70)+ " ---> "+g.calculPuits(ens.getVille(70))); }Vous devez obtenir :
Accua Latitude: -12.7225 Longitude: 45.0563889 ---> Malamani Latitude: -12.9169444 Longitude: 45.1563889 Manyassini Latitude: -12.8888889 Longitude: 45.1411111 ---> Manyassini Latitude: -12.8888889 Longitude: 45.1411111 Accua Latitude: -12.7225 Longitude: 45.0563889 ---> Manyassini Latitude: -12.8888889 Longitude: 45.1411111 Manyassini Latitude: -12.8888889 Longitude: 45.1411111 ---> Manyassini Latitude: -12.8888889 Longitude: 45.1411111
public static void test10() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); g.euclidien(2000); GrapheGeometrique g2 = g.composanteConnexe(); MaFenetre f = new MaFenetre(); f.setGraphe(g); MaFenetre f2 = new MaFenetre(); f2.setGraphe(g2); }Vous devez obtenir le résultat suivant :
Notez que sur le second graphe, nous avons des arbres.
public static void test11() { EnsembleVille ens = new EnsembleVille("/users/profs/info/rossin/mf.txt"); GrapheGeometrique g = new GrapheGeometrique(); for (int i = 0; i < ens.size(); i++) g.ajouterSommet(ens.getVille(i)); g.euclidien(2000); g.colorieComposantesConnexes(); MaFenetre f = new MaFenetre(); f.setGraphe(g); }Vous devez obtenir le résultat :
En reprenant le test sur la France avec un seuil euclidien de 21000, vous obtenez :
Le problème dans ce TD est le calcul des arêtes du graphe euclidien. Pour pallier ce problème, il suffit de représenter les sommets dans le graphe non pas par une liste chaînée mais par un tableau dont chaque case représentera un carré de 2km par 2km par exemple. Ensuite, pour construire le graphe euclidien il suffit de rechercher les sommets voisins d'un sommet dans la même case ou dans une des cases voisines.
Plus précisement, créez une classe GrapheEuclidien suivant le squelette suivant :
class GrapheEuclidien extends GrapheGeometrique { HashMap<Integer,HashMap<Integer,LinkedList<Ville>>> listeSommets; int nVilles; int distance; double angleDistance; GrapheEuclidien(int distance) { // La distance est en metres double R = 6371000; // rayon de la terre this.distance = distance; // On calcule la distance en degres angleDistance = distance / Math.PI * 180.0 / R; nVilles = 0; listeSommets = new HashMap<Integer,HashMap<Integer,LinkedList<Ville>>>(); } int caseX(Ville s) { return (int) ((s.getLongitude()+180.0)/angleDistance); } public int taille() { return nVilles; } int caseY(Ville s) { return (int) ((s.getLatitude()+180.0)/angleDistance); } public void ajouterSommet(Ville s) { aretes.put(s,new HashMap<Ville,Arc<Ville>>()); nVilles++; // TODO A COMPLETER } public Collection<Ville> sommets() { // TODO A COMPLETER return null; } public void euclidien(double distEssence) { euclidien(); // On utilise la distance du constructeur } Collection<Ville> sommetsEn(int x,int y) { if (!listeSommets.containsKey(x)) return java.util.Collections.emptySet(); HashMap<Integer,LinkedList<Ville>> ll = listeSommets.get(x); if (!ll.containsKey(y)) return java.util.Collections.emptySet(); return ll.get(y); } public void euclidien() { // TODO A COMPLETER } }Pour représenter le tableau, on utilise une double HashMap dont le premier entier représente l'abscisse de la case où doit se trouver le sommet et le second son ordonnée. Pour vous faciliter le travail nous vous donnons les méthodes qui à partir d'un sommet calculent l'abscisse et l'ordonnée de la case. Pour calculer le graphe euclidien avec une distance égale à la largeur d'une case, il suffit donc pour un sommet de calculer les coordonnées (x,y) de la case où se trouve ce sommet, et de remarquer qu'un sommet relié à celui-ci est forcément dans une des cases :
Vous pouvez maintenant lancer le calcul des composantes connexes et en utilisant une distance de 4000 vous obtenez le résultat suivant :