Compare commits
11 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 0052d67e9f | |||
| cfb1accd01 | |||
| a53be0b057 | |||
| a6892ff0ca | |||
| b81286e6ba | |||
| 5a9e842f39 | |||
| 5fc064e863 | |||
| 0b08b70cb9 | |||
| 369b12ee76 | |||
| 195b069490 | |||
| a35c883c44 |
13 changed files with 601 additions and 20 deletions
|
|
@ -12,7 +12,7 @@ import org.insa.graphs.model.AccessRestrictions.AccessRestriction;
|
||||||
|
|
||||||
public class ArcInspectorFactory {
|
public class ArcInspectorFactory {
|
||||||
|
|
||||||
private static class NoFilterByLengthArcInspector implements ArcInspector {
|
public static class NoFilterByLengthArcInspector implements ArcInspector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAllowed(Arc arc) {
|
public boolean isAllowed(Arc arc) {
|
||||||
|
|
@ -40,7 +40,7 @@ public class ArcInspectorFactory {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static class OnlyCarsByLengthArcInspector
|
public static class OnlyCarsByLengthArcInspector
|
||||||
extends NoFilterByLengthArcInspector {
|
extends NoFilterByLengthArcInspector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -57,7 +57,7 @@ public class ArcInspectorFactory {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static class OnlyCarsByTimeArcInspector
|
public static class OnlyCarsByTimeArcInspector
|
||||||
extends NoFilterByLengthArcInspector {
|
extends NoFilterByLengthArcInspector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -76,9 +76,9 @@ public class ArcInspectorFactory {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
private static class OnlyPedestrianByTime implements ArcInspector {
|
public static class OnlyPedestrianByTime implements ArcInspector {
|
||||||
|
|
||||||
static final int maxPedestrianSpeed = 5;
|
public static final int maxPedestrianSpeed = 5;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isAllowed(Arc arc) {
|
public boolean isAllowed(Arc arc) {
|
||||||
|
|
@ -110,6 +110,72 @@ 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;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowed(Arc arc) {
|
||||||
|
return arc.getRoadInformation().getAccessRestrictions().isAllowedForAny(
|
||||||
|
AccessMode.BICYCLE,
|
||||||
|
EnumSet.complementOf(EnumSet.of(AccessRestriction.FORBIDDEN,
|
||||||
|
AccessRestriction.PRIVATE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getCost(Arc arc) {
|
||||||
|
return arc.getTravelTime(Math.min(maxBicycleSpeed, arc.getRoadInformation().getMaximumSpeed()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaximumSpeed() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mode getMode() {
|
||||||
|
return Mode.TIME;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Le plus rapide, chemin accessible en vélo uniquement";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ajout de l'ArcInspector qui évalue les PCC en distance à vélo
|
||||||
|
public static class BicyleByLength implements ArcInspector {
|
||||||
|
static int maxBicycleSpeed = 20;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAllowed(Arc arc) {
|
||||||
|
return arc.getRoadInformation().getAccessRestrictions().isAllowedForAny(
|
||||||
|
AccessMode.BICYCLE,
|
||||||
|
EnumSet.complementOf(EnumSet.of(AccessRestriction.FORBIDDEN,
|
||||||
|
AccessRestriction.PRIVATE)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public double getCost(Arc arc) {
|
||||||
|
return arc.getLength();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getMaximumSpeed() {
|
||||||
|
return 20;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Mode getMode() {
|
||||||
|
return Mode.LENGTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Le plus court, chemin accessible en vélo uniquement";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return List of all arc filters in this factory.
|
* @return List of all arc filters in this factory.
|
||||||
*/
|
*/
|
||||||
|
|
@ -118,7 +184,7 @@ public class ArcInspectorFactory {
|
||||||
// to get an understandable output!):
|
// to get an understandable output!):
|
||||||
return Arrays.asList(new NoFilterByLengthArcInspector(),
|
return Arrays.asList(new NoFilterByLengthArcInspector(),
|
||||||
new OnlyCarsByLengthArcInspector(), new OnlyCarsByTimeArcInspector(),
|
new OnlyCarsByLengthArcInspector(), new OnlyCarsByTimeArcInspector(),
|
||||||
new OnlyPedestrianByTime());
|
new OnlyPedestrianByTime(), new BicyleByLength(), new BicyleByTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,43 @@
|
||||||
package org.insa.graphs.algorithm.shortestpath;
|
package org.insa.graphs.algorithm.shortestpath;
|
||||||
|
|
||||||
|
import org.insa.graphs.model.Node;
|
||||||
|
|
||||||
public class AStarAlgorithm extends DijkstraAlgorithm {
|
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) {
|
public AStarAlgorithm(ShortestPathData data) {
|
||||||
super(data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Label createLabel(Node node) {
|
||||||
|
final ShortestPathData data = (ShortestPathData)this.data;
|
||||||
|
// Création du LabelStar "vide"
|
||||||
|
final LabelStar labelStar = new LabelStar(node);
|
||||||
|
|
||||||
|
// Contrairement à Dijkstra où 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
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TIME:
|
||||||
|
final Double speed = 1.5*(data.getMaximumSpeed()/3.6); // km/h converti en m/s, +50% pour garantir que l'estimation est une bonne strictement inférieure
|
||||||
|
labelStar.setCoutEstime(distance/speed); // en secondes
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return labelStar;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,134 @@
|
||||||
package org.insa.graphs.algorithm.shortestpath;
|
package org.insa.graphs.algorithm.shortestpath;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import org.insa.graphs.algorithm.AbstractSolution.Status;
|
||||||
|
import org.insa.graphs.algorithm.utils.BinaryHeap;
|
||||||
|
import org.insa.graphs.algorithm.utils.ElementNotFoundException;
|
||||||
|
import org.insa.graphs.model.Arc;
|
||||||
|
import org.insa.graphs.model.Node;
|
||||||
|
import org.insa.graphs.model.Path;
|
||||||
|
|
||||||
|
|
||||||
public class DijkstraAlgorithm extends ShortestPathAlgorithm {
|
public class DijkstraAlgorithm extends ShortestPathAlgorithm {
|
||||||
|
|
||||||
public DijkstraAlgorithm(ShortestPathData data) {
|
public DijkstraAlgorithm(ShortestPathData data) {
|
||||||
super(data);
|
super(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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. Cette fonction sera à remplacer lors de l'implémentation de A*
|
||||||
|
protected Label createLabel(Node node) {
|
||||||
|
return new Label(node);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ShortestPathSolution doRun() {
|
protected ShortestPathSolution doRun() {
|
||||||
|
|
||||||
// retrieve data from the input problem (getInputData() is inherited from the
|
|
||||||
// parent class ShortestPathAlgorithm)
|
|
||||||
final ShortestPathData data = getInputData();
|
final ShortestPathData data = getInputData();
|
||||||
|
|
||||||
// variable that will contain the solution of the shortest path problem
|
// 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;
|
||||||
|
|
||||||
|
final Node currentNode = currentLabel.getNode();
|
||||||
|
//System.out.println("currentNode = " + currentNode);
|
||||||
|
|
||||||
|
currentLabel.setMarque(true);
|
||||||
|
notifyNodeMarked(currentNode);
|
||||||
|
|
||||||
|
// On constate bien que les coûts des sommets marqués sont bel et bien croissants
|
||||||
|
// System.out.println("-> Sommet marqué: " + currentLabel.getCoutRealise());
|
||||||
|
|
||||||
|
// 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();
|
||||||
|
// 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));
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Arrivé ici, l'algorithme est soit arrivé à destination (dans ce cas destinationReached = true), soit il est sorti de sa composante connexe.
|
||||||
ShortestPathSolution solution = null;
|
ShortestPathSolution solution = null;
|
||||||
|
// Si on a trouvé un PCC
|
||||||
|
if (destinationReached) {
|
||||||
|
notifyDestinationReached(data.getDestination());
|
||||||
|
|
||||||
// TODO: implement the Dijkstra algorithm
|
// 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);
|
||||||
|
currentNode = arc.getOrigin();
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
// when the algorithm terminates, return the solution that has been found
|
|
||||||
return solution;
|
return solution;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
package org.insa.graphs.algorithm.shortestpath;
|
||||||
|
|
||||||
|
import org.insa.graphs.model.Arc;
|
||||||
|
import org.insa.graphs.model.Node;
|
||||||
|
import java.lang.Comparable;
|
||||||
|
|
||||||
|
public class Label implements Comparable<Label> {
|
||||||
|
private Node node;
|
||||||
|
private Arc arc;
|
||||||
|
private Double coutRealise;
|
||||||
|
private boolean marque;
|
||||||
|
|
||||||
|
public Label(Node node) {
|
||||||
|
this.node = node;
|
||||||
|
this.arc = null;
|
||||||
|
this.coutRealise = Double.MAX_VALUE;
|
||||||
|
this.marque = false;
|
||||||
|
}
|
||||||
|
public Label(Node node, Arc arc, Double coutRealise, boolean marque){
|
||||||
|
this.node = node;
|
||||||
|
this.arc = arc;
|
||||||
|
this.coutRealise = coutRealise;
|
||||||
|
this.marque = marque;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCoutRealise() { return this.coutRealise; }
|
||||||
|
public Node getNode() { return this.node; }
|
||||||
|
public Arc getArc() { return this.arc; }
|
||||||
|
public boolean getMarque() { return this.marque; }
|
||||||
|
|
||||||
|
public void setCoutRealise(Double cout) { this.coutRealise = cout; }
|
||||||
|
public void setArc(Arc arc) { this.arc = arc; }
|
||||||
|
public void setMarque(boolean marque) { this.marque = marque; }
|
||||||
|
|
||||||
|
// Méthode destinée à être override dans LabelStar
|
||||||
|
public Double getTotalCost() {
|
||||||
|
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) {
|
||||||
|
// 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) {
|
||||||
|
// si on compare des LabelStar, on compare l'estimation
|
||||||
|
if (this instanceof LabelStar && l instanceof LabelStar) {
|
||||||
|
int cmp2 = ((LabelStar)this).getCoutEstime().compareTo(((LabelStar)l).getCoutEstime());
|
||||||
|
if (cmp2 != 0) return cmp2;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Soit ce n'est pas des LabelStar, soit ils ont le même cout total ET la même estimation
|
||||||
|
return ((Integer) this.getNode().getId()).compareTo((Integer) l.getNode().getId());
|
||||||
|
} else {
|
||||||
|
return this.getTotalCost().compareTo(l.getTotalCost());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Label(Node=" + this.node.getId() + "; Coût=" + this.coutRealise + "; Marque=" + (this.marque ? "OUI" : "NON") + ") [" + this.hashCode() + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package org.insa.graphs.algorithm.shortestpath;
|
||||||
|
|
||||||
|
import org.insa.graphs.model.Arc;
|
||||||
|
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) {
|
||||||
|
super(node);
|
||||||
|
this.coutEstime = 0D;
|
||||||
|
}
|
||||||
|
public LabelStar(Node node, Arc arc, Double coutRealise, boolean marque, Double coutEstime){
|
||||||
|
super(node, arc, coutRealise, marque);
|
||||||
|
this.coutEstime = coutEstime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Double getCoutEstime() { return this.coutEstime; }
|
||||||
|
public void setCoutEstime(Double val) { this.coutEstime = val; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Double getTotalCost() {
|
||||||
|
return this.getCoutRealise() + this.getCoutEstime();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "LabelStar(Node=" + this.getNode().getId() + "; Coût=" + this.getCoutRealise() + "; Marque=" + (this.getMarque() ? "OUI" : "NON") + "; Coût estimé=" + this.getCoutEstime() + ") [" + this.hashCode() + "]";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package org.insa.graphs.algorithm.utils;
|
package org.insa.graphs.algorithm.utils;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Stack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implements a binary heap containing elements of type E. Note that all comparisons are
|
* Implements a binary heap containing elements of type E. Note that all comparisons are
|
||||||
|
|
@ -134,7 +135,43 @@ public class BinaryHeap<E extends Comparable<E>> implements PriorityQueue<E> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void remove(E x) throws ElementNotFoundException {
|
public void remove(E x) throws ElementNotFoundException {
|
||||||
// TODO:
|
if (this.isEmpty()) throw new ElementNotFoundException(x);
|
||||||
|
|
||||||
|
// 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 "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()
|
||||||
|
this.arraySet(i, lastItem);
|
||||||
|
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);
|
||||||
|
|
||||||
|
int indRight = indLeft+1;
|
||||||
|
// De même avec l'étiquette de droite
|
||||||
|
if (indRight < this.currentSize && this.array.get(indRight).compareTo(x) <= 0) stack.push(indRight);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
||||||
|
return algo.doRun();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
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);
|
||||||
|
return algo.doRun();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,210 @@
|
||||||
|
package org.insa.graphs.algorithm.shortestpath;
|
||||||
|
|
||||||
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertNotNull;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.DataInputStream;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.insa.graphs.algorithm.ArcInspector;
|
||||||
|
import org.insa.graphs.algorithm.ArcInspectorFactory;
|
||||||
|
import org.insa.graphs.algorithm.AbstractSolution.Status;
|
||||||
|
import org.insa.graphs.model.Arc;
|
||||||
|
import org.insa.graphs.model.Graph;
|
||||||
|
import org.insa.graphs.model.Node;
|
||||||
|
import org.insa.graphs.model.Path;
|
||||||
|
import org.insa.graphs.model.io.BinaryGraphReader;
|
||||||
|
import org.insa.graphs.model.io.GraphReader;
|
||||||
|
import org.junit.Assume;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.junit.runners.Parameterized;
|
||||||
|
import org.junit.runners.Parameterized.Parameter;
|
||||||
|
import org.junit.runners.Parameterized.Parameters;
|
||||||
|
|
||||||
|
@RunWith(Parameterized.class)
|
||||||
|
public abstract class ShortestPathAlgorithmTest {
|
||||||
|
|
||||||
|
public abstract ShortestPathSolution runAlgo(ShortestPathData algoData);
|
||||||
|
|
||||||
|
private static String MAP_CARRE = "/mnt/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps/carre.mapgr";
|
||||||
|
private static String MAP_INSA = "/mnt/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps/insa.mapgr";
|
||||||
|
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;
|
||||||
|
public final Node origin, destination;
|
||||||
|
public final ArcInspector arcInspector;
|
||||||
|
public final Status expectedStatus;
|
||||||
|
|
||||||
|
public TestScenario(Graph graph, int originId, int destinationId, Status expectedStatus, ArcInspector arcInspector) {
|
||||||
|
this.graph = graph;
|
||||||
|
this.origin = graph.get(originId);
|
||||||
|
this.destination = graph.get(destinationId);
|
||||||
|
this.expectedStatus = expectedStatus;
|
||||||
|
this.arcInspector = arcInspector;
|
||||||
|
}
|
||||||
|
|
||||||
|
public TestScenario(Graph graph, int originId, int destinationId, Status expectedStatus) {
|
||||||
|
this(graph, originId, destinationId, expectedStatus, new ArcInspectorFactory.NoFilterByLengthArcInspector()); // par défaut, aucune restriction sur les arcs
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
try (final GraphReader reader = new BinaryGraphReader(new DataInputStream(new BufferedInputStream(new FileInputStream(pathname))))) {
|
||||||
|
graph = reader.read();
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
System.err.println("> Impossible de trouver le fichier " + MAP_CARRE);
|
||||||
|
e.printStackTrace();
|
||||||
|
} catch (IOException e) {
|
||||||
|
System.err.println("> Impossible d'ouvrir le fichier " + MAP_CARRE);
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
return graph;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameters
|
||||||
|
public static Collection<Object> data() {
|
||||||
|
carreGraph = loadGraph(MAP_CARRE);
|
||||||
|
assertNotNull("Impossible de charger le graphe carré", carreGraph);
|
||||||
|
insaGraph = loadGraph(MAP_INSA);
|
||||||
|
assertNotNull("Impossible de charger le graphe INSA", insaGraph);
|
||||||
|
haitiGraph = loadGraph(MAP_HAITI);
|
||||||
|
assertNotNull("Impossible de charger le graphe Haïti", haitiGraph);
|
||||||
|
toulouseGraph = loadGraph(MAP_TOULOUSE);
|
||||||
|
assertNotNull("Impossible de charger le graphe Toulouse", toulouseGraph);
|
||||||
|
franceGraph = loadGraph(MAP_FRANCE);
|
||||||
|
assertNotNull("Impossible de charger le graphe France", franceGraph);
|
||||||
|
|
||||||
|
Collection<Object> objects = new ArrayList<>();
|
||||||
|
|
||||||
|
// Exemple trajet court, toutes routes
|
||||||
|
objects.add(new TestScenario(carreGraph, 9, 11, Status.OPTIMAL));
|
||||||
|
// Exemple trajet moyen, toutes routes
|
||||||
|
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, à 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));
|
||||||
|
// Exemple trajet très long
|
||||||
|
//objects.add(new TestScenario(franceGraph, 981717, 5539046, Status.OPTIMAL));
|
||||||
|
|
||||||
|
return objects;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Parameter
|
||||||
|
public TestScenario scenario;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testScenario() {
|
||||||
|
assertNotNull(scenario.graph);
|
||||||
|
assertNotNull(scenario.origin);
|
||||||
|
assertNotNull(scenario.destination);
|
||||||
|
assertNotNull(scenario.arcInspector);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareWithBellman() {
|
||||||
|
// On ne compare avec Bellman qui si le scenario est faisable
|
||||||
|
Assume.assumeTrue(scenario.expectedStatus == Status.FEASIBLE || scenario.expectedStatus == Status.OPTIMAL);
|
||||||
|
// Si un chemin de longueur nulle (origin==destination) on ne compare pas avec Bellman, qui ne respecte pas les mêmes conventions que notre Dijkstra
|
||||||
|
Assume.assumeFalse(scenario.origin.equals(scenario.destination));
|
||||||
|
|
||||||
|
final ShortestPathData algoData = new ShortestPathData(scenario.graph, scenario.origin, scenario.destination, scenario.arcInspector);
|
||||||
|
|
||||||
|
final ShortestPathSolution solutionAlgo = this.runAlgo(algoData);
|
||||||
|
final ShortestPathSolution solutionBellman = new BellmanFordAlgorithm(algoData).doRun();
|
||||||
|
|
||||||
|
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 : pathAlgo.getArcs()) coutDijkstra += scenario.arcInspector.getCost(arc);
|
||||||
|
double coutBellman = 0.0;
|
||||||
|
for (final Arc arc : pathBellman.getArcs()) coutBellman += scenario.arcInspector.getCost(arc);
|
||||||
|
|
||||||
|
assertEquals(coutBellman, coutDijkstra, 0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCompareCost() {
|
||||||
|
Assume.assumeTrue(scenario.expectedStatus == Status.FEASIBLE || scenario.expectedStatus == Status.OPTIMAL);
|
||||||
|
|
||||||
|
final ShortestPathData algoData = new ShortestPathData(scenario.graph, scenario.origin, scenario.destination, scenario.arcInspector);
|
||||||
|
final ShortestPathSolution solution = this.runAlgo(algoData);
|
||||||
|
|
||||||
|
final Path algoPath = solution.getPath();
|
||||||
|
double coutDijkstra = 0;
|
||||||
|
for (Arc arc : algoPath.getArcs()) coutDijkstra += scenario.arcInspector.getCost(arc);
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tout sous chemin d'un PCC est un PCC, on vérifie aussi si le status est cohérent avec les attentes
|
||||||
|
@Test
|
||||||
|
public void testConherence() {
|
||||||
|
final ShortestPathData algoData = new ShortestPathData(scenario.graph, scenario.origin, scenario.destination, scenario.arcInspector);
|
||||||
|
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 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -313,7 +313,8 @@ public abstract class PriorityQueueTest {
|
||||||
assertTrue(queue.isEmpty());
|
assertTrue(queue.isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
// On considère que ce test n'est pas pertinent, car on peut faire attention à TOUJOURS manipuler des tas min triés. Il faudra faire attention à que cela soit le cas lors de la programmation de Dijkstra
|
||||||
|
/*@Test
|
||||||
public void testRemoveThenAdd() {
|
public void testRemoveThenAdd() {
|
||||||
Assume.assumeFalse(queue.isEmpty());
|
Assume.assumeFalse(queue.isEmpty());
|
||||||
int min = Collections.min(Arrays.asList(parameters.data)).get();
|
int min = Collections.min(Arrays.asList(parameters.data)).get();
|
||||||
|
|
@ -327,6 +328,6 @@ public abstract class PriorityQueueTest {
|
||||||
assertEquals(parameters.data.length, queue.size());
|
assertEquals(parameters.data.length, queue.size());
|
||||||
assertEquals(min, queue.findMin().get());
|
assertEquals(min, queue.findMin().get());
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ public class Launch {
|
||||||
|
|
||||||
public static void main(String[] args) throws Exception {
|
public static void main(String[] args) throws Exception {
|
||||||
// Pour mapName: un fichier .mapgr pour un graphe simple, un fichier .mapfg pour une carte (le .mapgr associé doit être présent dans le même dossier)
|
// Pour mapName: un fichier .mapgr pour un graphe simple, un fichier .mapfg pour une carte (le .mapgr associé doit être présent dans le même dossier)
|
||||||
final String mapName = "C:\\Users\\Brendan\\Mon Drive\\INSA\\3MIC\\S6 - Graphes\\BE-Graphes\\Maps\\insa.mapfg";
|
final String mapName = "/mnt/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Maps/midi-pyrenees.mapgr";
|
||||||
final String pathName = "C:\\Users\\Brendan\\Mon Drive\\INSA\\3MIC\\S6 - Graphes\\BE-Graphes\\Paths\\path_fr31insa_rangueil_r2.path";
|
final String pathName = "/mnt/commetud/3eme Annee MIC/Graphes-et-Algorithmes/Paths/path_fr31insa_rangueil_r2.path";
|
||||||
|
|
||||||
final Graph graph;
|
final Graph graph;
|
||||||
final Path path;
|
final Path path;
|
||||||
|
|
|
||||||
|
|
@ -29,11 +29,13 @@ public class Path {
|
||||||
public static Path createFastestPathFromNodes(Graph graph, List<Node> nodes)
|
public static Path createFastestPathFromNodes(Graph graph, List<Node> nodes)
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
Path path = null;
|
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));
|
path = new Path(graph, nodes.isEmpty() ? null : nodes.get(0));
|
||||||
} else {
|
} else {
|
||||||
List<Arc> arcs = new ArrayList<Arc>();
|
List<Arc> arcs = new ArrayList<Arc>();
|
||||||
Node prev = null;
|
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) {
|
for (Node next : nodes) {
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
Arc choosen = null;
|
Arc choosen = null;
|
||||||
|
|
@ -67,11 +69,13 @@ public class Path {
|
||||||
public static Path createShortestPathFromNodes(Graph graph, List<Node> nodes)
|
public static Path createShortestPathFromNodes(Graph graph, List<Node> nodes)
|
||||||
throws IllegalArgumentException {
|
throws IllegalArgumentException {
|
||||||
Path path = null;
|
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));
|
path = new Path(graph, nodes.isEmpty() ? null : nodes.get(0));
|
||||||
} else {
|
} else {
|
||||||
List<Arc> arcs = new ArrayList<Arc>();
|
List<Arc> arcs = new ArrayList<Arc>();
|
||||||
Node prev = null;
|
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) {
|
for (Node next : nodes) {
|
||||||
if (prev != null) {
|
if (prev != null) {
|
||||||
Arc choosen = null;
|
Arc choosen = null;
|
||||||
|
|
|
||||||
2
pom.xml
2
pom.xml
|
|
@ -11,7 +11,7 @@
|
||||||
<name>be-graphes-all</name>
|
<name>be-graphes-all</name>
|
||||||
|
|
||||||
<properties>
|
<properties>
|
||||||
<jdk.version>17</jdk.version>
|
<jdk.version>21</jdk.version>
|
||||||
<maven.compiler.source>${jdk.version}</maven.compiler.source>
|
<maven.compiler.source>${jdk.version}</maven.compiler.source>
|
||||||
<maven.compiler.target>${jdk.version}</maven.compiler.target>
|
<maven.compiler.target>${jdk.version}</maven.compiler.target>
|
||||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue