autre: Ajout de commentaires dans l'ensemble de nos codes produits

This commit is contained in:
Brendan Saint Germes 2026-06-02 10:51:47 +02:00
parent cfb1accd01
commit 0052d67e9f
10 changed files with 90 additions and 40 deletions

View file

@ -110,6 +110,7 @@ public class ArcInspectorFactory {
}
};
// Ajout de l'ArcInspector qui évalue les PCC en temps à vélo
public static class BicyleByTime implements ArcInspector {
static int maxBicycleSpeed = 20;
@ -142,6 +143,7 @@ public class ArcInspectorFactory {
}
}
// Ajout de l'ArcInspector qui évalue les PCC en distance à vélo
public static class BicyleByLength implements ArcInspector {
static int maxBicycleSpeed = 20;

View file

@ -4,16 +4,25 @@ import org.insa.graphs.model.Node;
public class AStarAlgorithm extends DijkstraAlgorithm {
/*
L'algorithme A* est une variante de Dijkstra.
Au lieu de réimplémenter bêtement un algorithme tout entier, nous nous sommes rendu compte que l'on pouvait simplement modifier une fonction createLabel()
et que l'ensemble des itérations restaient les mêmes. Notre classe AStarAlgorithm hérite donc de DijkstraAlgorithm, et on modifie seulement la fonction createLabel().
*/
public AStarAlgorithm(ShortestPathData data) {
super(data);
}
@Override
protected Label createLabel(Node node) {
final LabelStar labelStar = new LabelStar(node);
final ShortestPathData data = (ShortestPathData)this.data;
// Création du LabelStar "vide"
final LabelStar labelStar = new LabelStar(node);
// Contrairement à Dijkstra on créé juste un Label "vide", ici on doit en plus calculer la distance entre le Node et le départ du chemin.
final Double distance = data.getDestination().getPoint().distanceTo(node.getPoint()); // en mètres
// En fonction de si on fait un PCC en distance ou en temps de trajet, notre heuristique ne sera pas la même
switch (data.getMode()) {
case LENGTH:
labelStar.setCoutEstime(distance); // en mètres

View file

@ -22,8 +22,7 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
protected HashMap<Integer, Label> labels; // clé-valeur pour retrouver le label associé à un noeud à partir de son id
protected BinaryHeap<Label> heap; // tas binaire pour retrouver le min en temps constant
// Fonction pour créer un label inexistant, à override pour utiliser les LabelStar
// Fonction pour créer un label inexistant. Cette fonction sera à remplacer lors de l'implémentation de A*
protected Label createLabel(Node node) {
return new Label(node);
}
@ -32,18 +31,24 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
protected ShortestPathSolution doRun() {
final ShortestPathData data = getInputData();
// Initialisation du tas binaire et de la HashMap contenant les Labels
this.labels = new HashMap<>();
this.heap = new BinaryHeap<>();
// Création d'un Label pour le Node de départ, et ajout dans les structures précédentes
Label currentLabel = this.createLabel(data.getOrigin());
currentLabel.setCoutRealise(0D);
this.heap.insert(currentLabel);
this.labels.put(data.getOrigin().getId(), currentLabel);
// Boucle principale de l'algorithme, que l'on exécutera tant que la destination n'est pas atteinte,
// ou si l'on vient à marquer un noeud de coût infini (cela veut dire que l'on change de composante connexe).
boolean destinationReached = false;
notifyOriginProcessed(data.getOrigin());
while (!destinationReached && !this.heap.isEmpty()) {
// On récupère le Label (et donc le Node associé) de coût minimal (et non marqué, car les Labels marqués sont retirés du tas binaire)
currentLabel = this.heap.deleteMin();
// Si coût infini, on arrête la boucle car on a exploré toute la composante connexe du noeud de départ.
if (currentLabel.getTotalCost() == Double.MAX_VALUE)
break;
@ -52,31 +57,37 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
currentLabel.setMarque(true);
notifyNodeMarked(currentNode);
// On constate que les coûts des sommets marqués est bel et bien croissant
// On constate bien que les coûts des sommets marqués sont bel et bien croissants
// System.out.println("-> Sommet marqué: " + currentLabel.getCoutRealise());
// Si on atteint notre destination, on arrête l'algo
// Si l'on atteint notre destination, on arrête l'algorithme
if (currentNode.equals(data.getDestination())) {
destinationReached = true;
} else {
// On parcours l'ensemble des successeurs du Node courrant (à travers ses Arcs)
for (final Arc arc : currentNode.getSuccessors()) {
// C'est cette condition qui permet de filtrer les arcs non autorisés (exemple: l'autoroute alors que l'on est un piéton)
if (data.isAllowed(arc)) {
final Node successorNode = arc.getDestination();
//System.out.println("successorNode = " + successorNode);
// On récupère le Label associé au Node successorNode, et à défaut, on en créé un nouveau (de coût infini donc)
final Label successorLabel = Optional.ofNullable(this.labels.get(successorNode.getId())).orElse(this.createLabel(successorNode));
//System.out.println("successorLabel = " + successorLabel);
// Si on peut réduire le cout du successorLabel en passant par le Node courrant, on met à jour l'étiquette.
if (successorLabel.getTotalCost() > currentLabel.getTotalCost() + data.getCost(arc)) {
// On supprime du tas binaire l'ancienne étiquette
try {
this.heap.remove(successorLabel);
} catch(ElementNotFoundException e) {
// BinaryHeap#remove a échoué, donc ça veut dire qu'on doit juste insérer, pas mettre à jour, mais ça ne change rien au niveau du code
// BinaryHeap remove a échoué, donc ça veut dire qu'on doit juste insérer, pas mettre à jour, mais ça ne change rien au niveau du code
}
notifyNodeReached(successorNode);
// On met à jour le coût et l'arc parent sur l'étiquette
successorLabel.setCoutRealise(currentLabel.getCoutRealise() + data.getCost(arc));
successorLabel.setArc(arc);
// On insère la nouvelle étiquette dans les structures (tas binaire et HashMap)
this.heap.insert(successorLabel);
this.labels.put(successorNode.getId(), successorLabel);
}
@ -85,14 +96,17 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
}
}
// Arrivé ici, l'algorithme est soit arrivé à destination (dans ce cas destinationReached = true), soit il est sorti de sa composante connexe.
ShortestPathSolution solution = null;
// Si on a trouvé un PCC
if (destinationReached) {
notifyDestinationReached(data.getDestination());
// On part de la fin, et on remonte petit à petit vers le sommet d'origine
// On part de la fin, et on remonte petit à petit vers le sommet d'origine, pour construire le Path à partir des arcs
final List<Arc> arcs = new ArrayList<>();
Node currentNode = data.getDestination();
while (currentNode != data.getOrigin()) {
// On récupère l'arc associé au Node courrant
final Arc arc = labels.get(currentNode.getId()).getArc();
if (arc != null) {
arcs.add(arc);
@ -102,9 +116,16 @@ public class DijkstraAlgorithm extends ShortestPathAlgorithm {
}
}
final Path path = new Path(data.getGraph(), arcs.reversed());
solution = new ShortestPathSolution(data, Status.OPTIMAL, path);
if (currentNode == data.getOrigin()) {
// On construit la solution, en pensant bien à inverser la liste des arcs car on veut un chemin du départ vers l'arrivée et pas l'inverse
final Path path = new Path(data.getGraph(), arcs.reversed());
solution = new ShortestPathSolution(data, Status.OPTIMAL, path);
} else {
// On construit un objet Solution infaisable si on a pas réussi à reconstruire le chemin
solution = new ShortestPathSolution(data, Status.INFEASIBLE);
}
} else {
// On construit un objet Solution infaisable si on a pas atteint la destination
solution = new ShortestPathSolution(data, Status.INFEASIBLE);
}

View file

@ -37,15 +37,9 @@ public class Label implements Comparable<Label> {
return this.getCoutRealise();
}
// Cette fonction sert à trier les Labels entre eux (et aussi, parceque LabelStar hérite de Label, les LabelStars entre eux)
@Override
public int compareTo(Label l) {
/*if (this instanceof LabelStar && l instanceof LabelStar) {
System.out.println("==> Comparaison LabelStar vs LabelStar");
} else {
System.out.println("==> Comparaison Label vs Label");
}
System.out.println(this);
System.out.println(l);*/
// si deux nodes ont le même coût, on compare leur estimation (si LabelStar)
int cmp1 = this.getTotalCost().compareTo(l.getTotalCost());
if (cmp1 == 0) {
@ -55,7 +49,7 @@ public class Label implements Comparable<Label> {
if (cmp2 != 0) return cmp2;
}
// Soit ce n'est pas des LabelStar, soit ils ont meme cout total ET meme estimation
// Soit ce n'est pas des LabelStar, soit ils ont le même cout total ET la me estimation
return ((Integer) this.getNode().getId()).compareTo((Integer) l.getNode().getId());
} else {
return this.getTotalCost().compareTo(l.getTotalCost());

View file

@ -5,6 +5,7 @@ import org.insa.graphs.model.Node;
public class LabelStar extends Label {
// On ajoute la variable coutEstime qui contiendra notre heuristique, à savoir soit le temps estimé pour revenir au départ à vol d'oiseau, soit la distance.
private Double coutEstime;
public LabelStar(Node node) {

View file

@ -137,14 +137,17 @@ public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
public void remove(E x) throws ElementNotFoundException {
if (this.isEmpty()) throw new ElementNotFoundException(x);
Stack<Integer> stack = new Stack<>();
// Initialisation de la pile, qui nous permettra de faire un parcours en profondeur pour retrouver notre élément "x" dans le tas binaire
final Stack<Integer> stack = new Stack<>();
// On commence la recherche à la racine
stack.add(0);
while (!stack.isEmpty()) {
int i = stack.pop();
E current = this.array.get(i);
// On compare l'élément au sommet de la pile avec "x"
int comp = current.compareTo(x);
if (comp == 0) {
// on a trouvé notre élément
// on a trouvé notre élément "x", on va donc le supprimer du tas binaire et faire remonter/redescendre les autres éléments afin de corriger l'arbre et qu'il reste un tas binaire
this.currentSize--;
if (this.currentSize > 0) {
E lastItem = this.array.get(this.currentSize); // pas -1 car on prend l'élément qui était à la fin du tas AVANT le remove()
@ -152,8 +155,11 @@ public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
this.percolateDown(i);
this.percolateUp(i);
}
// On s'assure que l'on arrête le code ici pour ne pas renvoyer l'exception finale.
return;
} else {
// on a pas trouvé notre élément, on va donc continuer l'exploration des autres branches.
int indLeft = indexLeft(i);
// Si l'étiquette de gauche est plus grande que x, ça ne sert à rien de continuer sur cette branche car on est sur un tas min
if (indLeft < this.currentSize && this.array.get(indLeft).compareTo(x) <= 0) stack.push(indLeft);
@ -164,6 +170,7 @@ public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
}
}
// Si on arrive ici, ça veut dire que l'on n'a pas trouvé l'élément "x" dans le tas binaire.
throw new ElementNotFoundException(x);
}

View file

@ -2,6 +2,7 @@ package org.insa.graphs.algorithm.shortestpath;
public class AStarTest extends ShortestPathAlgorithmTest {
// Dijkstra et A* bénéficient de la même banque de test, ils héritent donc tous les deux de la classe ShortestPathAlgorithmTest
@Override
public ShortestPathSolution runAlgo(ShortestPathData algoData) {
final AStarAlgorithm algo = new AStarAlgorithm(algoData);

View file

@ -2,6 +2,7 @@ package org.insa.graphs.algorithm.shortestpath;
public class DijkstraTest extends ShortestPathAlgorithmTest {
// Dijkstra et A* bénéficient de la même banque de test, ils héritent donc tous les deux de la classe ShortestPathAlgorithmTest
@Override
public ShortestPathSolution runAlgo(ShortestPathData algoData) {
final DijkstraAlgorithm algo = new DijkstraAlgorithm(algoData);

View file

@ -40,11 +40,10 @@ public abstract class ShortestPathAlgorithmTest {
private static String MAP_HAITI = "/mnt/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps/haiti-and-domrep.mapgr";
private static String MAP_TOULOUSE = "/mnt/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps/toulouse.mapgr";
private static String MAP_FRANCE = "/mnt/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps/france.mapgr";
private static Graph carreGraph, insaGraph, haitiGraph, toulouseGraph, franceGraph;
protected static class TestScenario {
public final Graph graph; // chemin du fichier mapgr
public final Graph graph;
public final Node origin, destination;
public final ArcInspector arcInspector;
public final Status expectedStatus;
@ -62,6 +61,7 @@ public abstract class ShortestPathAlgorithmTest {
}
};
// Fonction utilitaire qui prend un chemin de fichier en entrée, charge le graphe associé et le renvoi.
private static Graph loadGraph(String pathname) {
Graph graph = null;
@ -99,7 +99,7 @@ public abstract class ShortestPathAlgorithmTest {
objects.add(new TestScenario(insaGraph, 286, 823, Status.OPTIMAL));
// Exemple trajet infaisable (composantes non connexes)
objects.add(new TestScenario(haitiGraph, 265362, 92314, Status.INFEASIBLE));
// Exemple trajet moyen, à pieds
// Exemple trajet moyen, à pied
objects.add(new TestScenario(toulouseGraph, 16824, 4028, Status.OPTIMAL, new ArcInspectorFactory.OnlyPedestrianByTime()));
// Exemple trajet de longueur nulle
objects.add(new TestScenario(insaGraph, 297, 297, Status.OPTIMAL));
@ -129,14 +129,15 @@ public abstract class ShortestPathAlgorithmTest {
final ShortestPathData algoData = new ShortestPathData(scenario.graph, scenario.origin, scenario.destination, scenario.arcInspector);
final ShortestPathSolution solutionDijkstra = this.runAlgo(algoData);
final ShortestPathSolution solutionAlgo = this.runAlgo(algoData);
final ShortestPathSolution solutionBellman = new BellmanFordAlgorithm(algoData).doRun();
final Path pathDijkstra = solutionDijkstra.getPath();
final Path pathAlgo = solutionAlgo.getPath();
final Path pathBellman = solutionBellman.getPath();
// Comparaison des chemins issus de Bellman et de Dijkstra/A*, en comparant les coûts.
double coutDijkstra = 0.0;
for (final Arc arc : pathDijkstra.getArcs()) coutDijkstra += scenario.arcInspector.getCost(arc);
for (final Arc arc : pathAlgo.getArcs()) coutDijkstra += scenario.arcInspector.getCost(arc);
double coutBellman = 0.0;
for (final Arc arc : pathBellman.getArcs()) coutBellman += scenario.arcInspector.getCost(arc);
@ -150,17 +151,20 @@ public abstract class ShortestPathAlgorithmTest {
final ShortestPathData algoData = new ShortestPathData(scenario.graph, scenario.origin, scenario.destination, scenario.arcInspector);
final ShortestPathSolution solution = this.runAlgo(algoData);
final Path dijkstraPath = solution.getPath();
final Path algoPath = solution.getPath();
double coutDijkstra = 0;
for (Arc arc : dijkstraPath.getArcs()) coutDijkstra += scenario.arcInspector.getCost(arc);
for (Arc arc : algoPath.getArcs()) coutDijkstra += scenario.arcInspector.getCost(arc);
// On doit comparer le cout de dijkstra (calculé au dessus) au cout du shortest path passant par les mêmes nodes.
final List<Node> nodes = dijkstraPath.getArcs().stream().map(arc -> arc.getOrigin()).collect(Collectors.toList());
if (!nodes.isEmpty()) nodes.add(dijkstraPath.getArcs().getLast().getDestination());
// On doit comparer le cout du PCC issu de l'algo Dijkstra/A* (calculé au dessus) au cout du shortest path (calculé dans la classe Path) passant par les mêmes nodes.
// D'abord on récupère tous les Nodes du PCC
final List<Node> nodes = algoPath.getArcs().stream().map(arc -> arc.getOrigin()).collect(Collectors.toList());
if (!nodes.isEmpty()) nodes.add(algoPath.getArcs().getLast().getDestination());
// Puis on calcule le coût avec la méthode Path#createShortestPathFromNodes
final Path computedPath = Path.createShortestPathFromNodes(scenario.graph, nodes);
// On calcule le coût de notre PCC.
double coutComputed = 0;
for (Arc arc : computedPath.getArcs()) coutComputed += scenario.arcInspector.getCost(arc);
// Comparaison finale
assertEquals(coutDijkstra, coutComputed, 0.0);
}
@ -171,28 +175,34 @@ public abstract class ShortestPathAlgorithmTest {
final ShortestPathSolution solution = this.runAlgo(algoData);
final Path solutionPath = solution.getPath();
// On s'assure que le status de la solution est bien celui attendu initialement
assertEquals(scenario.expectedStatus, solution.getStatus());
// Si on est sur un PCC faisable, on vérifie le respect de la règle que tout sous-chemin d'un PCC est lui même un PCC
if (solution.isFeasible()) {
final int solutionPathLength = solutionPath.getArcs().size();
// Pour se faire, on prend des portions partant du départ, jusqu'à des Nodes appartenant au PCC initial
for (int k = 1; k < (solutionPathLength/10); k++) {
// On calcule un sous chemin, partant du même origin, mais arrivant à un point intermédiaire de la solution globale
// On calcule un sous chemin, partant de la même origine, mais arrivant à un point intermédiaire de la solution globale
final Node middleNode = solutionPath.getArcs().get(k*5).getOrigin();
final ShortestPathData algoSousCheminData = new ShortestPathData(scenario.graph, scenario.origin, middleNode, scenario.arcInspector);
final ShortestPathSolution sousCheminSolution = this.runAlgo(algoSousCheminData);
// on s'assure qu'une solution a été trouvé pour le sous-chemin
assertTrue(sousCheminSolution.isFeasible());
final Path sousCheminPath = sousCheminSolution.getPath();
// Calcul du coût du sous-chemin
double sousCheminCost = 0.0;
System.out.println("SousCheminPathLength: " + sousCheminPath.getArcs().size());
for (final Arc arc : sousCheminPath.getArcs()) {
sousCheminCost += scenario.arcInspector.getCost(arc);
}
// Calcul du coût de la portion du PCC intial
double solutionCost = 0.0;
for (final Arc arc : solutionPath.getArcs()) {
solutionCost += scenario.arcInspector.getCost(arc);
if (arc.getDestination().equals(middleNode)) break; // Si on est arrivé au noeud intermédiaire, on arrête
}
// Comparaison finale
assertEquals(solutionCost, sousCheminCost, 0.0);
}
}

View file

@ -29,11 +29,13 @@ public class Path {
public static Path createFastestPathFromNodes(Graph graph, List<Node> nodes)
throws IllegalArgumentException {
Path path = null;
if (nodes.size() <= 1) {
// Pour créer un chemin correct, il nous faut au minimum deux Nodes.
if (nodes.size() < 2) {
path = new Path(graph, nodes.isEmpty() ? null : nodes.get(0));
} else {
List<Arc> arcs = new ArrayList<Arc>();
Node prev = null;
// Pour chaque Node à traverser, on choisi un arc qui connecte le Node courrant au Node suivant, en sélectionnant celui de coût minimal (ici la durée de trajet)
for (Node next : nodes) {
if (prev != null) {
Arc choosen = null;
@ -67,11 +69,13 @@ public class Path {
public static Path createShortestPathFromNodes(Graph graph, List<Node> nodes)
throws IllegalArgumentException {
Path path = null;
if (nodes.size() <= 1) {
// Pour créer un chemin correct, il nous faut au minimum deux Nodes.
if (nodes.size() < 2) {
path = new Path(graph, nodes.isEmpty() ? null : nodes.get(0));
} else {
List<Arc> arcs = new ArrayList<Arc>();
Node prev = null;
// Pour chaque Node à traverser, on choisi un arc qui connecte le Node courrant au Node suivant, en sélectionnant celui de coût minimal (ici la distance)
for (Node next : nodes) {
if (prev != null) {
Arc choosen = null;