Dijkstra en Java
Bonsoir,
Pouvez vous m'aider dans l'implémentation de l'algorithme de Dijkstra (en Java). J'ai déjà commencé le travail mais je bloque sur l'affichage de la distance du plus cours chemin entre deux noeuds du graphe.
public class InputGraph {
public int [][] arc; // Matrice d'adjacense
public Object [] sommet; //objet sommet
public static int count;
public InputGraph (int n){
arc = new int[n][n];
sommet = new Object[n];
}
public int GetSize (){ // récupère la taille du sommet
return sommet.length;
}
public void SetSommet(int node, Object label){ //permet d'ajouter un sommet
sommet[node]=label;
}
public Object GetSommet(int node){ // récupère la valeur d'un sommet
return sommet[node];
}
public void AddArc(int SommetDep, int SommetArr, int dist){ // ajoute un arc
arc [SommetDep][SommetArr]=dist;
}
public int GetPoids(int SommetDep, int SommetArr){ //récupère la distance
return arc [SommetDep][SommetArr];
}
public void AfficheMatrice(){ //affiche la matrice du graphe
for (int j = 0; j < arc.length; j++) {
System.out.println("");
System.out.println( " DE " + sommet[j] + " jusqu'à ");
System.out.println("");
for (int i = 0; i < arc[j].length; i++) {
if (arc[j][i]>0)
System.out.println(sommet[i] + " la distance est de " + arc[j][i]);
}
}
}
public int [] ChercheVoisin(int node){ //construit un tableau des sommets adjacents d'un sommet
count=0;
for (int i = 0; i < arc[node].length; i++) {
if (arc[node][i]>0 ) {
count ++;
}
}
final int[]rep = new int [count];
count = 0;
for (int i = 0; i < arc[node].length; i++) {
if (arc[node][i] > 0) {
rep[count++] = i;
}
}
return rep;
}
public static void main(String[] args) {
InputGraph ex = new InputGraph (4);
ex.SetSommet(0, "processus");
ex.SetSommet(1, "phase");
ex.SetSommet(2, "activité");
ex.SetSommet(3, "label");
ex.AddArc(0, 1, 400);
ex.AddArc(0, 2, 200);
ex.AddArc(1, 2, 300);
ex.AddArc(2, 1, 100);
ex.AddArc(1, 3, 100);
ex.AddArc(2, 3, 500);
ex.AddArc(2, 0, 200);
ex.AfficheMatrice();
}
}
/* ========================================================== */
public class Dijkstra {
public static int [] djikstra (InputGraph IG, int Source){
final int [] distancecc = new int [IG.GetSize()];
final int [] pred = new int [IG.GetSize()];
final boolean [] marque = new boolean [IG.GetSize()];
for (int i = 0; i < distancecc.length; i++) {
distancecc[i] = Integer.MAX_VALUE;
}
distancecc[Source] = 0;
for (int i = 0; i < distancecc.length; i++) {
final int U=ExtraireMin (distancecc, marque);
marque[U]=true;
final int [] V= IG.ChercheVoisin(U);
for (int j = 0; j < V.length; j++) {
final int NV = V [j];
final int d = distancecc[U] + IG.GetPoids(U, NV);
if (d < distancecc[NV]) {
distancecc[NV] = d;
pred[NV] = U;
}
}
}
return pred;
}
}
/* ========================================================== */
public class ExtraireMin {
public static int ExtraireMin(int [] distancecc, boolean [] marque){
int x = Integer.MAX_VALUE;
int y = 0;
for (int i = 0; i < distancecc.length; i++) {
if (!marque[i] && distancecc[i]< x) {
y=i;
x=distancecc[i];
}
}
return y;
}
}
19 réponses
La discussion concerne l'affichage du chemin le plus court et la distance entre deux nœuds dans un graphe orienté, en utilisant l'algorithme de Dijkstra et une implémentation Java. Plusieurs échanges rappellent que Dijkstra fournit d'abord les distances et le tableau des prédécesseurs; le chemin s'obtient ensuite en remontant les prédécesseurs pour afficher l'enchaînement des sommets. La solution proposée montre une fonction qui, à partir du tableau pred et d'un sommet de départ, imprime le chemin en ordre croissant ou renvoie une représentation lisible, sans afficher directement les calculs.
-
Il faut utiliser l'algorithme de Dijkstra pour calculer le tableau des prédécesseurs et s'en servir récursivement pour aller de la fin recherchée vers le début.
Voici comment on pourrait faire un tel affichage, avec le code que tu as déjà fait :
private static void aff(InputGraph graph, int[] pred, int deb, int cur) { if (cur == deb) System.out.print(graph.sommet[cur]); else { aff(graph,pred,deb,pred[cur]); System.out.print(" --> "+graph.sommet[cur]); } } public static void afficherPlusCourtChemin(InputGraph graph,int debut, int fin) { aff(graph,Dijkstra.djikstra(graph, debut),debut,fin); }
Exemple dans ton main : afficherPlusCourtChemin(ex,0,3);
Ce qui donne : "processus --> activité --> phase --> label" -
Bonsoir,
Merci KO pour votre réponse mais en sortie je veux un chiffre qui représente le chemin le plus court.
Cordialement-
-
-
Comme je te l'ai dit ce matin, ma méthode permet d'obtenir le chemin, il faut donc l'adapter pour qu'au lieu d'un affichage elle renvoie le résultat que tu veux, mais la structure est similaire :
private static int len(InputGraph graph, int[] pred, int deb, int s) { if (s == deb) return 0; else return len(graph,pred,deb,pred[s]) + graph.GetPoids(pred[s], s); } public static int longueurPlusCourtChemin(InputGraph graph,int debut, int fin) { return len(graph,Dijkstra.djikstra(graph, debut),debut,fin); } int longueur = longueurPlusCourtChemin(ex,0,3); // 400
-
-
-
Bonjour ,
Quelqu'un pourriez me dire SVP comment retrouver la profondeur d'un noeud x avec le code que j'ai déjà posté.
Cordialement. -
Vous n’avez pas trouvé la réponse que vous recherchez ?
Posez votre question -
Bonjour tout le monde,
Une question SVP, comment puis-je déterminer le poids entre deux noeuds voisins dans un graphe non orienté et en utilisant Dijkstra (le graphe et l'algorithme de Dijkstra sont déjà définis plus haut).
Merci à vous.-
Je ne comprends pas ce que tu appelles le poids entre deux noeuds. Je vois bien ce qu'est le poids d'un noeud, ou le poids d'une arete, mais le poids entre deux noeuds, il va falloir expliquer ce que tu entends par là.
De plus, je ne comprends pas ce que Dijkstra viendrait faire ici puisque les deux noeuds sont voisins...
-
-
Par poids entre deux noeuds, je veux dire le poids des arêtes qui les séparent. Je m'explique, moi je veux déterminer la proximité sémantique entre deux noeuds quelconque. Pour y arriver, j'ai attribuer des poids aux arêtes. Plus la distance qui sépare les deux noeuds est minimale, plus ces deux noeuds ( concepts dans l'ontologie) se rapprochent sémantiquement, d'ou l'intérêt de Dijkstra (parcourir le chemin minimal). Cependant, il arrive que deux noeuds soient dans le voisinage. C'est ici que je bloque pour déterminer le poids.
-
C'est quoi ta définition de voisinage ? Pour moi deux noeuds voisins ça voulait dire liés par une même arête (donc en récupérer le poids était immédiat), de toute évidence on parle ici d'un voisinage plus large...
Ce que tu voudrais faire c'est calculer le cumul des poids des arêtes du plus court chemin ?
-
-
Pour moi, deux noeuds liés par une même arête, appartiennent à la même hiérarchie. Par contre, par voisinage je parle de deux noeuds X et Y qui ne sont pas sur la même hiérarchie (ces deux noeuds qui ont forcément un noeud père en commun). Ce que je veux c'est de déterminer le poids entre X et Y.
-
C'est quoi "un noeud père commun" ?
Ton graphe est non-orienté, donc si X et Y sont sur la même composante connexe, tous les noeuds de cette composante (donc potentiellement tous les noeuds du graphe) seront sur ton voisinage.
Pour le poids entre X et Y, il faudrait effectivement un algorithme de plus court chemin pour passer de noeuds en noeuds, mais je ne suis pas sûr que le cumul doivent se faire par somme, je verrais plutôt un produit de pourcentages, mais je n'ai pas d'éléments qui me permettent de dire que c'est effectivement ça que tu fais...
Voici un exemple de graphe à pourcentages :
Le chemin A-B-C par exemple aurait un poids de 56% obtenu par produit des deux poids A-B et B-C. Vu ce que tu veux faire c'est plus cohérent qu'une somme...
Par contre il faudrait faire une adaptation du graphe pour que représenter tes poids en pourcentage où deux noeuds sont proches s'ils ont un poids d'arêtes élevés. Et une adaptation de l'algorithme pour avoir un calcul de plus long chemin; avec un cumul multiplicatif et non plus additif.
Exemple : entre D et E le chemin D-E est de 60%, le chemin D-A-E de 72% (donc c'est mieux), mais le meilleur chemin c'est D-C-A-E avec 77%. -
Ce que je veux c'est de retrouver le poids entre D et B (si je suis votre graphe) et ce ci en suivant le plus court chemin. Dans mon travail, plus deux noeuds aient un poids faible, plus ils sont similaires sémantiquement, c'est pourquoi j'utilise Dijkstra.
-
Le problème que j'essayais de montrer c'est que quand tu passes d'un noeud à un autre, tu dois cumuler le poids des arêtes.
Dans un algorithme de Dijkstra classique, ce cumul se fait en additionnant le poids des deux arêtes, or je doute que dans ton cas une somme soit vraiment pertinente, parce que le cumul des distances sémantiques ne serait plus réaliste.
C'est pour ça que je t'ai présenté un graphe avec des pourcentages sur les arêtes, pour permettre de faire un cumul avec des produits, mais cela nécessite d'inverser le problème et de faire un calcul de plus long chemin sur des arêtes où la similarité se fait avec un poids fort (sinon le produit donnerait un résultat incohérent).
Mais ça reste un algorithme de Dijkstra, on l'adapte juste à ton problème pour que le résultat corresponde à ce que l'on attend...
-
-
Bonjour,
En fait pour calculer la distance sémantique entre deux noeuds, j'ai une formule à appliquer (ce n'est pas simplement une somme des poids). Cependant, une partie de la formule consiste à chercher le chemin minimal entre ces deux noeuds. -
Pourquoi que je tourne en rond! Pour moi, l'algorithme de Dijkstra marche très bien pour deux noeuds qui sont sur la même hiérarchie mais il bloque pour deux noeuds voisins (qui ont un noeud subsompant en commun). Par exemple: Le noeud A est connecté à B. B est connecté à C et D ( C et D sont des feuilles). Dijkstra bloque pour retrouver le chemin entre C et D. Vous me comprenez ?
-
"pour calculer la distance sémantique entre deux noeuds, (...) une partie de la formule consiste à chercher le chemin minimal entre ces deux noeuds."
Tu tournes en rond si tu cherches à déterminer le poids d'une arête en fonction d'autres poids d'arêtes (le chemin minimal) qui dépendront eux même de l'arête que tu essayes de calculer.
Exemple : X dépend de Y et Z, avec Y qui dépend de X et Z, et Z qui dépend de X et Y, ce n'est pas possible ! Ou alors il faudrait faire un calcul d'une complexité extrême, largement au dessus de ton niveau, ce qui me fait plutôt penser que tu es mal parti dans ton raisonnement et qu'une manière plus simple de concevoir le problème existe.
-
-
Comment faire à votre avis ? Je ne peux pas tout de même utiliser des pourcentages pour représenter les poids parce que je me base sur le réseau sémantique Wordnet.
-
Je ne vois pas ce qui t'empêche d'utiliser un pourcentage...
Il suffit d'une simple formule de mathématiques élémentaires pour passer d'une distance à un pourcentage. Il suffit de considérer un intervalle [min,max] de tes distances (selon ta métrique), pour les faire coïncider aux 0% et 100% et échelonner les distances intermédiaires.
if (distance<min) pourcentage = 1.0; else if (distance>max) pourcentage = 0.0; else pourcentage = 1.0 - (distance - min)/(max - min);
Note : idéalement min=0, et max serait la plus grande grande distance possible. Çà permet de donner plus de sens aux pourcentages (et de simplifier le code au passage).
pourcentage = 1.0 - distance / max;
Par exemple si tes distances étaient issus d'un calcul de similarité cosinus, on aurait max = π
-
-
Non je vais pas utiliser une mesure de similarité déjà existante. Je vais définir une nouvelle. Pour deux noeuds quelconque, elle est donnée comme suit: 1-( poids/ 2*depth). Je ne vois pas toujours l'utilité des pourcentages.
-
La similarité cosinus n'était qu'un exemple, de toute façon elle ne s'applique pas à la sémantique.
Les pourcentages permettent de faire un cumul multiplicatif, alors qu'avec tes distances tu es contraint d'utiliser un cumul additif ce qui va te donner des valeurs bizarres.
Je reprends le même graphe mais avec des distances (min=0, max=20)
Si on prends A, B, C, avec un cumul additif on se retrouve avec A-B qui a le même poids (6) que A-C-B, alors qu'avec les pourcentages on avait 70% et 72%, donc c'est sensiblement la même chose pour une arête.
Le problème vient quand on met les arêtes bout à bout alors on se retrouve avec des distances aberrantes, par exemple E-D-A-B-C a une distance de 22 ce qui correspond à un pourcentage négatif (-10%) ce qui n'a aucun sens !
Alors qu'avec les pourcentages on aurait une similarité certes faible (27%) mais qui reste toujours cohérente... -
Les poids entre les noeuds de mon graphe varient de 0 à 1, donc je vais pas me retrouver dans cette situation aberrante. Ce que je veux c'est simplement de faire le cumul des poids.
-
Les poids entre tes noeuds varient de 0 à 1, donc si par exemple tu as trois arêtes avec 0.5, tu vas te retrouver avec une somme des distances à 1.5 qui ne sera plus enre 0 et 1, ce n'est donc pas logique !
Alors qu'avec des pourcentages tu aurais juste à faire "pourcentage=1-distance" et au lieu de faire une somme tu ferais un produit, donc 0.5*0.5*0.5=0.125, ce qui correspond à une distance de 0.875 qui elle est bien entre 0 et 1...
-
-
J'ai déjà normalisé ma formule, donc le résultat va toujours être compris entre 0 et 1. En fait, j'ai divisé la somme des poids (chemin du noeud x au noeud y en suivant le plus court chemin) par 2* profondeur du noeud le plus éloigné de la racine.
-
Cependant, tu pourras quand même faire des sommes et obtenir des chemins qui sont supérieurs à 1, d'où l'intérêt de la multiplication entre 0 et 1 qui elle aura toujours un résultat entre 0 et 1, jamais plus !
"profondeur du noeud le plus éloigné de la racine", que vient faire une racine là dedans ?
-
-
Non jamais des résultats supérieurs à 1 avec une formule comme 1-(poids/2*profondeur). Par "profondeur du noeud le plus éloigné de la racine", je veux dire le noeud ayant la profondeur la plus grande.
-
Un noeud n'a pas de profondeur dans un graphe, ça n'a pas de sens !
Et même si tu as "1-(poids/2*profondeur)" pour un arc, ce qui est faux au passage car ça devrait être "1-poids/(2*profondeur)", et bien quand tu as deux arcs tu vas calculer le poids de ton chemin en faisant une somme 1-poids/(2*profondeur)+1-poids/(2*profondeur), et là il est tout à fait possible d'avoir un nombre supérieur à 1, ce qui est faux, alors qu'avec un produit tu n'auras pas le problème, en plus comme il faut prendre les plus grandes valeurs ça t'enlève ce "1-" en ne faisant donc plus que : poids/(2*profondeur)*poids/(2*profondeur) qui est toujours entre 0 et 1, quelque soit le nombre d'opérandes...
-
-
Non, vous m'avez mal compris. Dans ma nouvelle approche, plus deux concepts se rapprochent sémantiquement, moins est la distance; c'est pourquoi j'ai noté le "1-".
-
Oui, mais je me répète, encore et encore, pour faire un cumul multiplicatif il est nécessaire que les valeurs soient proches de 1, sinon tu aurais le contraire de ce que tu veux !
Je reprends un exemple tout simple :
Tu as une distance de 0.60, ça revient à 40% de similarité, avec un cumul additif : 0.60+0.60=1.20 c'est supérieur à 1 donc ce n'est pas cohérent, alors qu'avec un cumul multiplicatif : 40%*40%=16% soit une distance de 0.84 qui est bien inférieur à 1, dans tous les cas.
-
-
Bonjour,
Je me permets de déterrer ce sujet (enfin en vrai, c'est la moulinette qui génère les résumés qui l'a fait) car les réponses proposées me paraissent ne sont pas tout à fait correct.
Tout d'abord l'algorithme de Dijkstra ne devrait pas être implémenté de manière récursive, car cela donnera dans en général soit un résultat faux, soit une boucle infinie.
La théorie
Paramètres
L'algorithme de Dijkstra est paramétré par :
- un graphe (orienté ou non, potentiellement avec des boucles ou des cycles)
- un sommet source
- une algèbre de chemin qui doit vérifier les propriétés d'un semi anneaux.
- en général, on utilise l'algèbre (R+, min, +) qui signifie que les poids sont des réels positifs, on préfère le chemin le plus court (min), et pour obtenir la longueur d'un chemin, on somme le poids d'un chemin avec celui d'un arc qu'on tente de lui concaténer
- la plupart des implémentations de l'algorithme de Dijkstra parte du principe que l'algèbre de chemin est "forcément" (R+, min, +), et c'est bien dommage, car l'algorithme est bien plus générique
- quelques exemples d'algèbres de chemins compatibles avec Dijkstra
- ([0, 1], max, x) : algèbre de la fiabilité : chaque arc est pondéré par un pourcentage (donc une valeur dans [0, 1]), on cherche le chemin le plus fiable (max), la fiabilité d'un chemin est le produit des poids de ses arcs (x), .
- (R+, max, min) : algèbre de la bande passante : chaque arc est pondéré par une capacité, on cherche le chemin avec la plus grande bande passante (max), la bande passante d'un chemin est déterminée par l'arc qui joue le rôle de goulot d'étranglement (min)
- Pour en voir d'autres, cf par exemple les travaux sur le Graphe et algorithmes de Gondran & Minoux, tous les articles scientifiques sur le "Metarouting", etc.
Sorties
L'algorithme de Dijkstra retourne un arbre pondéré de plus courts chemin (enraciné à la source et orienté vers les feuilles dans le cadre d'un graphe orienté) ainsi représenté :
- un vecteur de prédécesseur (on associe à chaque sommet son prédécesseur dans l'arbre de plus court chemin).
- Dans l'absolu plusieurs plus courts chemins peuvent exister d'un sommet vers un autre. Dans ce cas, l'algorithme de Dijkstra en reconstruit un arbitraire. On peut cependant adapter l'implémentation de l'algorithme pour maintenir un vecteur de prédécesseurs. Dans ce cas, on l'algorithme ainsi modifié retourne un DAG (Directed Acyclic Graph) de plus courts chemin enraciné à la source.
- un vecteur de distance, qui retourne pour chaque sommet le coût pour aller de la source à ce sommet.
Principe
Pour calculer un arbre de plus court chemins, l'algorithme de Dijkstra part de la source et traite le sommet non encore traité le plus proche de la source. On a donc besoin d'une queue à priorité, qui ordonne les sommets à traiter du plus proches au plus éloignés. En effet, selon le principe de sous-optimalité de Dijkstra, lorsqu'on traite les sommets dans cet ordre, on ne revient jamais sur une décision prise par le passé.
Complexité en temps
Le principe de sous-optimalité de Dijkstra permet de garantir que chaque sommet n'a besoin d'être traité qu'une et une seule fois. Dans un graphe G(V, E), en considérant le tri engendré par la queue à priorité (coût log([V|) pour chaque sommet, soit |V|.log(|V|)), et le fait qu'il faut examiner chaque arc (coût |E|), l'algorithme s'exécute en O(|V|.log(|V|) + |E|).
Pour aller plus loin
Intuitivement, quand l'algorithme s'exécute, tout se passe comme si l'on "contaminait" les sommets des plus proches vers les plus éloignés. Pour ce faire, l'algorithme de Dijkstra fait une sorte de Breadth First Search (BFS), mais en examinant les sommets conformément à l'ordre dicté par sa queue à priorité.
Un des gros danger pour l'algorithme de Dijkstra, c'est précisément d'être pris dans une boucle infinie (typiquement parce qu'un graphe comportant un cycle ou une boucle pourrait amener à traverser cette boucle ou ce cycle un nombre infini de fois). En particulier, si un cycle est absorbant (c'est-à-dire que le traverser permet de diminuer le coût), on a tout intérêt à tourner indéfiniment. C'est là que deux points entrent en jeu :
- Concernant les poids : l'algèbre de chemin garantit qu'un cycle ou une boucle absorbante ne peuvent exister. En effet, pour l'algèbre usuelle (R+, min, +), les poids négatifs sont de facto interdits. Et (R, min, +) (poids positifs ou négatifs n'est pas un semi anneau, donc on n'a pas le droit d'invoquer l'algorithme de Dijkstra.
- Concernant la topologie du graphe : Pour ne traiter chaque sommet qu'une seule fois, on a recours souvent à une structure auxiliaire, appelée color map, qui associe à chaque sommet sont statut (typiquement : traité, en cours de traitement, non traité). Cette color map prend tout son sens si le graphe comporte des boucles et/ou des cycles.
Implémentation de l'algorithme de Dijkstra
Calcul de la profondeur
Intuitivement, calculer une profondeur est un cas particulier de calcul de plus court chemin avec l'algèbre (R+, min, +) où le poids de tous les arcs vaut 1. On pourrait donc utiliser l'algorithme de Dijkstra, qui s'exécute en O(log(|V|).|V| + |E|).
Cependant on peut faire mieux et obtenir le même résultat plus rapidement, en O(|V| + |E|). Il suffit pour cela d'utiliser un algorithme de parcours de graphe, soit un BFS (Breadth First Search), soit un DFS (Depth First Search).
Dans ce fil de discussion, il faut donc :
- Exécuter un DFS/BFS pour intégrer la profondeur aux poids prédéfinis.
- Exécuter l'algorithme de Dijkstra sur ce nouveau jeu de poids.
Bonne chance