From 17c0f306d3d4a2bbef247e23add2441bd640acbf Mon Sep 17 00:00:00 2001
From: adjemaou
Date: Fri, 31 Oct 2025 22:43:12 +0100
Subject: [PATCH 1/5] javadoc Node,Edge,Graph,undGraph
---
src/main/java/m1graphs2025/Edge.java | 110 ++-
src/main/java/m1graphs2025/Graph.java | 668 +++++++++++++++---
.../java/m1graphs2025/UndirectedGraph.java | 103 ++-
3 files changed, 755 insertions(+), 126 deletions(-)
diff --git a/src/main/java/m1graphs2025/Edge.java b/src/main/java/m1graphs2025/Edge.java
index 0527c3d..4a34376 100644
--- a/src/main/java/m1graphs2025/Edge.java
+++ b/src/main/java/m1graphs2025/Edge.java
@@ -7,8 +7,9 @@ import java.util.*;
* Une arête relie deux nœuds (source et destination) et peut éventuellement
* porter un poids si le graphe est pondéré.
*
- * Cette classe implémente l’interface Comparable afin de permettre
+ * Cette classe implémente l’interface {@link Comparable} afin de permettre
* le tri des arêtes selon leur poids, puis selon leurs nœuds.
+ *
*/
public class Edge implements Comparable {
@@ -29,27 +30,49 @@ public class Edge implements Comparable {
// Constructeurs
// ======================
- /** Arête non pondérée */
+ /**
+ * Crée une arête non pondérée entre deux nœuds.
+ *
+ * @param from Le nœud source.
+ * @param to Le nœud destination.
+ */
public Edge(Node from, Node to) {
this.from = from;
this.to = to;
this.weight = null;
}
+ /**
+ * Crée une arête vide (sans nœuds ni poids).
+ */
public Edge() {
this.from = null;
this.to = null;
this.weight = null;
}
- /** Arête pondérée */
+ /**
+ * Crée une arête pondérée entre deux nœuds.
+ *
+ * @param from Le nœud source.
+ * @param to Le nœud destination.
+ * @param weight Le poids de l’arête.
+ */
public Edge(Node from, Node to, Integer weight) {
this.from = from;
this.to = to;
this.weight = weight;
}
- /** Arête pondérée à partir d’IDs de nœuds dans un graphe donné */
+ /**
+ * Crée une arête non pondérée à partir des identifiants des nœuds dans un
+ * graphe donné.
+ *
+ * @param fromId L’identifiant du nœud source.
+ * @param toId L’identifiant du nœud destination.
+ * @param g Le graphe auquel appartiennent les nœuds.
+ * @throws IllegalArgumentException si le graphe est null.
+ */
public Edge(int fromId, int toId, Graph g) {
if (g == null) {
throw new IllegalArgumentException("Le graphe ne peut pas être null.");
@@ -71,6 +94,16 @@ public class Edge implements Comparable {
this.weight = 0; // ou une valeur par défaut
}
+ /**
+ * Crée une arête pondérée à partir des identifiants des nœuds dans un graphe
+ * donné.
+ *
+ * @param fromId L’identifiant du nœud source.
+ * @param toId L’identifiant du nœud destination.
+ * @param weight Le poids de l’arête.
+ * @param g Le graphe auquel appartiennent les nœuds.
+ * @throws IllegalArgumentException si le graphe est null.
+ */
public Edge(int fromId, int toId, int weight, Graph g) {
if (g == null) {
throw new IllegalArgumentException("Le graphe ne peut pas être null.");
@@ -96,26 +129,56 @@ public class Edge implements Comparable {
// Getters / Setters
// ======================
+ /**
+ * Retourne le nœud source de l’arête.
+ *
+ * @return Le nœud source.
+ */
public Node getNodeFrom() {
return from;
}
+ /**
+ * Retourne le nœud destination de l’arête.
+ *
+ * @return Le nœud destination.
+ */
public Node getNodeTo() {
return to;
}
+ /**
+ * Retourne le poids de l’arête.
+ *
+ * @return Le poids de l’arête, ou null si elle n’est pas pondérée.
+ */
public Integer getWeight() {
return weight;
}
+ /**
+ * Définit le nœud source de l’arête.
+ *
+ * @param from Le nouveau nœud source.
+ */
public void setNodeFrom(Node from) {
this.from = from;
}
+ /**
+ * Définit le nœud destination de l’arête.
+ *
+ * @param to Le nouveau nœud destination.
+ */
public void setNodeTo(Node to) {
this.to = to;
}
+ /**
+ * Définit le poids de l’arête.
+ *
+ * @param weight Le nouveau poids.
+ */
public void setWeight(Integer weight) {
this.weight = weight;
}
@@ -124,10 +187,20 @@ public class Edge implements Comparable {
// Méthodes fonctionnelles
// ======================
+ /**
+ * Retourne le nœud source de l’arête.
+ *
+ * @return Le nœud source.
+ */
public Node from() {
return from;
}
+ /**
+ * Retourne le nœud destination de l’arête.
+ *
+ * @return Le nœud destination.
+ */
public Node to() {
return to;
}
@@ -135,7 +208,7 @@ public class Edge implements Comparable {
/**
* Retourne une arête symétrique (inversée) avec la même pondération.
*
- * @return une nouvelle arête inversée ou null si les nœuds n’appartiennent pas
+ * @return Une nouvelle arête inversée ou null si les nœuds n’appartiennent pas
* au même graphe.
*/
public Edge getSymmetric() {
@@ -152,6 +225,8 @@ public class Edge implements Comparable {
/**
* Vérifie si l’arête est une boucle (relie un nœud à lui-même).
+ *
+ * @return true si l’arête est une boucle, false sinon.
*/
public boolean isSelfLoop() {
return from != null && to != null && from.getId() == to.getId();
@@ -160,6 +235,8 @@ public class Edge implements Comparable {
/**
* Vérifie s’il existe une autre arête reliant les mêmes nœuds
* mais avec un poids différent (arête multiple).
+ *
+ * @return true si une arête multiple existe, false sinon.
*/
public boolean isMultiEdge() {
if (from == null || from.getGraph() == null)
@@ -183,7 +260,11 @@ public class Edge implements Comparable {
return false;
}
- /** Indique si l’arête est pondérée. */
+ /**
+ * Indique si l’arête est pondérée.
+ *
+ * @return true si l’arête est pondérée, false sinon.
+ */
public boolean isWeighted() {
return this.weight != null;
}
@@ -195,6 +276,9 @@ public class Edge implements Comparable {
/**
* Compare deux arêtes selon leur poids, puis les identifiants des nœuds.
* Gère correctement les arêtes non pondérées (poids null).
+ *
+ * @param other L’autre arête à comparer.
+ * @return Un entier négatif, zéro ou un entier positif selon l’ordre.
*/
@Override
public int compareTo(Edge other) {
@@ -215,6 +299,9 @@ public class Edge implements Comparable {
/**
* Vérifie l’égalité entre deux arêtes :
* même nœud source, même destination, même poids.
+ *
+ * @param o L’objet à comparer.
+ * @return true si les arêtes sont égales, false sinon.
*/
@Override
public boolean equals(Object o) {
@@ -227,14 +314,23 @@ public class Edge implements Comparable {
&& Objects.equals(weight, edge.weight);
}
+ /**
+ * Retourne le code de hachage de l’arête.
+ *
+ * @return Le code de hachage.
+ */
@Override
public int hashCode() {
return Objects.hash(from, to, weight);
}
+ /**
+ * Retourne une représentation textuelle de l’arête.
+ *
+ * @return Une chaîne de caractères représentant l’arête.
+ */
@Override
public String toString() {
return "Edge(" + from.getId() + " -> " + to.getId() + ", w=" + weight + ")";
}
-
}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/Graph.java b/src/main/java/m1graphs2025/Graph.java
index a4bf39c..f0e305b 100644
--- a/src/main/java/m1graphs2025/Graph.java
+++ b/src/main/java/m1graphs2025/Graph.java
@@ -8,8 +8,7 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
+
import m1graphs2025.NodeVisitInfo.NodeColour;
@@ -20,16 +19,24 @@ public class Graph {
protected Map> adjEdList;
- // ======================
- // CONSTRUCTEURS
- // ======================
+ /**
+ * Constructeur principal pour initialiser un graphe avec une liste d'adjacence
+ * donnée.
+ *
+ * @param adjEdList La liste d'adjacence représentant le graphe.
+ */
public Graph(Map> adjEdList) {
this.adjEdList = adjEdList;
}
/**
- * Constructeur alternatif à partir d’un tableau de successeurs.
+ * Constructeur alternatif pour créer un graphe à partir d'un tableau de
+ * successeurs.
+ * Chaque nœud est suivi de ses successeurs dans le tableau, et un 0 marque la
+ * fin des successeurs d'un nœud.
+ *
+ * @param successorArray Tableau de successeurs pour initialiser le graphe.
*/
public Graph(int... successorArray) {
int taille = successorArray.length;
@@ -60,10 +67,22 @@ public class Graph {
// API DES NŒUDS
// ======================
+ /**
+ * Retourne le nombre de nœuds dans le graphe.
+ *
+ * @return Le nombre de nœuds.
+ */
public int nbNodes() {
return this.adjEdList.size();
}
+
+ /**
+ * Vérifie si le graphe contient un nœud donné.
+ *
+ * @param n Le nœud à vérifier.
+ * @return {@code true} si le nœud est présent, sinon {@code false}.
+ */
public boolean usesNode(Node n) {
if (n == null) {
System.err.println("Erreur: tentative de vérification d’un nœud null.");
@@ -72,6 +91,12 @@ public class Graph {
return usesNode(n.getId());
}
+ /**
+ * Vérifie si le graphe contient un nœud avec un identifiant donné.
+ *
+ * @param id L'identifiant du nœud.
+ * @return {@code true} si le nœud est présent, sinon {@code false}.
+ */
public boolean usesNode(int id) {
if (id <= 0) {
System.err.println("Erreur: identifiant de nœud invalide (" + id + ").");
@@ -80,6 +105,12 @@ public class Graph {
return getNode(id) != null;
}
+ /**
+ * Vérifie si un nœud appartient au graphe actuel.
+ *
+ * @param n Le nœud à vérifier.
+ * @return {@code true} si le nœud appartient au graphe, sinon {@code false}.
+ */
public boolean holdsNode(Node n) {
if (n == null) {
System.err.println("Erreur: tentative de vérification d’un nœud null.");
@@ -88,6 +119,12 @@ public class Graph {
return usesNode(n) && n.getGraph() == this;
}
+ /**
+ * Récupère un nœud par son identifiant.
+ *
+ * @param id L'identifiant du nœud.
+ * @return Le nœud correspondant, ou {@code null} s'il n'existe pas.
+ */
public Node getNode(int id) {
if (id <= 0) {
System.err.println("Erreur: identifiant de nœud invalide (" + id + ").");
@@ -105,6 +142,12 @@ public class Graph {
return null;
}
+ /**
+ * Récupère un nœud par son identifiant ou le crée s'il n'existe pas.
+ *
+ * @param id L'identifiant du nœud.
+ * @return Le nœud correspondant.
+ */
public Node getOrCreateNode(int id) {
return getNode(id) != null ? getNode(id) : new Node(id, this);
}
@@ -132,6 +175,12 @@ public class Graph {
return false;
}
+ /**
+ * Ajoute un nœud au graphe.
+ *
+ * @param n Le nœud à ajouter.
+ * @return {@code true} si le nœud a été ajouté, sinon {@code false}.
+ */
public boolean addNode(int id) {
if (id <= 0) {
System.err.println("Erreur: identifiant de nœud invalide (" + id + ").");
@@ -152,6 +201,12 @@ public class Graph {
return false;
}
+ /**
+ * Ajoute un nœud au graphe par son identifiant.
+ *
+ * @param id L'identifiant du nœud.
+ * @return {@code true} si le nœud a été ajouté, sinon {@code false}.
+ */
public boolean removeNode(Node n) {
if (n == null) {
System.err.println("Erreur: tentative de suppression d’un nœud null.");
@@ -181,6 +236,14 @@ public class Graph {
return removeNode(0);
}
+ /**
+ * Supprime le nœud d'identifiant donné du graphe.
+ * Si le nœud n'existe pas, une erreur est affichée et {@code false} est retourné.
+ * Si une exception est survenue lors de la suppression, une erreur est affichée et {@code false} est retourné.
+ *
+ * @param id L'identifiant du nœud à supprimer.
+ * @return {@code true} si le nœud a été supprimé, sinon {@code false}.
+ */
public boolean removeNode(int id) {
if (id <= 0) {
System.err.println("Erreur: identifiant de nœud invalide (" + id + ").");
@@ -205,6 +268,12 @@ public class Graph {
return new ArrayList<>(adjEdList.keySet());
}
+ /**
+ * Retourne l'identifiant le plus grand parmi tous les nœuds du graphe.
+ * Si le graphe est vide, une erreur est affichée et la valeur minimale de l'entier est retournée.
+ *
+ * @return L'identifiant le plus grand du graphe, ou Integer.MIN_VALUE si le graphe est vide.
+ */
public int largestNodeId() {
if (adjEdList == null || adjEdList.isEmpty()) {
System.err.println("Avertissement: le graphe est vide.");
@@ -220,6 +289,12 @@ public class Graph {
return maxId;
}
+ /**
+ * Retourne l'identifiant du nœud le plus petit dans le graphe.
+ * Si le graphe est vide, une valeur par défaut est renvoyée.
+ *
+ * @return L'identifiant du nœud le plus petit, ou {@code Integer.MAX_VALUE} si le graphe est vide.
+ */
public int smallestNodeId() {
if (adjEdList == null || adjEdList.isEmpty()) {
System.err.println("Avertissement: le graphe est vide.");
@@ -235,6 +310,14 @@ public class Graph {
return minId;
}
+ /**
+ * Récupère la liste des successeurs d'un nœud.
+ *
+ * @param n Le nœud dont on souhaite récupérer les successeurs.
+ * @return La liste des successeurs, ou une liste vide si une erreur survient.
+ * @throws NullPointerException si le nœud est null.
+ * @throws Exception si une erreur survient lors de la récupération des successeurs.
+ */
public List getSuccessors(Node n) {
if (n == null) {
System.err.println("Erreur: nœud nul dans getSuccessors().");
@@ -249,6 +332,15 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des successeurs du nœud d'identifiant id.
+ * Si le nœud n'existe pas, une exception est levée.
+ * Si une erreur survient lors de la récupération des successeurs, un message d'erreur est affiché et une liste vide est retournée.
+ *
+ * @param id L'identifiant du nœud.
+ * @return liste des successeurs du nœud.
+ * @throws Exception si une erreur survient lors de la récupération des successeurs.
+ */
public List getSuccessors(int id) {
Node u = getNode(id);
if (u == null) {
@@ -258,6 +350,14 @@ public class Graph {
return getSuccessors(u);
}
+ /**
+ * Retourne la liste des successeurs du nœud, incluant les doublons (multi-arêtes).
+ * Les doublons sont éliminés.
+ *
+ * @param n Le nœud dont on souhaite récupérer les successeurs.
+ * @return liste des successeurs du nœud.
+ * @throws Exception si une erreur survient lors de la récupération des successeurs.
+ */
public List getSuccessorsMulti(Node n) {
if (n == null) {
System.err.println("Erreur: nœud nul dans getSuccessorsMulti().");
@@ -272,6 +372,15 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des successeurs, incluant les doublons (multi-arêtes),
+ * pour le nœud d'identifiant donné.
+ * Si le nœud n'existe pas, une erreur est affichée.
+ * Si une exception est survenue, un message d'erreur est affiché.
+ *
+ * @param id L'identifiant du nœud.
+ * @return liste complète des successeurs
+ */
public List getSuccessorsMulti(int id) {
Node u = getNode(id);
if (u == null) {
@@ -281,6 +390,16 @@ public class Graph {
return getSuccessorsMulti(u);
}
+ /**
+ * Vérifie si deux nœuds sont adjacents.
+ * Deux nœuds sont adjacents s'il existe une arête entre eux (dans les deux sens).
+ * Si un des nœuds est nul, une erreur est affichée.
+ * Si une exception est survenue, un message d'erreur est affiché.
+ *
+ * @param u nœud à tester
+ * @param v nœud à tester
+ * @return true si les deux nœuds sont adjacents, false sinon
+ */
public boolean adjacent(Node u, Node v) {
if (u == null || v == null) {
System.err.println("Erreur: un des nœuds est nul dans adjacent().");
@@ -295,6 +414,16 @@ public class Graph {
}
}
+ /**
+ * Vérifie si deux nœuds sont adjacents en fonction de leurs identifiants.
+ * Deux nœuds sont adjacents s'il existe une arête entre eux (dans les deux sens).
+ * Si un des identifiants est invalide, une erreur est affichée.
+ * Si une exception est survenue, un message d'erreur est affiché.
+ *
+ * @param uId identifiant du premier nœud à tester
+ * @param vId identifiant du second nœud à tester
+ * @return true si les deux nœuds sont adjacents, false sinon
+ */
public boolean adjacent(int uId, int vId) {
Node u = getNode(uId);
Node v = getNode(vId);
@@ -375,6 +504,13 @@ public class Graph {
// API DES ARÊTES
// ======================
+ /**
+ * Retourne le nombre d'arêtes du graphe.
+ * La méthode parcourt la liste d'adjacence du graphe et somme le nombre d'arêtes
+ * de chaque liste.
+ *
+ * @return nombre d'arêtes du graphe
+ */
public int nbEdges() {
int count = 0;
for (List edges : adjEdList.values())
@@ -382,6 +518,17 @@ public class Graph {
return count;
}
+ /**
+ * Vérifie si une arête existe entre deux nœuds donnés.
+ * Si l'un des nœuds est nul, une exception est levée.
+ * Si une erreur survient lors de la vérification, un message d'erreur est affiché
+ * et la méthode renvoie false.
+ *
+ * @param u Premier nœud.
+ * @param v Second nœud.
+ * @return true si l'arête existe, false sinon.
+ * @throws IllegalArgumentException si l'un des nœuds est nul.
+ */
public boolean existsEdge(Node u, Node v) {
try {
if (u == null || v == null) {
@@ -398,6 +545,18 @@ public class Graph {
}
}
+ /**
+ * Vérifie si une arête existe entre deux nœuds donnés par leur identifiants.
+ * Si l'un des nœuds n'existe pas, une exception est levée.
+ * Si une erreur survient lors de la vérification, un message d'erreur est affiché
+ * et la méthode renvoie false.
+ *
+ * @param uId L'identifiant du premier nœud.
+ * @param vId L'identifiant du second nœud.
+ * @return true si l'arête existe, false sinon.
+ * @throws IllegalArgumentException si l'un des identifiants est négatif
+ * ou si un ou plusieurs nœuds n'existent pas.
+ */
public boolean existsEdge(int uId, int vId) {
try {
if (uId < 0 || vId < 0) {
@@ -427,6 +586,18 @@ public class Graph {
}
}
+ /**
+ * Vérifie si une arête multiple existe entre deux nœuds donnés.
+ * Une arête multiple est une arête qui relie les mêmes nœuds mais avec un poids différent.
+ *
+ * Si l'un des nœuds n'existe pas, une exception est levée.
+ * Si une erreur survient lors de la vérification, un message d'erreur est affiché et false est retourné.
+ *
+ * @param u Nœud source.
+ * @param v Nœud destination.
+ * @return true si une arête multiple existe, false sinon.
+ * @throws IllegalArgumentException si l'un des nœuds est null.
+ */
public boolean isMultiEdge(Node u, Node v) {
try {
if (u == null || v == null) {
@@ -440,6 +611,16 @@ public class Graph {
}
}
+ /**
+ * Vérifie si le graphe contient une arête multiple (relie deux nœuds
+ * avec un poids différent) entre les nœuds d'identifiants uId et vId.
+ *
+ * @param uId L'identifiant du premier nœud.
+ * @param vId L'identifiant du second nœud.
+ * @return true si une arête multiple existe, false sinon.
+ * @throws IllegalArgumentException Si l'un des identifiants de nœuds est négatif.
+ * @throws NullPointerException Si l'un des nœuds n'existe pas.
+ */
public boolean isMultiEdge(int uId, int vId) {
try {
if (uId < 0 || vId < 0) {
@@ -472,6 +653,13 @@ public class Graph {
}
}
+ /**
+ * Ajoute une arête reliant deux nœuds.
+ *
+ * @param fromId identifiant du nœud source
+ * @param toId identifiant du nœud destination
+ * @throws IllegalArgumentException si les identifiants de nœuds sont négatifs
+ */
public void addEdge(int fromId, int toId) {
try {
if (fromId < 0 || toId < 0) {
@@ -489,6 +677,13 @@ public class Graph {
}
}
+ /**
+ * Ajoute une arête au graphe.
+ * Si l'arête existe déjà, lance une exception IllegalArgumentException.
+ *
+ * @param e l'arête à ajouter
+ * @throws IllegalArgumentException si l'arête existe déjà
+ */
public void addEdge(Edge e) {
try {
if (e == null) {
@@ -504,6 +699,16 @@ public class Graph {
}
}
+ /**
+ * Ajoute une arête au graphe.
+ *
+ * @param from Le nœud source.
+ * @param to Le nœud destination.
+ * @param weight Le poids de l'arête.
+ * @throws IllegalArgumentException si les nœuds source et destination sont null
+ * ou si le poids est négatif.
+ * @throws Exception si une erreur survient lors de l'ajout de l'arête
+ */
public void addEdge(Node from, Node to, int weight) {
try {
if (from == null || to == null) {
@@ -522,6 +727,16 @@ public class Graph {
}
}
+ /**
+ * Ajoute une arête au graphe.
+ *
+ * @param fromId L'identifiant du nœud source.
+ * @param toId L'identifiant du nœud destination.
+ * @param weight Le poids de l'arête.
+ * @throws IllegalArgumentException si les identifiants de nœuds sont négatifs
+ * ou si le poids est négatif.
+ * @throws Exception si une erreur survient lors de l'ajout de l'arête
+ */
public void addEdge(int fromId, int toId, int weight) {
try {
if (fromId < 0 || toId < 0) {
@@ -542,6 +757,18 @@ public class Graph {
}
}
+ /**
+ * Ajoute une arête au graphe.
+ * L'arête est identifiée par ses nœuds source et destination.
+ * Si l'arête n'existe pas, la méthode l'ajoute.
+ * Si une exception est survenue, un message d'erreur est affiché.
+ *
+ * @param fromId L'identifiant du nœud source.
+ * @param toId L'identifiant du nœud destination.
+ * @param g Le graphe.
+ * @throws IllegalArgumentException si le graphe est null ou si les identifiants de nœuds sont négatifs.
+ * @throws Exception si une erreur survient lors de l'ajout de l'arête.
+ */
public void addEdge(int fromId, int toId, Graph g) {
try {
if (g == null) {
@@ -562,6 +789,18 @@ public class Graph {
}
}
+ /**
+ * Supprime une arête du graphe.
+ * L'arête est identifiée par ses nœuds source et destination.
+ * Si l'arête n'existe pas, la méthode retourne false.
+ * Si une exception est survenue, un message d'erreur est affiché et la méthode retourne false.
+ *
+ * @param from Le nœud source.
+ * @param to Le nœud destination.
+ * @return true si l'arête a été supprimée, false sinon.
+ * @throws IllegalArgumentException si les nœuds source et destination sont null.
+ * @throws Exception si une erreur survient lors de la suppression de l'arête.
+ */
public boolean removeEdge(Node from, Node to) {
try {
if (from == null || to == null) {
@@ -575,6 +814,19 @@ public class Graph {
}
}
+ /**
+ * Supprime une arête du graphe.
+ * L'arête est identifiée par ses nœuds source et destination.
+ * Si l'arête n'existe pas, une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et false est retourné.
+ *
+ * @param fromId L'identifiant du nœud source.
+ * @param toId L'identifiant du nœud destination.
+ * @return true si l'arête a été supprimée, false sinon.
+ * @throws IllegalArgumentException si l'un des identifiants de nœuds est négatif
+ * ou si l'un des nœuds n'existe pas.
+ * @throws Exception si une erreur survient lors de la suppression de l'arête
+ */
public boolean removeEdge(int fromId, int toId) {
try {
if (fromId < 0 || toId < 0) {
@@ -592,6 +844,16 @@ public class Graph {
}
}
+ /**
+ * Supprime une arête du graphe.
+ * Si l'arête n'existe pas, une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et false est retourné.
+ *
+ * @param e L'arête à supprimer.
+ * @return true si l'arête a été supprimée, false sinon.
+ * @throws IllegalArgumentException si l'arête est null
+ * @throws Exception si une erreur survient lors de la suppression de l'arête
+ */
public boolean removeEdge(Edge e) {
try {
if (e == null) {
@@ -604,6 +866,17 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des arêtes sortantes depuis le nœud n.
+ * Les arêtes sont cherchées dans la liste d'adjacence du graphe.
+ * Si le nœud est null, une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et une liste vide est retournée.
+ *
+ * @param n Le nœud.
+ * @return liste des arêtes sortantes.
+ * @throws IllegalArgumentException si le nœud est null
+ * @throws Exception si une erreur survient lors de la récupération des arêtes
+ */
public List getOutEdges(Node n) {
try {
if (n == null) {
@@ -616,6 +889,18 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des arêtes sortantes depuis le nœud d'identifiant id.
+ * Les arêtes sont cherchées dans la liste d'adjacence du graphe.
+ * Si l'identifiant du nœud est négatif ou si le nœud n'existe pas,
+ * une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et une liste vide est retournée.
+ *
+ * @param id L'identifiant du nœud.
+ * @return liste des arêtes sortantes depuis le nœud.
+ * @throws IllegalArgumentException si l'identifiant du nœud est négatif ou si le nœud n'existe pas
+ * @throws Exception si une erreur survient lors de la récupération des arêtes
+ */
public List getOutEdges(int id) {
try {
if (id < 0) {
@@ -632,6 +917,15 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des arêtes entrantes vers le nœud n.
+ * Les arêtes sont cherchées dans la liste d'adjacence du graphe.
+ * Si le nœud est null, une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et une liste vide est retournée.
+ *
+ * @param n nœud
+ * @return liste des arêtes entrantes vers le nœud n
+ */
public List getInEdges(Node n) {
try {
if (n == null) {
@@ -651,6 +945,19 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des arêtes entrantes vers le nœud d'identifiant id.
+ * Les arêtes sont cherchées dans la liste d'adjacence du graphe.
+ * Si l'identifiant du nœud est négatif, une exception est levée.
+ * Si le nœud n'existe pas, une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et une liste vide est retournée.
+ *
+ * @param id L'identifiant du nœud.
+ * @return liste des arêtes entrantes vers le nœud.
+ * @throws IllegalArgumentException si l'identifiant du nœud est négatif
+ * ou si le nœud n'existe pas.
+ * @throws Exception si une erreur survient lors de la récupération des arêtes
+ */
public List getInEdges(int id) {
try {
if (id < 0) {
@@ -667,6 +974,16 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des arêtes incidentes au nœud n.
+ * Une arête incidente est soit une arête entrante, soit une arête sortante.
+ * Les arêtes sont cherchées dans la liste d'adjacence du graphe.
+ * Si le nœud est null, une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et une liste vide est retournée.
+ *
+ * @param n nœud
+ * @return liste des arêtes incidentes au nœud n
+ */
public List getIncidentEdges(Node n) {
try {
if (n == null) {
@@ -681,6 +998,17 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des arêtes incidentes au nœud d'identifiant id.
+ * Une arête est dite incidente si elle est soit entrante, soit sortante
+ * depuis le nœud.
+ *
+ * @param id L'identifiant du nœud.
+ * @return liste des arêtes incidentes au nœud.
+ * @throws IllegalArgumentException si l'identifiant du nœud est négatif
+ * ou si le nœud n'existe pas.
+ * @throws Exception si une erreur survient lors de la récupération des arêtes
+ */
public List getIncidentEdges(int id) {
try {
if (id < 0) {
@@ -697,6 +1025,16 @@ public class Graph {
}
}
+ /**
+ * Retourne la liste des arêtes reliant le nœud source u au nœud destination v.
+ * Les arêtes sont cherchées dans la liste d'adjacence du graphe.
+ * Si les nœuds source ou destination sont null, une exception est levée.
+ * Si une exception est survenue, un message d'erreur est affiché et une liste vide est retournée.
+ *
+ * @param u nœud source
+ * @param v nœud destination
+ * @return liste des arêtes reliant u à v
+ */
public List getEdges(Node u, Node v) {
try {
if (u == null || v == null) {
@@ -714,6 +1052,11 @@ public class Graph {
}
}
+ /**
+ * Returns a list of all edges in the graph.
+ * If an exception occurs while retrieving the edges, an empty list is returned and an error message is printed.
+ * @return list of all edges in the graph
+ */
public List getAllEdges() {
try {
List result = new ArrayList<>();
@@ -743,6 +1086,15 @@ public class Graph {
return list.stream().mapToInt(Integer::intValue).toArray();
}
+ /**
+ * Converts the graph to an adjacency matrix.
+ * The matrix is a square matrix of size n x n, where n is the largest node ID in the graph plus one.
+ * The value of matrix[i][j] is the number of edges from node i to node j.
+ * If there are no edges from node i to node j, then matrix[i][j] is 0.
+ * The matrix is symmetric, meaning matrix[i][j] is equal to matrix[j][i], because the graph is undirected.
+ * The matrix can be used to quickly check if there is an edge between two nodes, or to find all the edges between two nodes.
+ * @return the adjacency matrix of the graph
+ */
public int[][] toAdjMatrix() {
int n = largestNodeId() + 1;
int[][] matrix = new int[n][n];
@@ -754,6 +1106,12 @@ public class Graph {
return matrix;
}
+ /**
+ * Returns a new graph with all edges reversed.
+ * The reversed graph of G is a graph G' such that for every edge (u, v) in G, there is an edge (v, u) in G'.
+ * The weight of an edge in the reversed graph is the same as in the original graph.
+ * @return the reversed graph of this graph
+ */
public Graph getReverse() {
Map> reversed = new HashMap<>();
for (Node n : adjEdList.keySet())
@@ -768,6 +1126,14 @@ public class Graph {
return new Graph(reversed);
}
+ /**
+ * Returns the transitive closure of the graph.
+ * The transitive closure of a graph is a graph that contains an edge (u, v)
+ * whenever there is a path from u to v in the original graph.
+ * The weight of an edge in the transitive closure is the minimum weight of
+ * a path from u to v in the original graph.
+ * @return the transitive closure of the graph
+ */
public Graph getTransitiveClosure() {
Graph closure = this.copy();
List nodes = closure.getAllNodes();
@@ -785,6 +1151,12 @@ public class Graph {
return closure;
}
+ /**
+ * Indique si ce graphe est un multigraphe (au moins une paire de nœuds
+ * est reliée par plusieurs arêtes).
+ *
+ * @return true si ce graphe est un multigraphe, false sinon.
+ */
public boolean isMultiGraph() {
for (Node u : adjEdList.keySet()) {
List edges = adjEdList.get(u);
@@ -809,10 +1181,30 @@ public class Graph {
return false;
}
+
+ /**
+ * Returns true if and only if this graph is a simple graph.
+ * A simple graph is a graph which does not contain any self-loops
+ * (edges connecting a node to itself) and does not contain any
+ * multiple edges between any two nodes.
+ * @return true if this graph is a simple graph, false otherwise.
+ */
public boolean isSimpleGraph() {
return !isMultiGraph() && !hasSelfLoops();
}
+ /**
+ * Returns a simple graph equivalent to this graph.
+ *
+ * A simple graph is a graph which does not contain any self-loops
+ * (edges connecting a node to itself) and does not contain any
+ * multiple edges between any two nodes.
+ *
+ * The returned graph is a new instance of the same class as this
+ * graph. If this graph is an instance of a subclass of Graph, the
+ * returned graph is also an instance of the same subclass.
+ * @return a simple graph equivalent to this graph.
+ */
public Graph toSimpleGraph() {
Graph g = new Graph(new HashMap<>());
for (Node u : adjEdList.keySet())
@@ -830,6 +1222,19 @@ public class Graph {
return g;
}
+
+ /**
+ * Returns a deep copy of this graph.
+ *
+ * Each node and edge of the returned graph is a new instance, but
+ * their properties are the same as the corresponding nodes and edges of
+ * this graph.
+ *
+ * The returned graph is a new instance of the same class as this
+ * graph. If this graph is an instance of a subclass of Graph, the
+ * returned graph is also an instance of the same subclass.
+ * @return a deep copy of this graph.
+ */
public Graph copy() {
Map> copy = new HashMap<>();
for (Node u : adjEdList.keySet()) {
@@ -845,6 +1250,13 @@ public class Graph {
// Parcours DFS / BFS
// ======================
+ /**
+ * Effectue une recherche en profondeur (DFS) sur le graphe et retourne la liste
+ * des nœuds visités.
+ *
+ * @return Une liste des nœuds visités dans l'ordre de la recherche en
+ * profondeur.
+ */
public List getDFS() {
List visitedNodeList = new ArrayList<>();
Map infos = initVisitInfo();
@@ -856,6 +1268,14 @@ public class Graph {
return visitedNodeList;
}
+ /**
+ * Effectue une recherche en profondeur (DFS) à partir d'un nœud de départ
+ * donné.
+ *
+ * @param start Le nœud de départ pour la recherche en profondeur.
+ * @return Une liste des nœuds visités dans l'ordre de la recherche en
+ * profondeur.
+ */
public List getDFS(Node start) {
List visited = new ArrayList<>();
Map infos = initVisitInfo();
@@ -864,11 +1284,32 @@ public class Graph {
return visited;
}
+ /**
+ * Effectue une recherche en profondeur (DFS) à partir d'un identifiant de nœud de départ
+ * donné.
+ *
+ * @param startId L'identifiant du nœud de départ pour la recherche en
+ * profondeur.
+ * @return Une liste des nœuds visités dans l'ordre de la recherche en
+ * profondeur. Si le nœud de départ n'existe pas, une liste vide est
+ * renvoyée.
+ */
public List getDFS(int startId) {
Node s = getNode(startId);
return s == null ? new ArrayList<>() : getDFS(s);
}
+ /**
+ * Effectue une visite récursive d'un nœud et de ses successeurs.
+ * La méthode utilise une liste de nœuds visités, une map des informations de
+ * visite des nœuds et un comptage du temps de découverte et de fin de
+ * visite.
+ *
+ * @param u Le nœud à visiter.
+ * @param time Le comptage du temps de découverte et de fin de visite.
+ * @param infos La map des informations de visite des nœuds.
+ * @param visited La liste des nœuds visités.
+ */
private void DFSVisit(Node u, AtomicInteger time, Map infos, List visited) {
time.incrementAndGet();
NodeVisitInfo info = infos.get(u);
@@ -886,6 +1327,15 @@ public class Graph {
visited.add(u);
}
+ /**
+ * Effectue une visite en largeur d'un graphe.
+ * La méthode utilise une liste de nœuds visités, une map des informations de
+ * visite des nœuds et un comptage du temps de découverte et de fin de
+ * visite.
+ *
+ * @return Une liste des nœuds visités dans l'ordre de la recherche en
+ * largeur. Si le graphe est vide, une liste vide est renvoyée.
+ */
public List getBFS() {
List visited = new ArrayList<>();
Map infos = initVisitInfo();
@@ -895,6 +1345,15 @@ public class Graph {
return visited;
}
+ /**
+ * Effectue une recherche en largeur (BFS) à partir d'un nœud de départ
+ * donné.
+ *
+ * @param start Le nœud de départ pour la recherche en largeur.
+ * @return Une liste des nœuds visités dans l'ordre de la recherche en
+ * largeur. Si le nœud de départ n'existe pas, une liste vide est
+ * renvoyée.
+ */
public List getBFS(Node start) {
List visited = new ArrayList<>();
Map infos = initVisitInfo();
@@ -907,6 +1366,17 @@ public class Graph {
return s == null ? new ArrayList<>() : getBFS(s);
}
+ /**
+ * Effectue une visite en largeur d'un graphe à partir d'un nœud de départ
+ * donné.
+ * La méthode utilise une file d'attente pour stocker les nœuds à visiter,
+ * une map des informations de visite des nœuds et un comptage du temps de
+ * découverte et de fin de visite.
+ *
+ * @param s Le nœud de départ pour la recherche en largeur.
+ * @param infos La map des informations de visite des nœuds.
+ * @param visited La liste des nœuds visités.
+ */
private void BFSVisit(Node s, Map infos, List visited) {
Queue q = new LinkedList<>();
infos.get(s).setColour(NodeColour.GRAY);
@@ -930,80 +1400,89 @@ public class Graph {
// ======================
// DFS enrichi (59-60)
// ======================
+/**
+ * Effectue une recherche en profondeur (DFS) sur le graphe en utilisant des informations de visite pour les nœuds et les arêtes.
+ *
+ * @param nodeVisit Une carte associant chaque nœud à ses informations de visite.
+ * @param edgeVisit Une carte associant chaque arête à son type de visite.
+ * @return Une liste des nœuds visités dans l'ordre de la recherche en profondeur.
+ */
+public List getDFSWithVisitInfo(Map nodeVisit,
+ Map edgeVisit) {
+ List visited = new ArrayList<>();
+ AtomicInteger time = new AtomicInteger(0);
+ initVisitInfo(nodeVisit);
+ for (Node u : sortNodes()) {
+ if (nodeVisit.get(u).getColour() == NodeColour.WHITE)
+ DFSVisitEnriched(u, time, nodeVisit, edgeVisit, visited);
+ }
+ return visited;
+}
- public List getDFSWithVisitInfo(Map nodeVisit,
- Map edgeVisit) {
- List visited = new ArrayList<>();
- AtomicInteger time = new AtomicInteger(0);
- initVisitInfo(nodeVisit);
- for (Node u : sortNodes()) {
- if (nodeVisit.get(u).getColour() == NodeColour.WHITE)
- DFSVisitEnriched(u, time, nodeVisit, edgeVisit, visited);
- }
- return visited;
- }
-
- public List getDFSWithVisitInfo(Node start,
- Map nodeVisit,
- Map edgeVisit) {
- List visited = new ArrayList<>();
- AtomicInteger time = new AtomicInteger(0);
- initVisitInfo(nodeVisit);
- DFSVisitEnriched(start, time, nodeVisit, edgeVisit, visited);
- return visited;
- }
+/**
+ * Effectue une recherche en profondeur (DFS) sur le graphe à partir d'un nœud de départ donné,
+ * en utilisant des informations de visite pour les nœuds et les arêtes.
+ *
+ * @param start Le nœud de départ pour la recherche en profondeur.
+ * @param nodeVisit Une carte associant chaque nœud à ses informations de visite.
+ * @param edgeVisit Une carte associant chaque arête à son type de visite.
+ * @return Une liste des nœuds visités dans l'ordre de la recherche en profondeur.
+ */
+public List getDFSWithVisitInfo(Node start,
+ Map nodeVisit,
+ Map edgeVisit) {
+ List visited = new ArrayList<>();
+ AtomicInteger time = new AtomicInteger(0);
+ initVisitInfo(nodeVisit);
+ DFSVisitEnriched(start, time, nodeVisit, edgeVisit, visited);
+ return visited;
+}
- private void DFSVisitEnriched(Node u, AtomicInteger time,
- Map nodeVisit,
- Map edgeVisit,
- List visited) {
- time.incrementAndGet();
- nodeVisit.get(u).setDiscoveryTime(time.get());
- nodeVisit.get(u).setColour(NodeColour.GRAY);
- for (Edge e : getOutEdges(u)) {
- Node v = e.getNodeTo();
- NodeColour c = nodeVisit.get(v).getColour();
- if (c == NodeColour.WHITE) {
- edgeVisit.put(e, EdgeVisitType.TREE);
- nodeVisit.get(v).setPredecessor(u);
- DFSVisitEnriched(v, time, nodeVisit, edgeVisit, visited);
- } else if (c == NodeColour.GRAY) {
- edgeVisit.put(e, EdgeVisitType.BACKWARD);
- } else {
- // Noir
- if (nodeVisit.get(u).getDiscoveryTime() < nodeVisit.get(v).getDiscoveryTime())
- edgeVisit.put(e, EdgeVisitType.FORWARD);
- else
- edgeVisit.put(e, EdgeVisitType.CROSS);
- }
+/**
+ * Effectue une visite enrichie d'un nœud dans le cadre de la recherche en profondeur (DFS),
+ * en mettant à jour les informations de visite des nœuds et des arêtes.
+ *
+ * @param u Le nœud actuellement visité.
+ * @param time Un compteur atomique pour suivre les temps de découverte et de fin.
+ * @param nodeVisit Une carte associant chaque nœud à ses informations de visite.
+ * @param edgeVisit Une carte associant chaque arête à son type de visite.
+ * @param visited Une liste des nœuds visités dans l'ordre de la recherche en profondeur.
+ */
+private void DFSVisitEnriched(Node u, AtomicInteger time,
+ Map nodeVisit,
+ Map edgeVisit,
+ List visited) {
+ time.incrementAndGet();
+ nodeVisit.get(u).setDiscoveryTime(time.get());
+ nodeVisit.get(u).setColour(NodeColour.GRAY);
+ for (Edge e : getOutEdges(u)) {
+ Node v = e.getNodeTo();
+ NodeColour c = nodeVisit.get(v).getColour();
+ if (c == NodeColour.WHITE) {
+ edgeVisit.put(e, EdgeVisitType.TREE);
+ nodeVisit.get(v).setPredecessor(u);
+ DFSVisitEnriched(v, time, nodeVisit, edgeVisit, visited);
+ } else if (c == NodeColour.GRAY) {
+ edgeVisit.put(e, EdgeVisitType.BACKWARD);
+ } else {
+ // Noir
+ if (nodeVisit.get(u).getDiscoveryTime() < nodeVisit.get(v).getDiscoveryTime())
+ edgeVisit.put(e, EdgeVisitType.FORWARD);
+ else
+ edgeVisit.put(e, EdgeVisitType.CROSS);
}
- nodeVisit.get(u).setColour(NodeColour.BLACK);
- time.incrementAndGet();
- nodeVisit.get(u).setFinishTime(time.get());
- visited.add(u);
}
+ nodeVisit.get(u).setColour(NodeColour.BLACK);
+ time.incrementAndGet();
+ nodeVisit.get(u).setFinishTime(time.get());
+ visited.add(u);
+}
// ======================
// DOT I/O
// ======================
- /**
- * Builds a {@link Graph} instance by parsing a DOT-like file.
- *
- * The expected format supports both isolated nodes and weighted edges.
- * Examples of valid lines:
- *
- * 1 → an isolated node
- * 1 -> 2 [label="5"]; → a directed edge with weight 5
- * 1 -> 3; → a directed edge without weight (defaults to
- * 1)
- *
- *
- *
- * @param filename the path to the DOT file to parse
- * @return a new {@link Graph} built from the file contents
- * @throws RuntimeException if the file cannot be read or parsed
- */
+
/**
* Reads a directed graph from a DOT-like file.
* Supports lines such as:
@@ -1138,22 +1617,35 @@ public class Graph {
// ======================
// Méthodes utilitaires internes
// ======================
+/**
+ * Initialise une nouvelle carte (Map) contenant des informations de visite pour chaque nœud du graphe.
+ *
+ * @return Une carte où chaque nœud est associé à un nouvel objet {@link NodeVisitInfo}.
+ */
+private Map initVisitInfo() {
+ Map map = new HashMap<>();
+ for (Node n : adjEdList.keySet())
+ map.put(n, new NodeVisitInfo());
+ return map;
+}
- private Map initVisitInfo() {
- Map map = new HashMap<>();
- for (Node n : adjEdList.keySet())
- map.put(n, new NodeVisitInfo());
- return map;
- }
-
- private void initVisitInfo(Map map) {
- for (Node n : adjEdList.keySet())
- map.put(n, new NodeVisitInfo());
- }
-
- private List sortNodes() {
- List nodes = new ArrayList<>(adjEdList.keySet());
- nodes.sort(Comparator.comparingInt(Node::getId));
- return nodes;
- }
+/**
+ * Réinitialise les informations de visite dans une carte donnée pour chaque nœud du graphe.
+ *
+ * @param map La carte à réinitialiser, où chaque nœud sera associé à un nouvel objet {@link NodeVisitInfo}.
+ */
+private void initVisitInfo(Map map) {
+ for (Node n : adjEdList.keySet())
+ map.put(n, new NodeVisitInfo());
}
+
+/**
+ * Trie les nœuds du graphe par leur identifiant dans l'ordre croissant.
+ *
+ * @return Une liste triée des nœuds du graphe.
+ */
+private List sortNodes() {
+ List nodes = new ArrayList<>(adjEdList.keySet());
+ nodes.sort(Comparator.comparingInt(Node::getId));
+ return nodes;
+}}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java
index 9774326..5746bed 100644
--- a/src/main/java/m1graphs2025/UndirectedGraph.java
+++ b/src/main/java/m1graphs2025/UndirectedGraph.java
@@ -31,7 +31,8 @@ public class UndirectedGraph extends Graph {
int i = 0;
int id = 1;
- // D'abord, on crée tous les nœuds pour les réutiliser (sinon on crée des doublons)
+ // D'abord, on crée tous les nœuds pour les réutiliser (sinon on crée des
+ // doublons)
Map nodes = new HashMap<>();
// Première passe : créer les nœuds
@@ -79,7 +80,6 @@ public class UndirectedGraph extends Graph {
this.adjEdList = adjEdList;
}
-
@Override
public boolean addNode(Node n) {
return super.addNode(n);
@@ -109,8 +109,8 @@ public class UndirectedGraph extends Graph {
@Override
public void addEdge(Node from, Node to) {
- super.addEdge(from, to); // u -> v
- super.addEdge(to, from); // v -> u pour non dirigé
+ super.addEdge(from, to); // u -> v
+ super.addEdge(to, from); // v -> u pour non dirigé
}
@Override
@@ -123,8 +123,10 @@ public class UndirectedGraph extends Graph {
public void addEdge(int fromId, int toId) {
Node from = getNode(fromId);
Node to = getNode(toId);
- if (from == null) from = new Node(fromId, this);
- if (to == null) to = new Node(toId, this);
+ if (from == null)
+ from = new Node(fromId, this);
+ if (to == null)
+ to = new Node(toId, this);
addEdge(from, to);
}
@@ -132,8 +134,10 @@ public class UndirectedGraph extends Graph {
public void addEdge(int fromId, int toId, int weight) {
Node from = getNode(fromId);
Node to = getNode(toId);
- if (from == null) from = new Node(fromId, this);
- if (to == null) to = new Node(toId, this);
+ if (from == null)
+ from = new Node(fromId, this);
+ if (to == null)
+ to = new Node(toId, this);
addEdge(from, to, weight);
}
@@ -204,51 +208,91 @@ public class UndirectedGraph extends Graph {
}
return closure;
}
- // ======================
+
+ /**
+ * Lit un fichier DOT et retourne le graphe non orienté associé.
+ * Le format attend est le suivant :
+ * - Ligne contenant un seul nombre : nœud isolé
+ * - Ligne contenant trois nombres : arête (u, v, poids)
+ * Les arêtes sont automatiquement dupliquées pour satisfaire la propriété
+ * de graphe non orienté.
+ * Si une erreur survient lors de la lecture du fichier, une exception est
+ * levée.
+ *
+ * @param filename Le nom du fichier à lire.
+ * @return Le graphe non orienté associé au fichier.
+ * @throws RuntimeException si une erreur survient lors de la lecture du
+ * fichier.
+ */
+
public static UndirectedGraph fromDotFile(String filename) {
Map> adjEdList = new HashMap<>();
UndirectedGraph g = new UndirectedGraph(adjEdList);
- try (BufferedReader dot = new BufferedReader(new FileReader(filename))) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
String line;
- while ((line = dot.readLine()) != null) {
+ while ((line = reader.readLine()) != null) {
line = line.trim();
- if (line.isEmpty()) continue; // ignore lignes vides
+ if (line.isEmpty())
+ continue; // skip empty lines
- // Cas 1 : Ligne contenant seulement un numéro → nœud isolé
+ // --- Case 1: isolated node
if (line.matches("^\\d+$")) {
int id = Integer.parseInt(line);
- Node isolated = new Node(id, g, "");
- adjEdList.putIfAbsent(isolated, new ArrayList<>());
+ Node node = g.getOrCreateNode(id);
+ adjEdList.putIfAbsent(node, new ArrayList<>());
continue;
}
- // Cas 2 : Ligne décrivant une arête, ex: "1 ------> 5 ----> 5"
- String[] tokens = line.split("[^0-9]+");
- if (tokens.length < 3) continue; // ligne invalide → on saute
+ // --- Case 2: edge line
+ String[] tokens = line.replaceAll("[^0-9]+", " ").trim().split("\\s+");
+ if (tokens.length < 2)
+ continue; // invalid line → skip
int fromId = Integer.parseInt(tokens[0]);
- int toId = Integer.parseInt(tokens[1]);
- int weight = Integer.parseInt(tokens[2]);
+ int toId = Integer.parseInt(tokens[1]);
- Node nFrom = new Node(fromId, g, "");
- Node nTo = new Node(toId, g, "");
- Edge edge = new Edge(nFrom, nTo, weight);
+ Node from = g.getOrCreateNode(fromId);
+ Node to = g.getOrCreateNode(toId);
- // Ajout de l’arête dans la liste d’adjacence
- adjEdList.computeIfAbsent(nFrom, k -> new ArrayList<>()).add(edge);
+ Edge edge;
+ if (tokens.length >= 3) {
+ int weight = Integer.parseInt(tokens[2]);
+ edge = new Edge(from, to, weight);
+ } else {
+ edge = new Edge(from, to);
+ }
- // S’assurer que le nœud destination existe
- adjEdList.putIfAbsent(nTo, new ArrayList<>());
+ // Add to adjacency list
+ adjEdList.computeIfAbsent(from, k -> new ArrayList<>()).add(edge);
+ adjEdList.putIfAbsent(to, new ArrayList<>()); // ensure target node exists
}
+
} catch (IOException e) {
- throw new RuntimeException("Erreur lors de la lecture du fichier : " + filename, e);
+ throw new RuntimeException("Error reading file: " + filename, e);
}
return g;
}
+ /**
+ * Generates a DOT-format string representation of this graph.
+ *
+ * Example output for an undirected graph:
+ *
+ *
+ * graph G {
+ * rankdir=LR;
+ * 1 -- 2 [label="5", len="5"];
+ * 3 -- 4 [label="3", len="3"];
+ * 4;
+ * }
+ *
+ *
+ *
+ * @return the DOT-format representation of the graph
+ */
public String toDotString() {
StringBuilder sb = new StringBuilder();
sb.append("graph G {\n");
@@ -301,9 +345,6 @@ public class UndirectedGraph extends Graph {
// Utilitaires
// ======================
- /**
- * Indique que ce graphe est non orienté (utile pour certaines fonctions).
- */
public boolean isDirected() {
return false;
}
--
GitLab
From 70ffaa0b903501bbc5945934c1fdf52bdff48e23 Mon Sep 17 00:00:00 2001
From: dcisse2
Date: Thu, 20 Nov 2025 04:16:22 +0100
Subject: [PATCH 2/5] =?UTF-8?q?[VERSION=20STABLE]=20Code=20Restructur?=
=?UTF-8?q?=C3=A9=20et=20Fonctionnel?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/encodings.xml | 1 +
.idea/inspectionProfiles/Project_Default.xml | 6 +
input/example_flow.dot | 11 ++
output/flow1.gv | 12 ++
output/flow2.gv | 12 ++
output/flow3.gv | 12 ++
output/flow4.gv | 12 ++
output/flow5.gv | 12 ++
output/residualGraph1.gv | 14 ++
output/residualGraph2.gv | 17 ++
output/residualGraph3.gv | 17 ++
output/residualGraph4.gv | 18 ++
output/residualGraph5.gv | 17 ++
output/step_1_flow.dot | 18 --
output/step_1_residual.dot | 15 --
output/step_2_flow.dot | 18 --
output/step_2_residual.dot | 14 --
output/step_3_flow.dot | 18 --
output/step_3_residual.dot | 13 --
src/main/java/MaximumFlow/DotIO.java | 69 --------
src/main/java/MaximumFlow/FlowMap.java | 86 ----------
src/main/java/MaximumFlow/FlowNetwork.java | 132 ---------------
src/main/java/MaximumFlow/FordFulkerson.java | 27 ---
src/main/java/MaximumFlow/Main.java | 37 -----
src/main/java/MaximumFlow/PathFinder.java | 62 -------
src/main/java/MaximumFlow/ResidualGraph.java | 156 ------------------
.../MaximumFlow/AugmentingPathFinder.java | 23 +++
.../MaximumFlow/BFSPathFinder.java | 49 ++++++
.../MaximumFlow/DFSPathFinder.java | 51 ++++++
.../m1graphs2025/MaximumFlow/DotExporter.java | 115 +++++++++++++
.../m1graphs2025/MaximumFlow/FlowMap.java | 77 +++++++++
.../m1graphs2025/MaximumFlow/FlowNetwork.java | 144 ++++++++++++++++
.../MaximumFlow/FlowNetworkValidator.java | 39 +++++
.../MaximumFlow/FordFulkerson.java | 84 ++++++++++
.../java/m1graphs2025/MaximumFlow/Main.java | 63 +++++++
.../MaximumFlow/MaxCapacityPathFinder.java | 23 +++
.../MaximumFlow/ResidualGraph.java | 50 ++++++
.../java/m1graphs2025/{ => pw2}/Edge.java | 2 +-
.../m1graphs2025/{ => pw2}/EdgeVisitType.java | 2 +-
.../java/m1graphs2025/{ => pw2}/Graph.java | 6 +-
.../java/m1graphs2025/{ => pw2}/Node.java | 2 +-
.../m1graphs2025/{ => pw2}/NodeVisitInfo.java | 2 +-
.../{ => pw2}/UndirectedGraph.java | 5 +-
.../java/m1graphs2025/TestGraphPart1.java | 4 +
.../java/m1graphs2025/TestGraphPart2.java | 4 +-
.../java/m1graphs2025/TestGraphPart3.java | 5 +-
.../java/m1graphs2025/TestGraphPart4.java | 3 +
.../java/m1graphs2025/TestGraphPart5.java | 5 +-
.../java/m1graphs2025/TestGraphPart6.java | 3 +-
src/test/java/m1graphs2025/TestsGraphPW2.java | 5 +
50 files changed, 909 insertions(+), 683 deletions(-)
create mode 100644 .idea/inspectionProfiles/Project_Default.xml
create mode 100644 input/example_flow.dot
create mode 100644 output/flow1.gv
create mode 100644 output/flow2.gv
create mode 100644 output/flow3.gv
create mode 100644 output/flow4.gv
create mode 100644 output/flow5.gv
create mode 100644 output/residualGraph1.gv
create mode 100644 output/residualGraph2.gv
create mode 100644 output/residualGraph3.gv
create mode 100644 output/residualGraph4.gv
create mode 100644 output/residualGraph5.gv
delete mode 100644 output/step_1_flow.dot
delete mode 100644 output/step_1_residual.dot
delete mode 100644 output/step_2_flow.dot
delete mode 100644 output/step_2_residual.dot
delete mode 100644 output/step_3_flow.dot
delete mode 100644 output/step_3_residual.dot
delete mode 100644 src/main/java/MaximumFlow/DotIO.java
delete mode 100644 src/main/java/MaximumFlow/FlowMap.java
delete mode 100644 src/main/java/MaximumFlow/FlowNetwork.java
delete mode 100644 src/main/java/MaximumFlow/FordFulkerson.java
delete mode 100644 src/main/java/MaximumFlow/Main.java
delete mode 100644 src/main/java/MaximumFlow/PathFinder.java
delete mode 100644 src/main/java/MaximumFlow/ResidualGraph.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/BFSPathFinder.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/DFSPathFinder.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/DotExporter.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/FlowMap.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/FlowNetworkValidator.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/FordFulkerson.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/Main.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/MaxCapacityPathFinder.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/ResidualGraph.java
rename src/main/java/m1graphs2025/{ => pw2}/Edge.java (99%)
rename src/main/java/m1graphs2025/{ => pw2}/EdgeVisitType.java (79%)
rename src/main/java/m1graphs2025/{ => pw2}/Graph.java (99%)
rename src/main/java/m1graphs2025/{ => pw2}/Node.java (99%)
rename src/main/java/m1graphs2025/{ => pw2}/NodeVisitInfo.java (99%)
rename src/main/java/m1graphs2025/{ => pw2}/UndirectedGraph.java (99%)
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
index 226ed61..e2d3ca1 100644
--- a/.idea/encodings.xml
+++ b/.idea/encodings.xml
@@ -1,6 +1,7 @@
+
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..f4d0a6b
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/input/example_flow.dot b/input/example_flow.dot
new file mode 100644
index 0000000..70d7054
--- /dev/null
+++ b/input/example_flow.dot
@@ -0,0 +1,11 @@
+digraph flowNetwork {
+ rankdir="LR"
+ 1-> 2 [label=8, len=8]
+ 1-> 3 [label=6, len=6]
+ 2-> 4 [label=6, len=6]
+ 3-> 4 [label=10, len=10]
+ 3-> 5 [label=7, len=7]
+ 4-> 5 [label=3, len=3]
+ 4-> 6 [label=4, len=4]
+ 5-> 6 [label=6, len=6]
+ }
\ No newline at end of file
diff --git a/output/flow1.gv b/output/flow1.gv
new file mode 100644
index 0000000..97471c0
--- /dev/null
+++ b/output/flow1.gv
@@ -0,0 +1,12 @@
+digraph flow1 {
+ rankdir="LR";
+ label="(1) Flow. Value: 0";
+ 1 -> 2 [label="0/8", len=8];
+ 1 -> 3 [label="0/6", len=6];
+ 2 -> 4 [label="0/6", len=6];
+ 3 -> 4 [label="0/10", len=10];
+ 3 -> 5 [label="0/7", len=7];
+ 4 -> 5 [label="0/3", len=3];
+ 4 -> 6 [label="0/4", len=4];
+ 5 -> 6 [label="0/6", len=6];
+}
diff --git a/output/flow2.gv b/output/flow2.gv
new file mode 100644
index 0000000..05432c3
--- /dev/null
+++ b/output/flow2.gv
@@ -0,0 +1,12 @@
+digraph flow2 {
+ rankdir="LR";
+ label="(2) Flow. Value: 3";
+ 1 -> 2 [label="3/8", len=8];
+ 1 -> 3 [label="0/6", len=6];
+ 2 -> 4 [label="3/6", len=6];
+ 3 -> 4 [label="0/10", len=10];
+ 3 -> 5 [label="0/7", len=7];
+ 4 -> 5 [label="3/3", len=3];
+ 4 -> 6 [label="0/4", len=4];
+ 5 -> 6 [label="3/6", len=6];
+}
diff --git a/output/flow3.gv b/output/flow3.gv
new file mode 100644
index 0000000..f2d5e10
--- /dev/null
+++ b/output/flow3.gv
@@ -0,0 +1,12 @@
+digraph flow3 {
+ rankdir="LR";
+ label="(3) Flow. Value: 6";
+ 1 -> 2 [label="6/8", len=8];
+ 1 -> 3 [label="0/6", len=6];
+ 2 -> 4 [label="6/6", len=6];
+ 3 -> 4 [label="0/10", len=10];
+ 3 -> 5 [label="0/7", len=7];
+ 4 -> 5 [label="3/3", len=3];
+ 4 -> 6 [label="3/4", len=4];
+ 5 -> 6 [label="3/6", len=6];
+}
diff --git a/output/flow4.gv b/output/flow4.gv
new file mode 100644
index 0000000..f1254b3
--- /dev/null
+++ b/output/flow4.gv
@@ -0,0 +1,12 @@
+digraph flow4 {
+ rankdir="LR";
+ label="(4) Flow. Value: 7";
+ 1 -> 2 [label="6/8", len=8];
+ 1 -> 3 [label="1/6", len=6];
+ 2 -> 4 [label="6/6", len=6];
+ 3 -> 4 [label="1/10", len=10];
+ 3 -> 5 [label="0/7", len=7];
+ 4 -> 5 [label="3/3", len=3];
+ 4 -> 6 [label="4/4", len=4];
+ 5 -> 6 [label="3/6", len=6];
+}
diff --git a/output/flow5.gv b/output/flow5.gv
new file mode 100644
index 0000000..52e2408
--- /dev/null
+++ b/output/flow5.gv
@@ -0,0 +1,12 @@
+digraph flow5 {
+ rankdir="LR";
+ label="(5) Flow. Value: 10";
+ 1 -> 2 [label="6/8", len=8];
+ 1 -> 3 [label="4/6", len=6];
+ 2 -> 4 [label="6/6", len=6];
+ 3 -> 4 [label="1/10", len=10];
+ 3 -> 5 [label="3/7", len=7];
+ 4 -> 5 [label="3/3", len=3];
+ 4 -> 6 [label="4/4", len=4];
+ 5 -> 6 [label="6/6", len=6];
+}
diff --git a/output/residualGraph1.gv b/output/residualGraph1.gv
new file mode 100644
index 0000000..fb128fe
--- /dev/null
+++ b/output/residualGraph1.gv
@@ -0,0 +1,14 @@
+digraph residualGraph1 {
+ rankdir="LR";
+ label="(1) residual graph.
+ Augmenting path: [1, 2, 4, 5, 6].
+ Residual capacity: 3";
+ 4 -> 5 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
+ 4 -> 6 [label="4", len=4];
+ 5 -> 6 [label="6", len=6, penwidth=3, color="blue"];
+ 1 -> 2 [label="8", len=8, penwidth=3, color="blue"];
+ 1 -> 3 [label="6", len=6];
+ 2 -> 4 [label="6", len=6, penwidth=3, color="blue"];
+ 3 -> 4 [label="10", len=10];
+ 3 -> 5 [label="7", len=7];
+}
diff --git a/output/residualGraph2.gv b/output/residualGraph2.gv
new file mode 100644
index 0000000..7dc73b4
--- /dev/null
+++ b/output/residualGraph2.gv
@@ -0,0 +1,17 @@
+digraph residualGraph2 {
+ rankdir="LR";
+ label="(2) residual graph.
+ Augmenting path: [1, 2, 4, 6].
+ Residual capacity: 3";
+ 6 -> 5 [label="3", len=3];
+ 5 -> 4 [label="3", len=3];
+ 5 -> 6 [label="3", len=3];
+ 4 -> 2 [label="3", len=3];
+ 4 -> 6 [label="4", len=4, penwidth=3, color="blue"];
+ 3 -> 4 [label="10", len=10];
+ 3 -> 5 [label="7", len=7];
+ 2 -> 1 [label="3", len=3];
+ 2 -> 4 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
+ 1 -> 2 [label="5", len=5, penwidth=3, color="blue"];
+ 1 -> 3 [label="6", len=6];
+}
diff --git a/output/residualGraph3.gv b/output/residualGraph3.gv
new file mode 100644
index 0000000..4facbb5
--- /dev/null
+++ b/output/residualGraph3.gv
@@ -0,0 +1,17 @@
+digraph residualGraph3 {
+ rankdir="LR";
+ label="(3) residual graph.
+ Augmenting path: [1, 3, 4, 6].
+ Residual capacity: 1";
+ 5 -> 4 [label="3", len=3];
+ 5 -> 6 [label="3", len=3];
+ 4 -> 2 [label="6", len=6];
+ 4 -> 6 [label="1", len=1, penwidth=3, color="blue", fontcolor="red"];
+ 6 -> 4 [label="3", len=3];
+ 6 -> 5 [label="3", len=3];
+ 1 -> 2 [label="2", len=2];
+ 1 -> 3 [label="6", len=6, penwidth=3, color="blue"];
+ 3 -> 4 [label="10", len=10, penwidth=3, color="blue"];
+ 3 -> 5 [label="7", len=7];
+ 2 -> 1 [label="6", len=6];
+}
diff --git a/output/residualGraph4.gv b/output/residualGraph4.gv
new file mode 100644
index 0000000..15f7006
--- /dev/null
+++ b/output/residualGraph4.gv
@@ -0,0 +1,18 @@
+digraph residualGraph4 {
+ rankdir="LR";
+ label="(4) residual graph.
+ Augmenting path: [1, 3, 5, 6].
+ Residual capacity: 3";
+ 1 -> 2 [label="2", len=2];
+ 1 -> 3 [label="5", len=5, penwidth=3, color="blue"];
+ 4 -> 2 [label="6", len=6];
+ 4 -> 3 [label="1", len=1];
+ 5 -> 4 [label="3", len=3];
+ 5 -> 6 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
+ 2 -> 1 [label="6", len=6];
+ 3 -> 1 [label="1", len=1];
+ 3 -> 4 [label="9", len=9];
+ 3 -> 5 [label="7", len=7, penwidth=3, color="blue"];
+ 6 -> 4 [label="4", len=4];
+ 6 -> 5 [label="3", len=3];
+}
diff --git a/output/residualGraph5.gv b/output/residualGraph5.gv
new file mode 100644
index 0000000..485fdda
--- /dev/null
+++ b/output/residualGraph5.gv
@@ -0,0 +1,17 @@
+digraph residualGraph5 {
+ rankdir="LR";
+ label="(5) residual graph.
+ Augmenting path: none.";
+ 2 -> 1 [label="6", len=6];
+ 3 -> 1 [label="4", len=4];
+ 3 -> 4 [label="9", len=9];
+ 3 -> 5 [label="4", len=4];
+ 4 -> 2 [label="6", len=6];
+ 4 -> 3 [label="1", len=1];
+ 5 -> 3 [label="3", len=3];
+ 5 -> 4 [label="3", len=3];
+ 6 -> 4 [label="4", len=4];
+ 6 -> 5 [label="6", len=6];
+ 1 -> 2 [label="2", len=2];
+ 1 -> 3 [label="2", len=2];
+}
diff --git a/output/step_1_flow.dot b/output/step_1_flow.dot
deleted file mode 100644
index 5a13e73..0000000
--- a/output/step_1_flow.dot
+++ /dev/null
@@ -1,18 +0,0 @@
-graph {
- rankdir=LR;
- 1 -> 2 [label=4]
- 1 -> 3 [label=3]
- 1 -> 3 [label=6]
- 1 -> 4 [label=0]
- 2 -> 3 [label=1]
- 2 -> 4 [label=2]
- 2 -> 4 [label=50]
- 2 -> 5 [label=3]
- 3 -> 5 [label=2]
- 4 -> 4 [label=14]
- 4 -> 5 [label=2]
- 5 -> 6 [label=7]
- 5 -> 6 [label=10]
- 6 -> 6 [label=5]
- 6 -> 6 [label=8]
-}
\ No newline at end of file
diff --git a/output/step_1_residual.dot b/output/step_1_residual.dot
deleted file mode 100644
index d750d65..0000000
--- a/output/step_1_residual.dot
+++ /dev/null
@@ -1,15 +0,0 @@
-graph {
- rankdir=LR;
- 1 -> 2 [label=1]
- 1 -> 3 [label=4]
- 2 -> 1 [label=3]
- 2 -> 3 [label=1]
- 2 -> 4 [label=50]
- 3 -> 1 [label=2]
- 4 -> 4 [label=14]
- 4 -> 5 [label=2]
- 5 -> 2 [label=3]
- 5 -> 3 [label=2]
- 5 -> 6 [label=10]
- 6 -> 6 [label=8]
-}
\ No newline at end of file
diff --git a/output/step_2_flow.dot b/output/step_2_flow.dot
deleted file mode 100644
index 5a13e73..0000000
--- a/output/step_2_flow.dot
+++ /dev/null
@@ -1,18 +0,0 @@
-graph {
- rankdir=LR;
- 1 -> 2 [label=4]
- 1 -> 3 [label=3]
- 1 -> 3 [label=6]
- 1 -> 4 [label=0]
- 2 -> 3 [label=1]
- 2 -> 4 [label=2]
- 2 -> 4 [label=50]
- 2 -> 5 [label=3]
- 3 -> 5 [label=2]
- 4 -> 4 [label=14]
- 4 -> 5 [label=2]
- 5 -> 6 [label=7]
- 5 -> 6 [label=10]
- 6 -> 6 [label=5]
- 6 -> 6 [label=8]
-}
\ No newline at end of file
diff --git a/output/step_2_residual.dot b/output/step_2_residual.dot
deleted file mode 100644
index 5646477..0000000
--- a/output/step_2_residual.dot
+++ /dev/null
@@ -1,14 +0,0 @@
-graph {
- rankdir=LR;
- 1 -> 2 [label=1]
- 1 -> 3 [label=6]
- 2 -> 1 [label=3]
- 2 -> 3 [label=1]
- 2 -> 4 [label=50]
- 3 -> 5 [label=2]
- 4 -> 4 [label=14]
- 4 -> 5 [label=2]
- 5 -> 2 [label=3]
- 5 -> 6 [label=10]
- 6 -> 6 [label=8]
-}
\ No newline at end of file
diff --git a/output/step_3_flow.dot b/output/step_3_flow.dot
deleted file mode 100644
index 5a13e73..0000000
--- a/output/step_3_flow.dot
+++ /dev/null
@@ -1,18 +0,0 @@
-graph {
- rankdir=LR;
- 1 -> 2 [label=4]
- 1 -> 3 [label=3]
- 1 -> 3 [label=6]
- 1 -> 4 [label=0]
- 2 -> 3 [label=1]
- 2 -> 4 [label=2]
- 2 -> 4 [label=50]
- 2 -> 5 [label=3]
- 3 -> 5 [label=2]
- 4 -> 4 [label=14]
- 4 -> 5 [label=2]
- 5 -> 6 [label=7]
- 5 -> 6 [label=10]
- 6 -> 6 [label=5]
- 6 -> 6 [label=8]
-}
\ No newline at end of file
diff --git a/output/step_3_residual.dot b/output/step_3_residual.dot
deleted file mode 100644
index 14f08a2..0000000
--- a/output/step_3_residual.dot
+++ /dev/null
@@ -1,13 +0,0 @@
-graph {
- rankdir=LR;
- 1 -> 2 [label=4]
- 1 -> 3 [label=6]
- 2 -> 3 [label=1]
- 2 -> 4 [label=50]
- 2 -> 5 [label=3]
- 3 -> 5 [label=2]
- 4 -> 4 [label=14]
- 4 -> 5 [label=2]
- 5 -> 6 [label=10]
- 6 -> 6 [label=8]
-}
\ No newline at end of file
diff --git a/src/main/java/MaximumFlow/DotIO.java b/src/main/java/MaximumFlow/DotIO.java
deleted file mode 100644
index 919a184..0000000
--- a/src/main/java/MaximumFlow/DotIO.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package MaximumFlow;
-
-import m1graphs2025.Graph;
-import java.io.File;
-
-/**
- * Classe responsable de la lecture et de l’écriture des graphes DOT
- * pour le projet Maximum Flow (PW3).
- */
-public class DotIO {
-
- /**
- * Charge un graphe depuis un fichier DOT et crée un FlowNetwork.
- *
- * @param filename Le fichier DOT à lire
- * @param source Indice du sommet source
- * @param sink Indice du sommet puits
- * @return FlowNetwork correspondant
- */
- public static FlowNetwork loadFromDot(String filename, int source, int sink) {
- Graph g = Graph.fromDotFile(filename);
- return new FlowNetwork(g, source, sink);
- }
-
- /**
- * Sauvegarde les fichiers DOT pour le flux et le graphe résiduel à une étape donnée.
- *
- * @param flow FlowNetwork actuel
- * @param residual ResidualGraph actuel
- * @param step Numéro de l'étape
- * @param outputDir Dossier de sortie (par ex. "output")
- */
- public static void saveStepFiles(FlowNetwork flow, ResidualGraph residual, int step, String outputDir) {
- File dir = new File(outputDir);
- if (!dir.exists() && !dir.mkdirs()) {
- System.err.println("[Erreur] Impossible de créer le dossier '" + outputDir + "'.");
- return;
- }
-
- String flowPath = stepFilePath(outputDir, step, "flow");
- String residualPath = stepFilePath(outputDir, step, "residual");
-
- try {
- if (flow.getBaseGraph() != null) {
- flow.getBaseGraph().toDotFile(flowPath, "dot");
- }
- if (residual.getGraph() != null) {
- residual.getGraph().toDotFile(residualPath, "dot");
- }
- System.out.println("→ Étape " + step + " sauvegardée :");
- System.out.println(" " + flowPath);
- System.out.println(" " + residualPath);
- } catch (Exception e) {
- System.err.println("[Erreur] Échec de la sauvegarde : " + e.getMessage());
- }
- }
-
- /**
- * Génère le chemin du fichier pour une étape et un type donné.
- *
- * @param outputDir Dossier de sortie
- * @param step Numéro de l'étape
- * @param type "flow" ou "residual"
- * @return Chemin complet du fichier DOT
- */
- private static String stepFilePath(String outputDir, int step, String type) {
- return outputDir + "/step_" + step + "_" + type;
- }
-}
diff --git a/src/main/java/MaximumFlow/FlowMap.java b/src/main/java/MaximumFlow/FlowMap.java
deleted file mode 100644
index 238fa68..0000000
--- a/src/main/java/MaximumFlow/FlowMap.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package MaximumFlow;
-
-import m1graphs2025.*;
-import java.util.*;
-
-public class FlowMap {
-
- private Map flowmap = new HashMap<>();
-
- // -------------------------------------------------------------
- // Encodage / décodage de clé "u:v"
- // -------------------------------------------------------------
-
- private String key(Node u, Node v) {
- return u.getId() + ":" + v.getId();
- }
-
- private String key(int u, int v) {
- return u + ":" + v;
- }
-
- private int[] decodeKey(String key) {
- String[] parts = key.split(":");
- return new int[]{
- Integer.parseInt(parts[0]),
- Integer.parseInt(parts[1])
- };
- }
-
- // -------------------------------------------------------------
- // Accès au flux
- // -------------------------------------------------------------
-
- public int get(Node u, Node v) {
- return flowmap.getOrDefault(key(u, v), 0);
- }
-
- public int get(int u, int v) {
- return flowmap.getOrDefault(key(u, v), 0);
- }
-
- public void set(Node u, Node v, int val) {
- if (val < 0)
- throw new IllegalArgumentException("Flux négatif interdit : " + val);
- flowmap.put(key(u, v), val);
- }
-
- public void set(int u, int v, int val) {
- if (val < 0)
- throw new IllegalArgumentException("Flux négatif interdit : " + val);
- flowmap.put(key(u, v), val);
- }
-
- public void add(Node u, Node v, int val) {
- int newVal = get(u, v) + val;
- if (newVal < 0)
- throw new IllegalArgumentException("Flux négatif interdit : " + newVal);
- flowmap.put(key(u, v), newVal);
- }
-
- public void add(int u, int v, int val) {
- int newVal = get(u, v) + val;
- if (newVal < 0)
- throw new IllegalArgumentException("Flux négatif interdit : " + newVal);
- flowmap.put(key(u, v), newVal);
- }
-
- // -------------------------------------------------------------
- // Outils utiles
- // -------------------------------------------------------------
-
- /** Retourne toutes les clés "u:v" */
- public Set keys() {
- return flowmap.keySet();
- }
-
- /** Retourne directement les couples ("u:v", valeur) */
- public Set> entries() {
- return flowmap.entrySet();
- }
-
- /** Vide complètement tous les flux */
- public void reset() {
- flowmap.clear();
- }
-}
diff --git a/src/main/java/MaximumFlow/FlowNetwork.java b/src/main/java/MaximumFlow/FlowNetwork.java
deleted file mode 100644
index f658b00..0000000
--- a/src/main/java/MaximumFlow/FlowNetwork.java
+++ /dev/null
@@ -1,132 +0,0 @@
-package MaximumFlow;
-
-import m1graphs2025.*;
-import java.util.*;
-import java.io.FileWriter;
-import java.io.IOException;
-
-public class FlowNetwork {
-
- private Graph baseGraph; // Graphe de capacités
- private FlowMap flow = new FlowMap();
- private Map capacity = new HashMap<>();
-
- private int sourceId;
- private int sinkId;
-
- // -----------------------------------------------------
- // CONSTRUCTEUR
- // -----------------------------------------------------
- public FlowNetwork(Graph baseGraph, int sourceId, int sinkId) {
- this.baseGraph = baseGraph;
- this.sourceId = sourceId;
- this.sinkId = sinkId;
-
- // Charger les capacités depuis baseGraph
- for (Edge e : baseGraph.getAllEdges()) {
- Node u = e.getNodeFrom();
- Node v = e.getNodeTo();
- capacity.put(key(u, v), e.getWeight());
- }
- }
-
- // -----------------------------------------------------
- // GETTERS
- // -----------------------------------------------------
- public Graph getBaseGraph() { return baseGraph; }
- public int getSourceId() { return sourceId; }
- public int getSinkId() { return sinkId; }
-
- // -----------------------------------------------------
- // CAPACITÉS
- // -----------------------------------------------------
- private String key(Node u, Node v) {
- return u.getId() + ":" + v.getId();
- }
-
- public int getCapacity(Node u, Node v) {
- return capacity.getOrDefault(key(u, v), 0);
- }
-
- public void setCapacity(Node u, Node v, int value) {
- capacity.put(key(u, v), value);
- }
-
- // -----------------------------------------------------
- // FLUX
- // -----------------------------------------------------
- public int getFlow(Node u, Node v) {
- return flow.get(u, v);
- }
-
- public int getResidualCapacity(Node u, Node v) {
- return getCapacity(u, v) - getFlow(u, v);
- }
-
- // -----------------------------------------------------
- // CRÉATION DU GRAPHE RÉSIDUEL
- // -----------------------------------------------------
- public ResidualGraph toResidualGraph() {
- return new ResidualGraph(this);
- }
-
- // -----------------------------------------------------
- // MISE À JOUR DU FLUX (chemin augmentant)
- // -----------------------------------------------------
- public void updateFlow(List path, int delta) {
-
- for (int i = 0; i < path.size() - 1; i++) {
-
- Node u = path.get(i);
- Node v = path.get(i + 1);
-
- // Cas 1 : arc direct (u -> v)
- if (getCapacity(u, v) > 0) {
- flow.add(u, v, delta);
- }
- // Cas 2 : arc inverse (v -> u)
- else {
- flow.add(v, u, -delta);
- }
- }
- }
-
- public int getSource() { return sourceId; }
- public int getSink() { return sinkId; }
-
- // -----------------------------------------------------
- // EXPORTER LE GRAPHE DE FLUX EN DOT
- // -----------------------------------------------------
- public void toDotFileFlow(String filename, int step) {
- int totalFlow = 0;
-
- // Calculer le flot total en sortie de la source
- Node source = baseGraph.getNode(sourceId);
- for (Edge e : baseGraph.getOutEdges(source)) {
- totalFlow += getFlow(e.getNodeFrom(), e.getNodeTo());
- }
-
- try (FileWriter writer = new FileWriter(filename)) {
- writer.write("digraph flow" + step + " {\n");
- writer.write(" rankdir=\"LR\";\n");
- writer.write(" label=\"(" + step + ") Flow induced from residual graph "
- + (step-1) + ". Value: " + totalFlow + "\";\n");
-
- // Arêtes avec label f/c
- for (Edge e : baseGraph.getAllEdges()) {
- Node u = e.getNodeFrom();
- Node v = e.getNodeTo();
- int f = getFlow(u, v);
- int c = getCapacity(u, v);
-
- writer.write(" " + u.getId() + " -> " + v.getId() +
- " [label=\"" + f + "/" + c + "\", len=" + c + "];\n");
- }
-
- writer.write("}\n");
- System.out.println("Flow DOT file saved: " + filename);
- } catch (IOException e) {
- System.err.println("[Erreur] Impossible d'écrire le fichier DOT : " + e.getMessage());
- }
- }
-}
diff --git a/src/main/java/MaximumFlow/FordFulkerson.java b/src/main/java/MaximumFlow/FordFulkerson.java
deleted file mode 100644
index 0d92ed7..0000000
--- a/src/main/java/MaximumFlow/FordFulkerson.java
+++ /dev/null
@@ -1,27 +0,0 @@
-package MaximumFlow;
-
-// --- FordFulkerson.java ---
-public class FordFulkerson {
- public int computeMaxFlow(FlowNetwork network) {
- FlowMap flow = new FlowMap();
- int maxFlow = 0;
-
-
- while (true) {
- ResidualGraph residual = network.toResidualGraph();
- var path = PathFinder.findPathBFS(residual, network.getSource(), network.getSink());
- if (path == null) break;
-
-
- int delta = residual.minCapacity(path);
- network.updateFlow(path, delta);
- try {
- DotIO.saveStepFiles(network, residual, delta, "output");
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- maxFlow += delta;
- }
- return maxFlow;
- }
-}
\ No newline at end of file
diff --git a/src/main/java/MaximumFlow/Main.java b/src/main/java/MaximumFlow/Main.java
deleted file mode 100644
index c80ef90..0000000
--- a/src/main/java/MaximumFlow/Main.java
+++ /dev/null
@@ -1,37 +0,0 @@
-package MaximumFlow;
-
-import m1graphs2025.*;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-public class Main {
- public static void main(String[] args) {
- if (args.length > 0) {
- String path = args[0];
- System.out.println("Lecture du graphe depuis : " + path);
- Graph g = Graph.fromDotFile("src/main/resources/undirWeightedMultiGraph.dot");
- // --- Construction du réseau ---
- FlowNetwork network = new FlowNetwork(g, 1, 5);
-
- // --- Calcul du flot maximum ---
- FordFulkerson solver = new FordFulkerson();
- int maxFlow = solver.computeMaxFlow(network);
-
- System.out.println("Maximum flow = " + maxFlow);
- } else {
- System.out.println("Aucun fichier spécifié. Création manuelle du graphe...");
- Graph g = Graph.fromDotFile("src/main/resources/undirWeightedMultiGraph.dot");
-
- // --- Construction du réseau ---
- FlowNetwork network = new FlowNetwork(g, 1, 5);
-
- // --- Calcul du flot maximum ---
- FordFulkerson solver = new FordFulkerson();
- int maxFlow = solver.computeMaxFlow(network);
-
- System.out.println("Maximum flow = " + maxFlow);
- }
- }
-}
diff --git a/src/main/java/MaximumFlow/PathFinder.java b/src/main/java/MaximumFlow/PathFinder.java
deleted file mode 100644
index 77cde86..0000000
--- a/src/main/java/MaximumFlow/PathFinder.java
+++ /dev/null
@@ -1,62 +0,0 @@
-package MaximumFlow;
-
-import m1graphs2025.*;
-import java.util.*;
-
-public class PathFinder {
-
- /**
- * BFS standard dans le graphe résiduel.
- * Retourne un chemin s -> t en liste de Node.
- */
- public static List findPathBFS(ResidualGraph residual, int sourceId, int sinkId) {
-
- Graph g = residual.getGraph();
-
- Node s = g.getNode(sourceId);
- Node t = g.getNode(sinkId);
-
- if (s == null || t == null)
- return null;
-
- // BFS
- Map parent = new HashMap<>();
- Queue queue = new LinkedList<>();
-
- parent.put(s, null);
- queue.add(s);
-
- while (!queue.isEmpty()) {
-
- Node u = queue.poll();
-
- if (u.equals(t))
- break;
-
- for (Edge e : g.getOutEdges(u)) {
-
- if (e.getWeight() <= 0)
- continue;
-
- Node v = e.getNodeTo();
-
- if (!parent.containsKey(v)) {
- parent.put(v, u);
- queue.add(v);
- }
- }
- }
-
- // Pas de chemin
- if (!parent.containsKey(t))
- return null;
-
- // Reconstruction du chemin
- List path = new ArrayList<>();
- for (Node v = t; v != null; v = parent.get(v)) {
- path.add(0, v);
- }
-
- return path;
- }
-}
diff --git a/src/main/java/MaximumFlow/ResidualGraph.java b/src/main/java/MaximumFlow/ResidualGraph.java
deleted file mode 100644
index 23cf5fb..0000000
--- a/src/main/java/MaximumFlow/ResidualGraph.java
+++ /dev/null
@@ -1,156 +0,0 @@
-package MaximumFlow;
-
-import m1graphs2025.*;
-import java.util.*;
-import java.io.FileWriter;
-import java.io.IOException;
-
-public class ResidualGraph {
-
- private Graph graph; // graphe résiduel complet
-
- // ---------------------------------------------------------
- // CONSTRUCTEUR : construit le graphe résiduel à partir du FlowNetwork
- // ---------------------------------------------------------
- public ResidualGraph(FlowNetwork network) {
-
- graph = new Graph(new HashMap<>());
-
- // 1) Ajouter tous les nœuds du graphe de base
- for (Node n : network.getBaseGraph().getAllNodes()) {
- graph.addNode(n.getId());
- }
-
- // 2) Ajouter les arcs résiduels directs + inverses
- for (Edge e : network.getBaseGraph().getAllEdges()) {
-
- Node u = e.getNodeFrom();
- Node v = e.getNodeTo();
-
- int resCap = network.getResidualCapacity(u, v); // cap(u,v) - flow(u,v)
- int backCap = network.getFlow(u, v); // flux(u,v)
-
- // (u -> v) direct si capacité résiduelle > 0
- if (resCap > 0) {
- addOrReplaceEdge(u.getId(), v.getId(), resCap);
- }
-
- // (v -> u) inverse si flux > 0
- if (backCap > 0) {
- addOrReplaceEdge(v.getId(), u.getId(), backCap);
- }
- }
- }
-
- // ---------------------------------------------------------
- // GETTER : récupérer le graphe résiduel
- // ---------------------------------------------------------
- public Graph getGraph() {
- return graph;
- }
-
- // ---------------------------------------------------------
- // OBTENIR LA CAPACITÉ RÉSIDUELLE D'UN ARC
- // ---------------------------------------------------------
- public int getResidualCapacity(Node u, Node v) {
- List edges = graph.getEdges(u, v);
- if (edges == null || edges.isEmpty()) return 0;
- return edges.get(0).getWeight();
- }
-
- // ---------------------------------------------------------
- // TROUVER LA CAPACITÉ MINIMALE SUR UN CHEMIN
- // ---------------------------------------------------------
- public int minCapacity(List path) {
- int min = Integer.MAX_VALUE;
- for (int i = 0; i < path.size() - 1; i++) {
- Node u = path.get(i);
- Node v = path.get(i + 1);
- List edges = graph.getEdges(u, v);
- if (edges == null || edges.isEmpty()) {
- throw new IllegalStateException("Arc manquant : " + u.getId() + " -> " + v.getId());
- }
- int cap = edges.get(0).getWeight();
- if (cap < min) min = cap;
- }
- return min;
- }
-
- // ---------------------------------------------------------
- // MÉTHODE UTILITAIRE : remplace toute arête existante (u → v)
- // ---------------------------------------------------------
- private void addOrReplaceEdge(int from, int to, int capacity) {
- Node u = graph.getNode(from);
- Node v = graph.getNode(to);
-
- List current = new ArrayList<>(graph.getEdges(u, v));
- for (Edge e : current) {
- graph.removeEdge(e);
- }
-
- graph.addEdge(from, to, capacity);
- }
-
- // ---------------------------------------------------------
- // EXPORTER EN DOT AVEC CHEMIN AUGMENTANT
- // ---------------------------------------------------------
- public void toDotFile(String filename, List augmentingPath, int step) {
- int bottleneck = minCapacity(augmentingPath);
-
- try (FileWriter writer = new FileWriter(filename)) {
- writer.write("digraph residualGraph" + step + " {\n");
- writer.write(" rankdir=\"LR\";\n");
- writer.write(" label=\"(" + step + ") residual graph.\\nAugmenting path: " +
- nodeListToString(augmentingPath) +
- ".\\nResidual capacity: " + bottleneck + "\";\n");
-
- for (Node u : graph.getAllNodes()) {
- for (Edge e : graph.getOutEdges(u)) {
- Node v = e.getNodeTo();
- int cap = e.getWeight();
-
- String color = "black";
- String penwidth = "1";
- String fontcolor = "black";
-
- // Vérifie si l'arête est dans le chemin augmentant
- for (int i = 0; i < augmentingPath.size() - 1; i++) {
- if (u.equals(augmentingPath.get(i)) && v.equals(augmentingPath.get(i + 1))) {
- color = "blue";
- penwidth = "3";
- if (cap == bottleneck) fontcolor = "red";
- break;
- }
- }
-
- writer.write(" " + u.getId() + " -> " + v.getId() +
- " [label=" + cap +
- ", len=" + cap +
- ", penwidth=" + penwidth +
- ", color=\"" + color + "\"" +
- ", fontcolor=\"" + fontcolor + "\"];\n");
- }
- }
-
- writer.write("}\n");
- System.out.println("DOT file saved: " + filename);
- } catch (IOException e) {
- System.err.println("[Erreur] Impossible d'écrire le fichier DOT : " + e.getMessage());
- }
- }
-
- private String nodeListToString(List path) {
- StringBuilder sb = new StringBuilder("[");
- for (int i = 0; i < path.size(); i++) {
- sb.append(path.get(i).getId());
- if (i < path.size() - 1) sb.append(", ");
- }
- sb.append("]");
- return sb.toString();
- }
-
- @Override
- public String toString() {
- return graph.toString();
- }
-}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java
new file mode 100644
index 0000000..a45a5f1
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java
@@ -0,0 +1,23 @@
+package m1graphs2025.MaximumFlow;
+
+import java.util.*;
+
+import m1graphs2025.pw2.*;
+
+/**
+ * Interface stratégie pour trouver un chemin augmentant dans un graphe résiduel.
+ * Implémentations proposées : BFSPathFinder (Edmonds-Karp), DFSPathFinder (classic),
+ * MaxCapacityPathFinder (chemin à plus grande capacité résiduelle).
+ */
+public interface AugmentingPathFinder {
+ /**
+ * Doit retourner une liste de Node formant un chemin de source à sink dans
+ * le résiduel, ou null si aucun chemin n'existe.
+ *
+ * @param a3ResidualGraph le ResidualGraph construit à partir du FlowNetwork
+ * @param sourceId id du nœud source (selon Graph)
+ * @param sinkId id du nœud puits
+ * @return List chemin [s,...,t] ou null
+ */
+ List findAugmentingPath(ResidualGraph a3ResidualGraph, int sourceId, int sinkId);
+}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/BFSPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/BFSPathFinder.java
new file mode 100644
index 0000000..5cd9f0a
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/BFSPathFinder.java
@@ -0,0 +1,49 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * BFSPathFinder: trouve le chemin avec le moins d'arêtes (Edmonds-Karp).
+ * Complexité : O(E) par BFS ; Edmonds-Karp donne O(V * E^2) au pire pour Ford-Fulkerson
+ * si on l'utilise comme stratégie de chemin.
+ */
+public class BFSPathFinder implements AugmentingPathFinder {
+
+ @Override
+ public List findAugmentingPath(ResidualGraph a3ResidualGraph, int sourceId, int sinkId) {
+ Graph g = a3ResidualGraph.getGraph();
+
+ Queue queue = new LinkedList<>();
+ Map parent = new HashMap<>(); // parent[v] = u.id
+
+ Node s = g.getNode(sourceId);
+ Node t = g.getNode(sinkId);
+ if (s == null || t == null) return null;
+
+ queue.add(s);
+ parent.put(s.getId(), -1);
+
+ while (!queue.isEmpty()) {
+ Node u = queue.poll();
+ for (Edge e : g.getOutEdges(u)) {
+ Node v = e.getNodeTo();
+ if (!parent.containsKey(v.getId())) {
+ parent.put(v.getId(), u.getId());
+ if (v.getId() == sinkId) return buildPath(g, parent, sourceId, sinkId);
+ queue.add(v);
+ }
+ }
+ }
+ return null; // aucun chemin trouvé*/
+ }
+
+ private List buildPath(Graph g, Map parent, int s, int t) {
+ List path = new ArrayList<>();
+ for (int cur = t; cur != -1; cur = parent.get(cur)) {
+ path.add(g.getNode(cur));
+ }
+ Collections.reverse(path);
+ return path;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/DFSPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/DFSPathFinder.java
new file mode 100644
index 0000000..95a3a3a
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/DFSPathFinder.java
@@ -0,0 +1,51 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * DFSPathFinder : parcours en profondeur pour trouver un chemin (premier trouvé).
+ * Peut être très efficace sur certains graphes, mais son choix de chemin est non déterministe
+ * (dépend de l'ordre des arcs) et peut conduire à de nombreux itérations.
+ */
+public class DFSPathFinder implements AugmentingPathFinder {
+
+ @Override
+ public List findAugmentingPath(ResidualGraph a3ResidualGraph, int sourceId, int sinkId) {
+ Graph g = a3ResidualGraph.getGraph();
+ Node s = g.getNode(sourceId);
+ Node t = g.getNode(sinkId);
+ if (s == null || t == null) return null;
+
+ Set visited = new HashSet<>();
+ Map parent = new HashMap<>();
+
+ boolean found = dfs(g, s, t.getId(), visited, parent);
+ if (!found) return null;
+ return buildPath(g, parent, sourceId, sinkId);
+ }
+
+ private boolean dfs(Graph g, Node u, int targetId, Set visited, Map parent) {
+ visited.add(u.getId());
+ if (u.getId() == targetId) return true;
+ for (Edge e : g.getOutEdges(u)) {
+ Node v = e.getNodeTo();
+ if (!visited.contains(v.getId())) {
+ parent.put(v.getId(), u.getId());
+ boolean found = dfs(g, v, targetId, visited, parent);
+ if (found) return true;
+ }
+ }
+ return false;
+ }
+
+ private List buildPath(Graph g, Map parent, int s, int t) {
+ List path = new ArrayList<>();
+ for (int cur = t; cur != s; cur = parent.get(cur)) {
+ path.add(g.getNode(cur));
+ }
+ path.add(g.getNode(s));
+ Collections.reverse(path);
+ return path;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java b/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java
new file mode 100644
index 0000000..1a21f85
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java
@@ -0,0 +1,115 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * DotExporter : méthodes utilitaires pour exporter :
+ * - un FlowNetwork sous forme d'un fichier DOT (labels "f/c")
+ * - un ResidualGraph sous forme d'un fichier DOT (avec mise en évidence d'un chemin)
+ *
+ * Les méthodes sont volontairement explicites et génèrent des fichiers similaires
+ * à ceux montrés dans l'énoncé du sujet.
+ */
+public class DotExporter {
+
+ // Exporte le flow courant du FlowNetwork en .gv
+ public void exportFlow(FlowNetwork network, String filename, int step) {
+ int totalFlow = network.computeTotalFlow();
+ Graph g = network.getBaseGraph();
+
+ try (FileWriter writer = new FileWriter(filename)) {
+ writer.write("digraph flow" + step + " {\n");
+ writer.write("\trankdir=\"LR\";\n");
+ writer.write("\tlabel=\"(" + step + ") Flow. Value: " + totalFlow + "\";\n");
+
+ for (Edge e : g.getAllEdges()) {
+ Node u = e.getNodeFrom();
+ Node v = e.getNodeTo();
+ int f = network.getFlow(u, v);
+ int c = network.getCapacity(u, v);
+ writer.write("\t" + u.getId() + " -> " + v.getId()
+ + " [label=\"" + f + "/" + c + "\", len=" + c + "];\n");
+ }
+
+ writer.write("}\n");
+ } catch (IOException ex) {
+ System.err.println("[DotExporter] Erreur écriture flow : " + ex.getMessage());
+ }
+ }
+
+ /**
+ * Exporte un ResidualGraph.
+ * @param rg residual graph
+ * @param filename nom du fichier .gv
+ * @param step numéro d'étape
+ * @param path chemin augmentant (peut être null)
+ * @param delta valeur résiduelle utilisée (0 si none)
+ */
+ public void exportResidual(ResidualGraph rg, String filename, int step, List path, int delta) {
+ Graph g = rg.getGraph();
+ try (FileWriter writer = new FileWriter(filename)) {
+ writer.write("digraph residualGraph" + step + " {\n");
+ writer.write("\trankdir=\"LR\";\n");
+
+ // Label : si chemin fourni, on l'affiche sinon on dit "none"
+ if (path != null && !path.isEmpty()) {
+ writer.write("\tlabel=\"(" + step + ") residual graph.\n\tAugmenting path: " + pathToString(path) +
+ ".\n\tResidual capacity: " + delta + "\";\n");
+ } else {
+ writer.write("\tlabel=\"(" + step + ") residual graph.\n\tAugmenting path: none.\";\n");
+ }
+
+ // Arêtes résiduelles : on parcourt toutes les arêtes du Graph rg
+ for (Edge e : g.getAllEdges()) {
+ Node u = e.getNodeFrom();
+ Node v = e.getNodeTo();
+ int w = e.getWeight();
+
+ // Style : si l'arête appartient au chemin augmentant -> color/penwidth
+ boolean onPath = isEdgeInPath(u, v, path);
+ String style = "";
+
+ // Si l'arête est dans le chemin augmentant → surlignage bleu
+ if (onPath) {
+ style += ", penwidth=3, color=\"blue\"";
+
+ // Si la capacité résiduelle est nulle → arête saturée → texte en rouge
+ if (w == delta) {
+ style += ", fontcolor=\"red\"";
+ }
+ }
+
+ // Écriture dans le fichier DOT
+ writer.write("\t" + u.getId() + " -> " + v.getId()
+ + " [label=\"" + w + "\", len=" + w + style + "];\n");
+ }
+
+ writer.write("}\n");
+ } catch (IOException ex) {
+ System.err.println("[DotExporter] Erreur écriture residual : " + ex.getMessage());
+ }
+ }
+
+ // utilitaire pour savoir si u->v est dans le chemin (path)
+ private boolean isEdgeInPath(Node u, Node v, List path) {
+ if (path == null) return false;
+ for (int i = 0; i < path.size() - 1; i++) {
+ if (path.get(i).getId() == u.getId() && path.get(i+1).getId() == v.getId()) return true;
+ }
+ return false;
+ }
+
+ // utilitaire pour formater le path lisiblement
+ private String pathToString(List path) {
+ StringBuilder sb = new StringBuilder("[");
+ for (int i = 0; i < path.size(); i++) {
+ sb.append(path.get(i).getId());
+ if (i + 1 < path.size()) sb.append(", ");
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/FlowMap.java b/src/main/java/m1graphs2025/MaximumFlow/FlowMap.java
new file mode 100644
index 0000000..89ddf60
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/FlowMap.java
@@ -0,0 +1,77 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * FlowMap : stockage des flux f(u,v).
+ * - On stocke uniquement des flux non-négatifs.
+ * - Clé : "uId:vId".
+ *
+ * Cette classe permet d'isoler la notion de flux de la structure du graphe,
+ * ce qui facilite la construction du graphe résiduel indépendamment du graphe de base.
+ */
+public class FlowMap {
+
+ // map "u:v" -> flux (int)
+ private Map flowmap = new HashMap<>();
+
+ // Crée la clé à partir d'objets Node
+ private String key(Node u, Node v) {
+ return u.getId() + ":" + v.getId();
+ }
+
+ // Crée la clé à partir d'entiers (IDs)
+ private String key(int u, int v) {
+ return u + ":" + v;
+ }
+
+ // Récupère le flux f(u, v) (0 si absent)
+ public int get(Node u, Node v) {
+ return flowmap.getOrDefault(key(u, v), 0);
+ }
+
+ public int get(int u, int v) {
+ return flowmap.getOrDefault(key(u, v), 0);
+ }
+
+ // Définit le flux (vérifie qu'il n'est pas négatif)
+ public void set(Node u, Node v, int val) {
+ if (val < 0) throw new IllegalArgumentException("Flux négatif interdit : " + val);
+ flowmap.put(key(u, v), val);
+ }
+
+ public void set(int u, int v, int val) {
+ if (val < 0) throw new IllegalArgumentException("Flux négatif interdit : " + val);
+ flowmap.put(key(u, v), val);
+ }
+
+ // Ajoute val au flux actuel (val peut être négatif si on enlève du flux,
+ // mais le résultat final ne doit pas être négatif)
+ public void add(Node u, Node v, int val) {
+ int newVal = get(u, v) + val;
+ if (newVal < 0) throw new IllegalArgumentException("Flux négatif interdit : " + newVal);
+ flowmap.put(key(u, v), newVal);
+ }
+
+ public void add(int u, int v, int val) {
+ int newVal = get(u, v) + val;
+ if (newVal < 0) throw new IllegalArgumentException("Flux négatif interdit : " + newVal);
+ flowmap.put(key(u, v), newVal);
+ }
+
+ // Retourne les clés "u:v" actuellement présentes
+ public Set keys() {
+ return flowmap.keySet();
+ }
+
+ // Retourne les entrées (utile pour exporter)
+ public Set> entries() {
+ return flowmap.entrySet();
+ }
+
+ // Réinitialise tous les flux à 0
+ public void reset() {
+ flowmap.clear();
+ }
+}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java b/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java
new file mode 100644
index 0000000..23c681c
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java
@@ -0,0 +1,144 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+import java.io.FileWriter;
+import java.io.IOException;
+
+/**
+ * FlowNetwork : représente le réseau de capacité + le flux courant.
+ *
+ * - baseGraph : graphe de capacité (objet Graph de PW2)
+ * - capacity : stockage des capacités (clé "u:v" -> int)
+ * - flow : FlowMap contenant f(u,v)
+ * - sourceId, sinkId : identifiants des nœuds source et puits
+ *
+ * La classe fournit des utilitaires pour :
+ * - obtenir la capacité d'une arête
+ * - obtenir la capacité résiduelle
+ * - mettre à jour le flux à la suite d'un chemin augmentant
+ * - exporter l'état du flux (DOT)
+ */
+public class FlowNetwork {
+
+ private Graph baseGraph; // Graphe de base (capacités)
+ private FlowMap flow = new FlowMap();
+ private Map capacity = new HashMap<>();
+ private int sourceId;
+ private int sinkId;
+
+ public FlowNetwork(Graph baseGraph, int sourceId, int sinkId) {
+ this.baseGraph = baseGraph;
+ this.sourceId = sourceId;
+ this.sinkId = sinkId;
+
+ // Charger les capacités depuis le graphe de base
+ for (Edge e : baseGraph.getAllEdges()) {
+ Node u = e.getNodeFrom();
+ Node v = e.getNodeTo();
+ capacity.put(key(u, v), e.getWeight());
+ }
+ }
+
+ // Encodage clé "u:v"
+ private String key(Node u, Node v) {
+ return u.getId() + ":" + v.getId();
+ }
+
+ // Accesseurs utiles
+ public Graph getBaseGraph() { return baseGraph; }
+ public int getSourceId() { return sourceId; }
+ public int getSinkId() { return sinkId; }
+
+ // Retourne la capacité c(u,v) (0 si pas d'arête)
+ public int getCapacity(Node u, Node v) {
+ return capacity.getOrDefault(key(u, v), 0);
+ }
+
+ // Setter capacité (utile si on veut modifier le réseau dynamiquement)
+ public void setCapacity(Node u, Node v, int val) {
+ capacity.put(key(u, v), val);
+ }
+
+ // Retourne le flux f(u,v)
+ public int getFlow(Node u, Node v) {
+ return flow.get(u, v);
+ }
+
+ public int getFlow(int u, int v) { return flow.get(u, v); }
+
+ // Calcule la capacité résiduelle c_f(u,v) = c(u,v) - f(u,v)
+ public int getResidualCapacity(Node u, Node v) {
+ return getCapacity(u, v) - getFlow(u, v);
+ }
+
+ // Génère un ResidualGraph à partir de l'état courant du flow
+ public ResidualGraph toResidualGraph() {
+ return new ResidualGraph(this);
+ }
+
+ /**
+ * Met à jour le flux le long d'un chemin augmentant.
+ * Le chemin est une liste de Node [s, ..., t].
+ *
+ * Pour chaque paire (u, v) du chemin :
+ * - si arc direct u->v existait dans la capacité (c(u,v)>0), on ajoute delta à f(u,v)
+ * - sinon (on a pris un arc inversé v->u dans le résiduel), on soustrait delta à f(v,u)
+ *
+ * On suppose que delta est le goulot calculé sur le chemin.
+ */
+ public void updateFlow(List path, int delta) {
+ for (int i = 0; i < path.size() - 1; i++) {
+ Node u = path.get(i);
+ Node v = path.get(i + 1);
+
+ if (getCapacity(u, v) > 0) {
+ // arc direct : augmenter le flux
+ flow.add(u, v, delta);
+ } else {
+ // arc inverse : diminuer le flux sur v->u
+ flow.add(v, u, -delta);
+ }
+ }
+ }
+
+ // Calcul du flux total (sortie de la source)
+ public int computeTotalFlow() {
+ Node s = baseGraph.getNode(sourceId);
+ int total = 0;
+ for (Edge e : baseGraph.getOutEdges(s)) {
+ total += getFlow(e.getNodeFrom(), e.getNodeTo());
+ }
+ return total;
+ }
+
+ // Export du flow courant en DOT, label f/c sur chaque arête
+ public void toDotFileFlow(String filename, int step) {
+ int totalFlow = computeTotalFlow();
+
+ try (FileWriter writer = new FileWriter(filename)) {
+ writer.write("digraph flow" + step + " {\n");
+ writer.write(" rankdir=\"LR\";\n");
+ writer.write(" label=\"(" + step + ") Flow. Value: " + totalFlow + "\";\n");
+
+ for (Edge e : baseGraph.getAllEdges()) {
+ Node u = e.getNodeFrom();
+ Node v = e.getNodeTo();
+ int f = getFlow(u, v);
+ int c = getCapacity(u, v);
+ writer.write(" " + u.getId() + " -> " + v.getId()
+ + " [label=\"" + f + "/" + c + "\", len=" + c + "];\n");
+ }
+
+ writer.write("}\n");
+ System.out.println("Flow DOT file saved: " + filename);
+ } catch (IOException ex) {
+ System.err.println("[Erreur] Impossible d'écrire le fichier DOT : " + ex.getMessage());
+ }
+ }
+
+ // Accesseur FlowMap (utile pour inspections/export)
+ public FlowMap getFlowMap() {
+ return flow;
+ }
+}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/FlowNetworkValidator.java b/src/main/java/m1graphs2025/MaximumFlow/FlowNetworkValidator.java
new file mode 100644
index 0000000..67b8f41
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/FlowNetworkValidator.java
@@ -0,0 +1,39 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+
+/**
+ * Vérifie si le graphe lu est bien un flow network :
+ * - présence des labels 's' et 't'
+ * - pas d'arcs entrants vers la source (optionnel selon conventions)
+ * - pas d'arcs sortants du puits (optionnel selon conventions)
+ * - capacities >= 0
+ *
+ * Cette classe peut être complétée pour appliquer toutes les règles vues en cours.
+ */
+public class FlowNetworkValidator {
+
+ // Retourne true si le Graph respecte les contraintes de base du sujet
+ public static boolean validate(Graph g, int sourceId, int sinkId) {
+ // Exemples de vérifications simples :
+ if (g.getNode(sourceId) == null) {
+ System.err.println("[Validator] Source absente.");
+ return false;
+ }
+ if (g.getNode(sinkId) == null) {
+ System.err.println("[Validator] Sink absente.");
+ return false;
+ }
+
+ // Vérifier non-négativité des capacités
+ for (Edge e : g.getAllEdges()) {
+ if (e.getWeight() < 0) {
+ System.err.println("[Validator] Capacité négative sur l'arête " +
+ e.getNodeFrom().getId() + "->" + e.getNodeTo().getId());
+ return false;
+ }
+ }
+ // D'autres contrôles (optionnels) : connexité s->t, numérotation des noeuds, etc.
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/FordFulkerson.java b/src/main/java/m1graphs2025/MaximumFlow/FordFulkerson.java
new file mode 100644
index 0000000..203683c
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/FordFulkerson.java
@@ -0,0 +1,84 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * FordFulkerson : exécute la méthode générale :
+ * - tant qu'il existe un chemin augmentant dans le graphe résiduel
+ * * calculer le goulot delta
+ * * mettre à jour le flux le long du chemin
+ * * exporter les fichiers DOT (résiduel + flow) si demandé
+ *
+ * Le choix du finder influe sur la performance (BFS -> Edmonds-Karp, DFS -> Ford-Fulkerson classique).
+ */
+public class FordFulkerson {
+
+ private FlowNetwork network;
+ private AugmentingPathFinder finder;
+ private DotExporter exporter;
+
+ public FordFulkerson(FlowNetwork network, AugmentingPathFinder finder, DotExporter exporter) {
+ this.network = network;
+ this.finder = finder;
+ this.exporter = exporter;
+ }
+
+ /**
+ * Exécute l'algorithme, écrivant les fichiers DOT au fur et à mesure.
+ * @param outPrefix préfixe ou dossier de sortie (ex: "output/flow")
+ * @return valeur du flux maximum atteint
+ */
+ public int run(String outPrefix) {
+ int flowstep = 1;
+ int residualstep = 1;
+
+ // Exporter le flow initial (valeur 0)
+ exporter.exportFlow(network, outPrefix + "flow" + flowstep + ".gv", flowstep);
+ flowstep++;
+
+ while (true) {
+ ResidualGraph rg = network.toResidualGraph();
+ // exporter le résiduel avant recherche (optionnel)
+ exporter.exportResidual(rg, outPrefix + "residualGraph" + residualstep + ".gv", residualstep, null, 0);
+
+ List path = finder.findAugmentingPath(rg, network.getSourceId(), network.getSinkId());
+ if (path == null) {
+ // plus d'augmenting path => max flow atteint
+ exporter.exportResidual(rg, outPrefix + "residualGraph" + residualstep + ".gv", residualstep, null, 0);
+ break;
+ }
+
+ // calculer le goulot (bottleneck) = min residual capacity le long du chemin
+ int delta = computeBottleneck(rg, path);
+
+ // mettre à jour le flow dans le FlowNetwork
+ network.updateFlow(path, delta);
+
+ // exporter fichiers : résiduel (avec indication de chemin + delta) puis flow induit
+ exporter.exportResidual(rg, outPrefix + "residualGraph" + residualstep + ".gv", residualstep, path, delta);
+ exporter.exportFlow(network, outPrefix + "flow" + (flowstep) + ".gv", flowstep);
+
+ flowstep += 1; // incrémenter étape (flowX)
+ residualstep += 1; // incrémenter étape (residGraphX)
+
+ }
+
+ // retour du flux total final
+ return network.computeTotalFlow();
+ }
+
+ // calcule le minimum des capacités résiduelles le long du chemin
+ private int computeBottleneck(ResidualGraph rg, List path) {
+ Graph g = rg.getGraph();
+ int delta = Integer.MAX_VALUE;
+ for (int i = 0; i < path.size() - 1; i++) {
+ Node u = path.get(i);
+ Node v = path.get(i+1);
+ Edge e = u.getEdgesTo(v).get(0);
+ if (e == null) return 0; // sécurité
+ delta = Math.min(delta, e.getWeight());
+ }
+ return delta;
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/Main.java b/src/main/java/m1graphs2025/MaximumFlow/Main.java
new file mode 100644
index 0000000..29d96b5
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/Main.java
@@ -0,0 +1,63 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+
+/**
+ * Main : point d'entrée du programme sans arguments.
+ *
+ * Les fichiers input/output sont définis directement dans le code.
+ */
+public class Main {
+
+ public static void main(String[] args) {
+
+ // 🔹 Chemin du fichier d'entrée (graphe DOT)
+ String inputFile = "input/example_flow.dot";
+
+ // 🔹 Dossier où les fichiers .gv générés seront enregistrés
+ String outFolder = "output/";
+
+ // S'assure que le chemin se termine par '/'
+ if (!outFolder.endsWith("/")) outFolder += "/";
+
+ try {
+ // Lecture du DOT en utilisant DotReader (PW2)
+ Graph base = Graph.fromDotFile(inputFile);
+
+ // Identifie source 's' et sink 't' via leur label (convention du sujet)
+ int sourceId = base.smallestNodeId();
+ int sinkId = base.largestNodeId();
+
+ // Vérification de validité du réseau de flux
+ if (!FlowNetworkValidator.validate(base, sourceId, sinkId)) {
+ System.err.println("Graphe d'entrée invalide.");
+ return;
+ }
+
+ // Construction du réseau de flux
+ FlowNetwork network = new FlowNetwork(base, sourceId, sinkId);
+
+ // Export DOT
+ DotExporter exporter = new DotExporter();
+
+ // Choix de l'algorithme de recherche de chemin augmentant
+ //AugmentingPathFinder finder = new BFSPathFinder(); // Edmonds-Karp (recommandé)
+ AugmentingPathFinder finder = new DFSPathFinder(); // Ford-Fulkerson classique
+ // AugmentingPathFinder finder = new MaxCapacityPathFinder(); // plus grande capacité
+
+ // Lancement de l'algorithme
+ FordFulkerson algo = new FordFulkerson(network, finder, exporter);
+ int maxFlow = algo.run(outFolder);
+
+ // Affiche le résultat final
+ System.out.println("\n-----------------------------------");
+ System.out.println(" Maximum Flow = " + maxFlow);
+ System.out.println("-----------------------------------");
+ System.out.println("Les fichiers DOT sont disponibles dans : " + outFolder);
+
+ } catch (Exception e) {
+ System.err.println("Erreur lors de l'exécution : " + e.getMessage());
+ e.printStackTrace();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/MaxCapacityPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/MaxCapacityPathFinder.java
new file mode 100644
index 0000000..b3c8f65
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/MaxCapacityPathFinder.java
@@ -0,0 +1,23 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * MaxCapacityPathFinder : heuristique qui favorise les chemins dont le
+ * goulot (minimum des capacités le long du chemin) est maximal.
+ *
+ * Implémentation simple : variation de Dijkstra où la "distance" d'une route
+ * est la capacité minimale le long du chemin ; on cherche à maximiser cette valeur.
+ *
+ * Ce n'est pas nécessairement optimal mais tend à réduire le nombre d'itérations.
+ */
+public class MaxCapacityPathFinder implements AugmentingPathFinder {
+
+ @Override
+ public List findAugmentingPath(ResidualGraph a3ResidualGraph, int sourceId, int sinkId) {
+
+ /* à implémenter */
+ return null;
+ }
+}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/ResidualGraph.java b/src/main/java/m1graphs2025/MaximumFlow/ResidualGraph.java
new file mode 100644
index 0000000..a5d3aa1
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/ResidualGraph.java
@@ -0,0 +1,50 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+
+/**
+ * ResidualGraph construit le graphe résiduel à partir d'un FlowNetwork donné.
+ *
+ * On suit la construction standard :
+ * - Pour chaque arête (u,v) de base :
+ * * si c(u,v) - f(u,v) > 0 alors on ajoute (u -> v) avec poids c(u,v)-f(u,v)
+ * * si f(u,v) > 0 alors on ajoute (v -> u) avec poids f(u,v)
+ *
+ * Le graphe retourné est un Graph (m1graphs2025.pw2.Graph) prêt à être parcouru.
+ */
+public class ResidualGraph {
+
+ private Graph graph; // graphe résiduel concret
+ private FlowNetwork network; // référence au réseau original (pour consulter capacities & flows)
+
+ public ResidualGraph(FlowNetwork network) {
+ this.network = network;
+ this.graph = new Graph();
+
+ // Copier les nœuds (id & labels) depuis le graphe de base
+ for (Node n : network.getBaseGraph().getAllNodes()) {
+ graph.addNode(n.getId());
+ }
+
+ // Construire les arêtes résiduelles
+ for (Edge e : network.getBaseGraph().getAllEdges()) {
+ Node u = e.getNodeFrom();
+ Node v = e.getNodeTo();
+
+ int forward = network.getResidualCapacity(u, v); // c - f
+ int backward = network.getFlow(u, v); // f
+
+ if (forward > 0) {
+ graph.addEdge(u.getId(), v.getId(), forward); // arc direct résiduel
+ }
+ if (backward > 0) {
+ graph.addEdge(v.getId(), u.getId(), backward); // arc inverse
+ }
+ }
+ }
+
+ // Retourne l'objet Graph du package PW2 représentant le résiduel
+ public Graph getGraph() {
+ return graph;
+ }
+}
diff --git a/src/main/java/m1graphs2025/Edge.java b/src/main/java/m1graphs2025/pw2/Edge.java
similarity index 99%
rename from src/main/java/m1graphs2025/Edge.java
rename to src/main/java/m1graphs2025/pw2/Edge.java
index 378dcdf..b5c5d18 100644
--- a/src/main/java/m1graphs2025/Edge.java
+++ b/src/main/java/m1graphs2025/pw2/Edge.java
@@ -1,4 +1,4 @@
-package m1graphs2025;
+package m1graphs2025.pw2;
import java.util.*;
diff --git a/src/main/java/m1graphs2025/EdgeVisitType.java b/src/main/java/m1graphs2025/pw2/EdgeVisitType.java
similarity index 79%
rename from src/main/java/m1graphs2025/EdgeVisitType.java
rename to src/main/java/m1graphs2025/pw2/EdgeVisitType.java
index 7e86722..eb4a601 100644
--- a/src/main/java/m1graphs2025/EdgeVisitType.java
+++ b/src/main/java/m1graphs2025/pw2/EdgeVisitType.java
@@ -1,4 +1,4 @@
-package m1graphs2025;
+package m1graphs2025.pw2;
/**
* Enum for DFS edge types.
diff --git a/src/main/java/m1graphs2025/Graph.java b/src/main/java/m1graphs2025/pw2/Graph.java
similarity index 99%
rename from src/main/java/m1graphs2025/Graph.java
rename to src/main/java/m1graphs2025/pw2/Graph.java
index e1917aa..4d58003 100644
--- a/src/main/java/m1graphs2025/Graph.java
+++ b/src/main/java/m1graphs2025/pw2/Graph.java
@@ -1,4 +1,4 @@
-package m1graphs2025;
+package m1graphs2025.pw2;
import java.io.BufferedReader;
import java.io.FileReader;
@@ -8,10 +8,8 @@ import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.concurrent.atomic.AtomicInteger;
-import java.util.regex.Matcher;
-import java.util.regex.Pattern;
-import m1graphs2025.NodeVisitInfo.NodeColour;
+import m1graphs2025.pw2.NodeVisitInfo.NodeColour;
public class Graph {
diff --git a/src/main/java/m1graphs2025/Node.java b/src/main/java/m1graphs2025/pw2/Node.java
similarity index 99%
rename from src/main/java/m1graphs2025/Node.java
rename to src/main/java/m1graphs2025/pw2/Node.java
index 1f7e600..237d53a 100644
--- a/src/main/java/m1graphs2025/Node.java
+++ b/src/main/java/m1graphs2025/pw2/Node.java
@@ -1,4 +1,4 @@
-package m1graphs2025;
+package m1graphs2025.pw2;
import java.util.*;
diff --git a/src/main/java/m1graphs2025/NodeVisitInfo.java b/src/main/java/m1graphs2025/pw2/NodeVisitInfo.java
similarity index 99%
rename from src/main/java/m1graphs2025/NodeVisitInfo.java
rename to src/main/java/m1graphs2025/pw2/NodeVisitInfo.java
index e8d4469..98dd7ca 100644
--- a/src/main/java/m1graphs2025/NodeVisitInfo.java
+++ b/src/main/java/m1graphs2025/pw2/NodeVisitInfo.java
@@ -1,4 +1,4 @@
-package m1graphs2025;
+package m1graphs2025.pw2;
/**
* Représente les informations de visite d’un nœud lors d’un parcours en profondeur (DFS).
diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/pw2/UndirectedGraph.java
similarity index 99%
rename from src/main/java/m1graphs2025/UndirectedGraph.java
rename to src/main/java/m1graphs2025/pw2/UndirectedGraph.java
index 493f310..585dc06 100644
--- a/src/main/java/m1graphs2025/UndirectedGraph.java
+++ b/src/main/java/m1graphs2025/pw2/UndirectedGraph.java
@@ -1,11 +1,8 @@
-package m1graphs2025;
+package m1graphs2025.pw2;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
diff --git a/src/test/java/m1graphs2025/TestGraphPart1.java b/src/test/java/m1graphs2025/TestGraphPart1.java
index b2d8bb0..f4e3152 100644
--- a/src/test/java/m1graphs2025/TestGraphPart1.java
+++ b/src/test/java/m1graphs2025/TestGraphPart1.java
@@ -1,5 +1,9 @@
package m1graphs2025;
+import m1graphs2025.pw2.Edge;
+import m1graphs2025.pw2.Graph;
+import m1graphs2025.pw2.Node;
+
import java.util.Collections;
import java.util.List;
diff --git a/src/test/java/m1graphs2025/TestGraphPart2.java b/src/test/java/m1graphs2025/TestGraphPart2.java
index d764977..a22e57f 100644
--- a/src/test/java/m1graphs2025/TestGraphPart2.java
+++ b/src/test/java/m1graphs2025/TestGraphPart2.java
@@ -1,6 +1,8 @@
package m1graphs2025;
-import java.util.Arrays;
+import m1graphs2025.pw2.Graph;
+import m1graphs2025.pw2.Node;
+
import java.util.Collections;
import java.util.List;
diff --git a/src/test/java/m1graphs2025/TestGraphPart3.java b/src/test/java/m1graphs2025/TestGraphPart3.java
index 953be9b..35ca94f 100644
--- a/src/test/java/m1graphs2025/TestGraphPart3.java
+++ b/src/test/java/m1graphs2025/TestGraphPart3.java
@@ -1,8 +1,7 @@
package m1graphs2025;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
+import m1graphs2025.pw2.Edge;
+import m1graphs2025.pw2.Graph;
public class TestGraphPart3 {
diff --git a/src/test/java/m1graphs2025/TestGraphPart4.java b/src/test/java/m1graphs2025/TestGraphPart4.java
index 7200435..ae5b315 100644
--- a/src/test/java/m1graphs2025/TestGraphPart4.java
+++ b/src/test/java/m1graphs2025/TestGraphPart4.java
@@ -1,5 +1,8 @@
package m1graphs2025;
+import m1graphs2025.pw2.Node;
+import m1graphs2025.pw2.UndirectedGraph;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
diff --git a/src/test/java/m1graphs2025/TestGraphPart5.java b/src/test/java/m1graphs2025/TestGraphPart5.java
index d0fa911..be9986f 100644
--- a/src/test/java/m1graphs2025/TestGraphPart5.java
+++ b/src/test/java/m1graphs2025/TestGraphPart5.java
@@ -1,6 +1,9 @@
package m1graphs2025;
-import java.util.Arrays;
+import m1graphs2025.pw2.Edge;
+import m1graphs2025.pw2.Node;
+import m1graphs2025.pw2.UndirectedGraph;
+
import java.util.Collections;
import java.util.List;
diff --git a/src/test/java/m1graphs2025/TestGraphPart6.java b/src/test/java/m1graphs2025/TestGraphPart6.java
index 0cbe7d2..a19378f 100644
--- a/src/test/java/m1graphs2025/TestGraphPart6.java
+++ b/src/test/java/m1graphs2025/TestGraphPart6.java
@@ -1,7 +1,8 @@
package m1graphs2025;
+import m1graphs2025.pw2.*;
+
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
public class TestGraphPart6 {
diff --git a/src/test/java/m1graphs2025/TestsGraphPW2.java b/src/test/java/m1graphs2025/TestsGraphPW2.java
index 84ced11..da1d180 100644
--- a/src/test/java/m1graphs2025/TestsGraphPW2.java
+++ b/src/test/java/m1graphs2025/TestsGraphPW2.java
@@ -1,5 +1,10 @@
package m1graphs2025;
+import m1graphs2025.pw2.Edge;
+import m1graphs2025.pw2.Graph;
+import m1graphs2025.pw2.Node;
+import m1graphs2025.pw2.UndirectedGraph;
+
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
--
GitLab
From 663dff4c44fd2e80db20db74e226984c26322fb3 Mon Sep 17 00:00:00 2001
From: adjemaou
Date: Tue, 25 Nov 2025 14:44:54 +0100
Subject: [PATCH 3/5] new algo very good
---
.gitignore | 1 +
input/example_flow.dot | 43 ++-
output/flow1.gv | 29 +-
output/flow2.gv | 31 +-
output/flow3.gv | 31 +-
output/flow4.gv | 31 +-
output/flow5.gv | 31 +-
output/residualGraph1.gv | 33 +-
output/residualGraph2.gv | 39 +-
output/residualGraph3.gv | 42 ++-
output/residualGraph4.gv | 45 ++-
output/residualGraph5.gv | 44 ++-
src/main/java/m1graphs2025/Edge.java | 336 -----------------
.../BFLongestAugmentingPathFinder.java | 121 ++++++
.../java/m1graphs2025/MaximumFlow/Main.java | 4 +-
.../MaximumFlow/MaxBottleneckPathFinder.java | 78 ++++
.../java/m1graphs2025/UndirectedGraph.java | 351 ------------------
src/main/java/m1graphs2025/pw2/Edge.java | 9 +
src/main/java/m1graphs2025/pw2/Graph.java | 28 +-
.../m1graphs2025/pw2/LongestPathFinder.java | 74 ++++
20 files changed, 567 insertions(+), 834 deletions(-)
delete mode 100644 src/main/java/m1graphs2025/Edge.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java
delete mode 100644 src/main/java/m1graphs2025/UndirectedGraph.java
create mode 100644 src/main/java/m1graphs2025/pw2/LongestPathFinder.java
diff --git a/.gitignore b/.gitignore
index 480bdf5..efbcfbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target/
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
+output/*
### IntelliJ IDEA ###
.idea/modules.xml
diff --git a/input/example_flow.dot b/input/example_flow.dot
index 70d7054..9f9e0d6 100644
--- a/input/example_flow.dot
+++ b/input/example_flow.dot
@@ -1,11 +1,32 @@
-digraph flowNetwork {
- rankdir="LR"
- 1-> 2 [label=8, len=8]
- 1-> 3 [label=6, len=6]
- 2-> 4 [label=6, len=6]
- 3-> 4 [label=10, len=10]
- 3-> 5 [label=7, len=7]
- 4-> 5 [label=3, len=3]
- 4-> 6 [label=4, len=4]
- 5-> 6 [label=6, len=6]
- }
\ No newline at end of file
+digraph FlowTest {
+ rankdir="LR";
+ 1 -> 2 [label="15"];
+ 1 -> 3 [label="8"];
+ 1 -> 4 [label="20"];
+
+ 2 -> 5 [label="12"];
+ 2 -> 6 [label="6"];
+
+ 3 -> 2 [label="5"];
+ 3 -> 7 [label="10"];
+
+ 4 -> 7 [label="5"];
+ 4 -> 8 [label="15"];
+
+ 5 -> 9 [label="10"];
+ 5 -> 6 [label="4"];
+
+ 6 -> 9 [label="7"];
+ 6 -> 10 [label="10"];
+
+ 7 -> 5 [label="2"];
+ 7 -> 10 [label="8"];
+ 7 -> 11 [label="5"];
+
+ 8 -> 7 [label="4"];
+ 8 -> 11 [label="12"];
+
+ 9 -> 12 [label="15"];
+ 10 -> 12 [label="10"];
+ 11 -> 12 [label="8"];
+}
diff --git a/output/flow1.gv b/output/flow1.gv
index 97471c0..f4418c4 100644
--- a/output/flow1.gv
+++ b/output/flow1.gv
@@ -1,12 +1,25 @@
digraph flow1 {
rankdir="LR";
label="(1) Flow. Value: 0";
- 1 -> 2 [label="0/8", len=8];
- 1 -> 3 [label="0/6", len=6];
- 2 -> 4 [label="0/6", len=6];
- 3 -> 4 [label="0/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="0/3", len=3];
- 4 -> 6 [label="0/4", len=4];
- 5 -> 6 [label="0/6", len=6];
+ 9 -> 12 [label="0/15", len=15];
+ 10 -> 12 [label="0/10", len=10];
+ 11 -> 12 [label="0/8", len=8];
+ 5 -> 9 [label="0/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="0/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="0/12", len=12];
+ 1 -> 2 [label="0/15", len=15];
+ 1 -> 3 [label="0/8", len=8];
+ 1 -> 4 [label="0/20", len=20];
+ 2 -> 5 [label="0/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="0/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="0/15", len=15];
}
diff --git a/output/flow2.gv b/output/flow2.gv
index 05432c3..80b940f 100644
--- a/output/flow2.gv
+++ b/output/flow2.gv
@@ -1,12 +1,25 @@
digraph flow2 {
rankdir="LR";
- label="(2) Flow. Value: 3";
- 1 -> 2 [label="3/8", len=8];
- 1 -> 3 [label="0/6", len=6];
- 2 -> 4 [label="3/6", len=6];
- 3 -> 4 [label="0/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="0/4", len=4];
- 5 -> 6 [label="3/6", len=6];
+ label="(2) Flow. Value: 10";
+ 9 -> 12 [label="10/15", len=15];
+ 10 -> 12 [label="0/10", len=10];
+ 11 -> 12 [label="0/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="0/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="0/12", len=12];
+ 1 -> 2 [label="10/15", len=15];
+ 1 -> 3 [label="0/8", len=8];
+ 1 -> 4 [label="0/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="0/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="0/15", len=15];
}
diff --git a/output/flow3.gv b/output/flow3.gv
index f2d5e10..561cae4 100644
--- a/output/flow3.gv
+++ b/output/flow3.gv
@@ -1,12 +1,25 @@
digraph flow3 {
rankdir="LR";
- label="(3) Flow. Value: 6";
- 1 -> 2 [label="6/8", len=8];
- 1 -> 3 [label="0/6", len=6];
- 2 -> 4 [label="6/6", len=6];
- 3 -> 4 [label="0/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="3/4", len=4];
- 5 -> 6 [label="3/6", len=6];
+ label="(3) Flow. Value: 18";
+ 9 -> 12 [label="10/15", len=15];
+ 10 -> 12 [label="0/10", len=10];
+ 11 -> 12 [label="8/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="0/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="8/12", len=12];
+ 1 -> 2 [label="10/15", len=15];
+ 1 -> 3 [label="0/8", len=8];
+ 1 -> 4 [label="8/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="0/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="8/15", len=15];
}
diff --git a/output/flow4.gv b/output/flow4.gv
index f1254b3..edb242d 100644
--- a/output/flow4.gv
+++ b/output/flow4.gv
@@ -1,12 +1,25 @@
digraph flow4 {
rankdir="LR";
- label="(4) Flow. Value: 7";
- 1 -> 2 [label="6/8", len=8];
- 1 -> 3 [label="1/6", len=6];
- 2 -> 4 [label="6/6", len=6];
- 3 -> 4 [label="1/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="4/4", len=4];
- 5 -> 6 [label="3/6", len=6];
+ label="(4) Flow. Value: 26";
+ 9 -> 12 [label="10/15", len=15];
+ 10 -> 12 [label="8/10", len=10];
+ 11 -> 12 [label="8/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="8/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="8/12", len=12];
+ 1 -> 2 [label="10/15", len=15];
+ 1 -> 3 [label="8/8", len=8];
+ 1 -> 4 [label="8/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="8/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="8/15", len=15];
}
diff --git a/output/flow5.gv b/output/flow5.gv
index 52e2408..e8a5766 100644
--- a/output/flow5.gv
+++ b/output/flow5.gv
@@ -1,12 +1,25 @@
digraph flow5 {
rankdir="LR";
- label="(5) Flow. Value: 10";
- 1 -> 2 [label="6/8", len=8];
- 1 -> 3 [label="4/6", len=6];
- 2 -> 4 [label="6/6", len=6];
- 3 -> 4 [label="1/10", len=10];
- 3 -> 5 [label="3/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="4/4", len=4];
- 5 -> 6 [label="6/6", len=6];
+ label="(5) Flow. Value: 31";
+ 9 -> 12 [label="15/15", len=15];
+ 10 -> 12 [label="8/10", len=10];
+ 11 -> 12 [label="8/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="5/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="8/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="8/12", len=12];
+ 1 -> 2 [label="15/15", len=15];
+ 1 -> 3 [label="8/8", len=8];
+ 1 -> 4 [label="8/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="5/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="8/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="8/15", len=15];
}
diff --git a/output/residualGraph1.gv b/output/residualGraph1.gv
index fb128fe..6a24e21 100644
--- a/output/residualGraph1.gv
+++ b/output/residualGraph1.gv
@@ -1,14 +1,27 @@
digraph residualGraph1 {
rankdir="LR";
label="(1) residual graph.
- Augmenting path: [1, 2, 4, 5, 6].
- Residual capacity: 3";
- 4 -> 5 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
- 4 -> 6 [label="4", len=4];
- 5 -> 6 [label="6", len=6, penwidth=3, color="blue"];
- 1 -> 2 [label="8", len=8, penwidth=3, color="blue"];
- 1 -> 3 [label="6", len=6];
- 2 -> 4 [label="6", len=6, penwidth=3, color="blue"];
- 3 -> 4 [label="10", len=10];
- 3 -> 5 [label="7", len=7];
+ Augmenting path: [1, 2, 5, 9, 12].
+ Residual capacity: 10";
+ 5 -> 9 [label="10", len=10, penwidth=3, color="blue", fontcolor="red"];
+ 5 -> 6 [label="4", len=4];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="15", len=15];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="10", len=10];
+ 2 -> 5 [label="12", len=12, penwidth=3, color="blue"];
+ 2 -> 6 [label="6", len=6];
+ 1 -> 2 [label="15", len=15, penwidth=3, color="blue"];
+ 1 -> 3 [label="8", len=8];
+ 1 -> 4 [label="20", len=20];
+ 11 -> 12 [label="8", len=8];
+ 10 -> 12 [label="10", len=10];
+ 9 -> 12 [label="15", len=15, penwidth=3, color="blue"];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="12", len=12];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 10 [label="8", len=8];
+ 7 -> 11 [label="5", len=5];
+ 6 -> 9 [label="7", len=7];
+ 6 -> 10 [label="10", len=10];
}
diff --git a/output/residualGraph2.gv b/output/residualGraph2.gv
index 7dc73b4..647b18f 100644
--- a/output/residualGraph2.gv
+++ b/output/residualGraph2.gv
@@ -1,17 +1,30 @@
digraph residualGraph2 {
rankdir="LR";
label="(2) residual graph.
- Augmenting path: [1, 2, 4, 6].
- Residual capacity: 3";
- 6 -> 5 [label="3", len=3];
- 5 -> 4 [label="3", len=3];
- 5 -> 6 [label="3", len=3];
- 4 -> 2 [label="3", len=3];
- 4 -> 6 [label="4", len=4, penwidth=3, color="blue"];
- 3 -> 4 [label="10", len=10];
- 3 -> 5 [label="7", len=7];
- 2 -> 1 [label="3", len=3];
- 2 -> 4 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
- 1 -> 2 [label="5", len=5, penwidth=3, color="blue"];
- 1 -> 3 [label="6", len=6];
+ Augmenting path: [1, 4, 8, 11, 12].
+ Residual capacity: 8";
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="10", len=10];
+ 2 -> 1 [label="10", len=10];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="6", len=6];
+ 1 -> 2 [label="5", len=5];
+ 1 -> 3 [label="8", len=8];
+ 1 -> 4 [label="20", len=20, penwidth=3, color="blue"];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 10 [label="8", len=8];
+ 7 -> 11 [label="5", len=5];
+ 6 -> 9 [label="7", len=7];
+ 6 -> 10 [label="10", len=10];
+ 5 -> 6 [label="4", len=4];
+ 5 -> 2 [label="10", len=10];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="15", len=15, penwidth=3, color="blue"];
+ 11 -> 12 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"];
+ 10 -> 12 [label="10", len=10];
+ 9 -> 12 [label="5", len=5];
+ 9 -> 5 [label="10", len=10];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="12", len=12, penwidth=3, color="blue"];
+ 12 -> 9 [label="10", len=10];
}
diff --git a/output/residualGraph3.gv b/output/residualGraph3.gv
index 4facbb5..f692262 100644
--- a/output/residualGraph3.gv
+++ b/output/residualGraph3.gv
@@ -1,17 +1,33 @@
digraph residualGraph3 {
rankdir="LR";
label="(3) residual graph.
- Augmenting path: [1, 3, 4, 6].
- Residual capacity: 1";
- 5 -> 4 [label="3", len=3];
- 5 -> 6 [label="3", len=3];
- 4 -> 2 [label="6", len=6];
- 4 -> 6 [label="1", len=1, penwidth=3, color="blue", fontcolor="red"];
- 6 -> 4 [label="3", len=3];
- 6 -> 5 [label="3", len=3];
- 1 -> 2 [label="2", len=2];
- 1 -> 3 [label="6", len=6, penwidth=3, color="blue"];
- 3 -> 4 [label="10", len=10, penwidth=3, color="blue"];
- 3 -> 5 [label="7", len=7];
- 2 -> 1 [label="6", len=6];
+ Augmenting path: [1, 3, 7, 10, 12].
+ Residual capacity: 8";
+ 6 -> 9 [label="7", len=7];
+ 6 -> 10 [label="10", len=10];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 10 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"];
+ 7 -> 11 [label="5", len=5];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="4", len=4];
+ 8 -> 4 [label="8", len=8];
+ 9 -> 12 [label="5", len=5];
+ 9 -> 5 [label="10", len=10];
+ 2 -> 1 [label="10", len=10];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="6", len=6];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="10", len=10, penwidth=3, color="blue"];
+ 4 -> 1 [label="8", len=8];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="7", len=7];
+ 5 -> 6 [label="4", len=4];
+ 5 -> 2 [label="10", len=10];
+ 1 -> 2 [label="5", len=5];
+ 1 -> 3 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"];
+ 1 -> 4 [label="12", len=12];
+ 10 -> 12 [label="10", len=10, penwidth=3, color="blue"];
+ 11 -> 8 [label="8", len=8];
+ 12 -> 9 [label="10", len=10];
+ 12 -> 11 [label="8", len=8];
}
diff --git a/output/residualGraph4.gv b/output/residualGraph4.gv
index 15f7006..3c5dfbf 100644
--- a/output/residualGraph4.gv
+++ b/output/residualGraph4.gv
@@ -1,18 +1,35 @@
digraph residualGraph4 {
rankdir="LR";
label="(4) residual graph.
- Augmenting path: [1, 3, 5, 6].
- Residual capacity: 3";
- 1 -> 2 [label="2", len=2];
- 1 -> 3 [label="5", len=5, penwidth=3, color="blue"];
- 4 -> 2 [label="6", len=6];
- 4 -> 3 [label="1", len=1];
- 5 -> 4 [label="3", len=3];
- 5 -> 6 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
- 2 -> 1 [label="6", len=6];
- 3 -> 1 [label="1", len=1];
- 3 -> 4 [label="9", len=9];
- 3 -> 5 [label="7", len=7, penwidth=3, color="blue"];
- 6 -> 4 [label="4", len=4];
- 6 -> 5 [label="3", len=3];
+ Augmenting path: [1, 2, 6, 9, 12].
+ Residual capacity: 5";
+ 9 -> 12 [label="5", len=5, penwidth=3, color="blue", fontcolor="red"];
+ 9 -> 5 [label="10", len=10];
+ 10 -> 12 [label="2", len=2];
+ 10 -> 7 [label="8", len=8];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 11 [label="5", len=5];
+ 7 -> 3 [label="8", len=8];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="4", len=4];
+ 8 -> 4 [label="8", len=8];
+ 11 -> 8 [label="8", len=8];
+ 12 -> 9 [label="10", len=10];
+ 12 -> 10 [label="8", len=8];
+ 12 -> 11 [label="8", len=8];
+ 1 -> 2 [label="5", len=5, penwidth=3, color="blue", fontcolor="red"];
+ 1 -> 4 [label="12", len=12];
+ 2 -> 1 [label="10", len=10];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="6", len=6, penwidth=3, color="blue"];
+ 5 -> 6 [label="4", len=4];
+ 5 -> 2 [label="10", len=10];
+ 6 -> 9 [label="7", len=7, penwidth=3, color="blue"];
+ 6 -> 10 [label="10", len=10];
+ 3 -> 1 [label="8", len=8];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="2", len=2];
+ 4 -> 1 [label="8", len=8];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="7", len=7];
}
diff --git a/output/residualGraph5.gv b/output/residualGraph5.gv
index 485fdda..deba65b 100644
--- a/output/residualGraph5.gv
+++ b/output/residualGraph5.gv
@@ -1,17 +1,35 @@
digraph residualGraph5 {
rankdir="LR";
label="(5) residual graph.
- Augmenting path: none.";
- 2 -> 1 [label="6", len=6];
- 3 -> 1 [label="4", len=4];
- 3 -> 4 [label="9", len=9];
- 3 -> 5 [label="4", len=4];
- 4 -> 2 [label="6", len=6];
- 4 -> 3 [label="1", len=1];
- 5 -> 3 [label="3", len=3];
- 5 -> 4 [label="3", len=3];
- 6 -> 4 [label="4", len=4];
- 6 -> 5 [label="6", len=6];
- 1 -> 2 [label="2", len=2];
- 1 -> 3 [label="2", len=2];
+ Augmenting path: [1, 4, 7, 5, 6, 10, 12].
+ Residual capacity: 2";
+ 7 -> 5 [label="2", len=2, penwidth=3, color="blue", fontcolor="red"];
+ 7 -> 11 [label="5", len=5];
+ 7 -> 3 [label="8", len=8];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="4", len=4];
+ 8 -> 4 [label="8", len=8];
+ 5 -> 6 [label="4", len=4, penwidth=3, color="blue"];
+ 5 -> 2 [label="10", len=10];
+ 6 -> 9 [label="2", len=2];
+ 6 -> 10 [label="10", len=10, penwidth=3, color="blue"];
+ 6 -> 2 [label="5", len=5];
+ 3 -> 1 [label="8", len=8];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="2", len=2];
+ 4 -> 1 [label="8", len=8];
+ 4 -> 7 [label="5", len=5, penwidth=3, color="blue"];
+ 4 -> 8 [label="7", len=7];
+ 1 -> 4 [label="12", len=12, penwidth=3, color="blue"];
+ 2 -> 1 [label="15", len=15];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="1", len=1];
+ 11 -> 8 [label="8", len=8];
+ 12 -> 9 [label="15", len=15];
+ 12 -> 10 [label="8", len=8];
+ 12 -> 11 [label="8", len=8];
+ 9 -> 5 [label="10", len=10];
+ 9 -> 6 [label="5", len=5];
+ 10 -> 12 [label="2", len=2, penwidth=3, color="blue", fontcolor="red"];
+ 10 -> 7 [label="8", len=8];
}
diff --git a/src/main/java/m1graphs2025/Edge.java b/src/main/java/m1graphs2025/Edge.java
deleted file mode 100644
index 4a34376..0000000
--- a/src/main/java/m1graphs2025/Edge.java
+++ /dev/null
@@ -1,336 +0,0 @@
-package m1graphs2025;
-
-import java.util.*;
-
-/**
- * Représente une arête (Edge) dans un graphe.
- * Une arête relie deux nœuds (source et destination) et peut éventuellement
- * porter un poids si le graphe est pondéré.
- *
- * Cette classe implémente l’interface {@link Comparable} afin de permettre
- * le tri des arêtes selon leur poids, puis selon leurs nœuds.
- *
- */
-public class Edge implements Comparable {
-
- // ======================
- // Attributs
- // ======================
-
- /** Nœud source de l’arête */
- private Node from;
-
- /** Nœud destination de l’arête */
- private Node to;
-
- /** Poids de l’arête (null si non pondérée) */
- private Integer weight;
-
- // ======================
- // Constructeurs
- // ======================
-
- /**
- * Crée une arête non pondérée entre deux nœuds.
- *
- * @param from Le nœud source.
- * @param to Le nœud destination.
- */
- public Edge(Node from, Node to) {
- this.from = from;
- this.to = to;
- this.weight = null;
- }
-
- /**
- * Crée une arête vide (sans nœuds ni poids).
- */
- public Edge() {
- this.from = null;
- this.to = null;
- this.weight = null;
- }
-
- /**
- * Crée une arête pondérée entre deux nœuds.
- *
- * @param from Le nœud source.
- * @param to Le nœud destination.
- * @param weight Le poids de l’arête.
- */
- public Edge(Node from, Node to, Integer weight) {
- this.from = from;
- this.to = to;
- this.weight = weight;
- }
-
- /**
- * Crée une arête non pondérée à partir des identifiants des nœuds dans un
- * graphe donné.
- *
- * @param fromId L’identifiant du nœud source.
- * @param toId L’identifiant du nœud destination.
- * @param g Le graphe auquel appartiennent les nœuds.
- * @throws IllegalArgumentException si le graphe est null.
- */
- public Edge(int fromId, int toId, Graph g) {
- if (g == null) {
- throw new IllegalArgumentException("Le graphe ne peut pas être null.");
- }
- Node from = g.getNode(fromId);
- Node to = g.getNode(toId);
-
- if (from == null) {
- from = new Node(fromId, g);
- g.addNode(from);
- }
- if (to == null) {
- to = new Node(toId, g);
- g.addNode(to);
- }
-
- this.from = from;
- this.to = to;
- this.weight = 0; // ou une valeur par défaut
- }
-
- /**
- * Crée une arête pondérée à partir des identifiants des nœuds dans un graphe
- * donné.
- *
- * @param fromId L’identifiant du nœud source.
- * @param toId L’identifiant du nœud destination.
- * @param weight Le poids de l’arête.
- * @param g Le graphe auquel appartiennent les nœuds.
- * @throws IllegalArgumentException si le graphe est null.
- */
- public Edge(int fromId, int toId, int weight, Graph g) {
- if (g == null) {
- throw new IllegalArgumentException("Le graphe ne peut pas être null.");
- }
- Node from = g.getNode(fromId);
- Node to = g.getNode(toId);
-
- if (from == null) {
- from = new Node(fromId, g);
- g.addNode(from);
- }
- if (to == null) {
- to = new Node(toId, g);
- g.addNode(to);
- }
-
- this.from = from;
- this.to = to;
- this.weight = weight;
- }
-
- // ======================
- // Getters / Setters
- // ======================
-
- /**
- * Retourne le nœud source de l’arête.
- *
- * @return Le nœud source.
- */
- public Node getNodeFrom() {
- return from;
- }
-
- /**
- * Retourne le nœud destination de l’arête.
- *
- * @return Le nœud destination.
- */
- public Node getNodeTo() {
- return to;
- }
-
- /**
- * Retourne le poids de l’arête.
- *
- * @return Le poids de l’arête, ou null si elle n’est pas pondérée.
- */
- public Integer getWeight() {
- return weight;
- }
-
- /**
- * Définit le nœud source de l’arête.
- *
- * @param from Le nouveau nœud source.
- */
- public void setNodeFrom(Node from) {
- this.from = from;
- }
-
- /**
- * Définit le nœud destination de l’arête.
- *
- * @param to Le nouveau nœud destination.
- */
- public void setNodeTo(Node to) {
- this.to = to;
- }
-
- /**
- * Définit le poids de l’arête.
- *
- * @param weight Le nouveau poids.
- */
- public void setWeight(Integer weight) {
- this.weight = weight;
- }
-
- // ======================
- // Méthodes fonctionnelles
- // ======================
-
- /**
- * Retourne le nœud source de l’arête.
- *
- * @return Le nœud source.
- */
- public Node from() {
- return from;
- }
-
- /**
- * Retourne le nœud destination de l’arête.
- *
- * @return Le nœud destination.
- */
- public Node to() {
- return to;
- }
-
- /**
- * Retourne une arête symétrique (inversée) avec la même pondération.
- *
- * @return Une nouvelle arête inversée ou null si les nœuds n’appartiennent pas
- * au même graphe.
- */
- public Edge getSymmetric() {
- Graph g1 = (from != null) ? from.getGraph() : null;
- Graph g2 = (to != null) ? to.getGraph() : null;
-
- if (Objects.equals(g1, g2)) {
- Node symFrom = new Node(to.getId(), g1, to.getName());
- Node symTo = new Node(from.getId(), g1, from.getName());
- return new Edge(symFrom, symTo, this.weight);
- }
- return null;
- }
-
- /**
- * Vérifie si l’arête est une boucle (relie un nœud à lui-même).
- *
- * @return true si l’arête est une boucle, false sinon.
- */
- public boolean isSelfLoop() {
- return from != null && to != null && from.getId() == to.getId();
- }
-
- /**
- * Vérifie s’il existe une autre arête reliant les mêmes nœuds
- * mais avec un poids différent (arête multiple).
- *
- * @return true si une arête multiple existe, false sinon.
- */
- public boolean isMultiEdge() {
- if (from == null || from.getGraph() == null)
- return false;
-
- Graph graph = from.getGraph();
- List edges = graph.adjEdList.get(from);
-
- if (edges == null)
- return false;
-
- for (Edge edge : edges) {
- boolean sameNodes = edge.from.getId() == this.from.getId()
- && edge.to.getId() == this.to.getId();
- boolean differentWeight = !Objects.equals(edge.getWeight(), this.weight);
-
- if (sameNodes && differentWeight) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Indique si l’arête est pondérée.
- *
- * @return true si l’arête est pondérée, false sinon.
- */
- public boolean isWeighted() {
- return this.weight != null;
- }
-
- // ======================
- // Méthodes redéfinies
- // ======================
-
- /**
- * Compare deux arêtes selon leur poids, puis les identifiants des nœuds.
- * Gère correctement les arêtes non pondérées (poids null).
- *
- * @param other L’autre arête à comparer.
- * @return Un entier négatif, zéro ou un entier positif selon l’ordre.
- */
- @Override
- public int compareTo(Edge other) {
- int w1 = (this.weight == null) ? 0 : this.weight;
- int w2 = (other.weight == null) ? 0 : other.weight;
-
- int cmp = Integer.compare(w1, w2);
- if (cmp != 0)
- return cmp;
-
- cmp = Integer.compare(this.from.getId(), other.from.getId());
- if (cmp != 0)
- return cmp;
-
- return Integer.compare(this.to.getId(), other.to.getId());
- }
-
- /**
- * Vérifie l’égalité entre deux arêtes :
- * même nœud source, même destination, même poids.
- *
- * @param o L’objet à comparer.
- * @return true si les arêtes sont égales, false sinon.
- */
- @Override
- public boolean equals(Object o) {
- if (this == o)
- return true;
- if (!(o instanceof Edge edge))
- return false;
- return Objects.equals(from, edge.from)
- && Objects.equals(to, edge.to)
- && Objects.equals(weight, edge.weight);
- }
-
- /**
- * Retourne le code de hachage de l’arête.
- *
- * @return Le code de hachage.
- */
- @Override
- public int hashCode() {
- return Objects.hash(from, to, weight);
- }
-
- /**
- * Retourne une représentation textuelle de l’arête.
- *
- * @return Une chaîne de caractères représentant l’arête.
- */
- @Override
- public String toString() {
- return "Edge(" + from.getId() + " -> " + to.getId() + ", w=" + weight + ")";
- }
-}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java
new file mode 100644
index 0000000..44a61d6
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java
@@ -0,0 +1,121 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * Trouve un chemin augmentant en transformant le problème du plus long chemin
+ * en un plus court chemin en utilisant des poids négatifs (coût = -1 par arête)
+ * et l'algorithme de Bellman-Ford (limité à n-1 relaxations, donc simple path).
+ *
+ * Attention: si le graphe contient des cycles, Bellman-Ford peut détecter des
+ * cycles négatifs — nous ignorons la détection et utilisons les distances après
+ * n-1 itérations (correspondant aux plus courts chemins simples).
+ */
+public class BFLongestAugmentingPathFinder implements AugmentingPathFinder {
+
+ @Override
+ public List findAugmentingPath(ResidualGraph a3ResidualGraph, int sourceId, int sinkId) {
+
+
+ // Approach:
+ // 1) Run Bellman-Ford with cost = -1 per (positive-capacity) residual edge for exactly n-1 relaxations.
+ // This yields distances dist[] where dist[v] equals -L for some simple path length L (if reachable).
+ // 2) Build the equality graph H containing edges (u->v) that satisfy dist[v] == dist[u] - 1.
+ // Any path in H from source to sink corresponds to a path achieving the target distance.
+ // 3) Find a simple path in H from source to sink using BFS over partial paths (iterative,
+ // avoids recursion and prefers shorter expansions). This guarantees no infinite loops.
+
+
+
+ if (a3ResidualGraph == null) return null;
+ Graph g = a3ResidualGraph.getGraph();
+ if (g == null) return null;
+
+ // Topological DP approach (works only if residual is a DAG)
+ List nodes = g.getAllNodes();
+ int n = nodes.size();
+ if (n == 0) return null;
+
+ Map idToIndex = new HashMap<>();
+ for (int i = 0; i < nodes.size(); i++) idToIndex.put(nodes.get(i).getId(), i);
+ if (!idToIndex.containsKey(sourceId) || !idToIndex.containsKey(sinkId)) return null;
+ int src = idToIndex.get(sourceId);
+ int tgt = idToIndex.get(sinkId);
+
+ // Build in-degree considering only positive residual edges
+ int[] indeg = new int[n];
+ for (int i = 0; i < n; i++) indeg[i] = 0;
+ List edges = new ArrayList<>();
+ for (Node u : nodes) {
+ for (Edge e : u.getOutEdges()) {
+ Integer w = e.getWeight();
+ if (w != null && w > 0) {
+ int vi = idToIndex.get(e.getNodeTo().getId());
+ indeg[vi]++;
+ edges.add(e);
+ }
+ }
+ }
+
+ Deque q = new ArrayDeque<>();
+ for (int i = 0; i < n; i++) if (indeg[i] == 0) q.addLast(i);
+ List topo = new ArrayList<>();
+ while (!q.isEmpty()) {
+ int u = q.removeFirst();
+ topo.add(u);
+ Node nu = nodes.get(u);
+ for (Edge e : nu.getOutEdges()) {
+ Integer w = e.getWeight();
+ if (w == null || w <= 0) continue;
+ int vi = idToIndex.get(e.getNodeTo().getId());
+ indeg[vi]--;
+ if (indeg[vi] == 0) q.addLast(vi);
+ }
+ }
+
+ if (topo.size() != n) {
+ // residual graph contains cycles — cannot apply topological DP safely
+ // fallback to BFS to guarantee a simple augmenting path
+ AugmentingPathFinder bfs = new BFSPathFinder();
+ return bfs.findAugmentingPath(a3ResidualGraph, sourceId, sinkId);
+ }
+
+ // DP: longest path length from source to each node (in number of edges)
+ final int NEG = Integer.MIN_VALUE / 4;
+ int[] dp = new int[n];
+ int[] pred = new int[n];
+ Arrays.fill(dp, NEG);
+ Arrays.fill(pred, -1);
+ dp[src] = 0;
+
+ for (int u : topo) {
+ if (dp[u] == NEG) continue; // unreachable from source
+ Node nu = nodes.get(u);
+ for (Edge e : nu.getOutEdges()) {
+ Integer w = e.getWeight();
+ if (w == null || w <= 0) continue;
+ int v = idToIndex.get(e.getNodeTo().getId());
+ if (dp[u] + 1 > dp[v]) {
+ dp[v] = dp[u] + 1;
+ pred[v] = u;
+ }
+ }
+ }
+
+ if (dp[tgt] == NEG) return null;
+
+ // reconstruct path from pred[]
+ LinkedList path = new LinkedList<>();
+ int cur = tgt;
+ int steps = 0;
+ while (cur != -1 && steps <= n) {
+ path.addFirst(nodes.get(cur));
+ if (cur == src) break;
+ cur = pred[cur];
+ steps++;
+ }
+ if (path.isEmpty() || path.getFirst().getId() != sourceId) return null;
+ return path;
+ }
+}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/Main.java b/src/main/java/m1graphs2025/MaximumFlow/Main.java
index 29d96b5..bec8dc2 100644
--- a/src/main/java/m1graphs2025/MaximumFlow/Main.java
+++ b/src/main/java/m1graphs2025/MaximumFlow/Main.java
@@ -42,9 +42,9 @@ public class Main {
// Choix de l'algorithme de recherche de chemin augmentant
//AugmentingPathFinder finder = new BFSPathFinder(); // Edmonds-Karp (recommandé)
- AugmentingPathFinder finder = new DFSPathFinder(); // Ford-Fulkerson classique
+ //AugmentingPathFinder finder = new DFSPathFinder(); // Ford-Fulkerson classique
// AugmentingPathFinder finder = new MaxCapacityPathFinder(); // plus grande capacité
-
+ AugmentingPathFinder finder = new MaxBottleneckPathFinder(); // plus long chemin (Bellman-Ford)
// Lancement de l'algorithme
FordFulkerson algo = new FordFulkerson(network, finder, exporter);
int maxFlow = algo.run(outFolder);
diff --git a/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java
new file mode 100644
index 0000000..2d74ec7
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java
@@ -0,0 +1,78 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * MaxBottleneckPathFinder :
+ * Trouve un chemin augmentant en maximisant la capacité résiduelle minimale
+ * sur le chemin (Maximum Bottleneck Path).
+ *
+ * Cet algo est le plus efficace pour réduire le nombre d'étapes
+ * lors du calcul du flot maximum.
+ */
+public class MaxBottleneckPathFinder implements AugmentingPathFinder {
+
+ @Override
+ public List findAugmentingPath(ResidualGraph residual, int sourceId, int sinkId) {
+ Graph g = residual.getGraph();
+
+ Node source = g.getNode(sourceId);
+ Node sink = g.getNode(sinkId);
+ if (source == null || sink == null) return null;
+
+ // capacité max rencontrée pour chaque nœud
+ Map best = new HashMap<>();
+ // parent pour reconstruire le chemin
+ Map parent = new HashMap<>();
+
+ for (Node n : g.getAllNodes()) {
+ best.put(n.getId(), 0);
+ }
+ best.put(sourceId, Integer.MAX_VALUE);
+
+ // max-heap : on explore toujours le sommet ayant la meilleure capacité
+ PriorityQueue pq = new PriorityQueue<>(
+ (a, b) -> Integer.compare(best.get(b.getId()), best.get(a.getId()))
+ );
+ pq.add(source);
+
+ while (!pq.isEmpty()) {
+ Node u = pq.poll();
+ int ucap = best.get(u.getId());
+
+ if (u.getId() == sinkId) break;
+
+ for (Edge e : g.getOutEdges(u)) {
+ if (e.getResidualCapacity() > 0) {
+ Node v = e.getNodeTo();
+ int bottleneck = Math.min(ucap, e.getResidualCapacity());
+
+ if (bottleneck > best.get(v.getId())) {
+ best.put(v.getId(), bottleneck);
+ parent.put(v.getId(), u.getId());
+ pq.add(v);
+ }
+ }
+ }
+ }
+
+ if (!parent.containsKey(sinkId)) return null;
+
+ return reconstructPath(g, parent, sourceId, sinkId);
+ }
+
+ private List reconstructPath(Graph g, Map parent, int s, int t) {
+ List path = new ArrayList<>();
+ int cur = t;
+
+ while (cur != s) {
+ path.add(g.getNode(cur));
+ cur = parent.get(cur);
+ }
+ path.add(g.getNode(s));
+
+ Collections.reverse(path);
+ return path;
+ }
+}
diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java
deleted file mode 100644
index 5746bed..0000000
--- a/src/main/java/m1graphs2025/UndirectedGraph.java
+++ /dev/null
@@ -1,351 +0,0 @@
-package m1graphs2025;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.*;
-
-/**
- * Représente un graphe non orienté en héritant de Graph.
- * Chaque arête (u, v) est automatiquement dupliquée en (v, u).
- */
-public class UndirectedGraph extends Graph {
-
- // ======================
- // CONSTRUCTEURS
- // ======================
- public UndirectedGraph(Map> adjEdList) {
- super(adjEdList);
- }
-
- public UndirectedGraph() {
- super(new HashMap<>()); // graphe vide
- }
-
- public UndirectedGraph(int... successorArray) {
- int taille = successorArray.length;
- Map> adjEdList = new HashMap<>();
- int i = 0;
- int id = 1;
-
- // D'abord, on crée tous les nœuds pour les réutiliser (sinon on crée des
- // doublons)
- Map nodes = new HashMap<>();
-
- // Première passe : créer les nœuds
- int tempId = 1;
- while (i < taille) {
- Node node = new Node(tempId, this);
- nodes.put(tempId, node);
-
- while (i < taille && successorArray[i] != 0) {
- int succ = successorArray[i];
- if (!nodes.containsKey(succ)) {
- nodes.put(succ, new Node(succ, this));
- }
- i++;
- }
-
- i++; // sauter le 0
- tempId++;
- }
-
- // Deuxième passe : créer les arêtes bidirectionnelles
- i = 0;
- id = 1;
- while (i < taille) {
- Node node = nodes.get(id);
- List list = adjEdList.computeIfAbsent(node, k -> new ArrayList<>());
-
- while (i < taille && successorArray[i] != 0) {
- Node nodeTo = nodes.get(successorArray[i]);
- Edge edge = new Edge(node, nodeTo);
- list.add(edge);
-
- // Ajout de l’arête inverse (bidirectionnelle)
- List reverseList = adjEdList.computeIfAbsent(nodeTo, k -> new ArrayList<>());
- Edge reverseEdge = new Edge(nodeTo, node);
- reverseList.add(reverseEdge);
-
- i++;
- }
-
- i++; // sauter le 0
- id++;
- }
-
- this.adjEdList = adjEdList;
- }
-
- @Override
- public boolean addNode(Node n) {
- return super.addNode(n);
- }
-
- /**
- * Symétrise toutes les arêtes existantes dans le graphe
- * (utile après construction par tableau de successeurs).
- */
- private void symmetrize() {
- List edges = getAllEdges();
- for (Edge e : edges) {
- Node u = e.getNodeFrom();
- Node v = e.getNodeTo();
- if (!existsEdge(v, u)) {
- if (e.isWeighted())
- addEdge(v, u, e.getWeight());
- else
- addEdge(v, u);
- }
- }
- }
-
- // ======================
- // API DES ARÊTES (Override)
- // ======================
-
- @Override
- public void addEdge(Node from, Node to) {
- super.addEdge(from, to); // u -> v
- super.addEdge(to, from); // v -> u pour non dirigé
- }
-
- @Override
- public void addEdge(Node from, Node to, int weight) {
- super.addEdge(from, to, weight);
- super.addEdge(to, from, weight);
- }
-
- @Override
- public void addEdge(int fromId, int toId) {
- Node from = getNode(fromId);
- Node to = getNode(toId);
- if (from == null)
- from = new Node(fromId, this);
- if (to == null)
- to = new Node(toId, this);
- addEdge(from, to);
- }
-
- @Override
- public void addEdge(int fromId, int toId, int weight) {
- Node from = getNode(fromId);
- Node to = getNode(toId);
- if (from == null)
- from = new Node(fromId, this);
- if (to == null)
- to = new Node(toId, this);
- addEdge(from, to, weight);
- }
-
- @Override
- public boolean removeEdge(Node from, Node to) {
- boolean r1 = super.removeEdge(from, to);
- boolean r2 = super.removeEdge(to, from);
- return r1 || r2;
- }
-
- @Override
- public boolean removeEdge(int fromId, int toId) {
- boolean r1 = super.removeEdge(fromId, toId);
- boolean r2 = super.removeEdge(toId, fromId);
- return r1 || r2;
- }
-
- @Override
- public boolean removeEdge(Edge e) {
- boolean r1 = super.removeEdge(e);
- Edge sym = e.getSymmetric();
- boolean r2 = (sym != null) && super.removeEdge(sym);
- return r1 || r2;
- }
-
- @Override
- public boolean existsEdge(Node u, Node v) {
- return super.existsEdge(u, v) || super.existsEdge(v, u);
- }
-
- @Override
- public boolean existsEdge(int uId, int vId) {
- return existsEdge(getNode(uId), getNode(vId));
- }
-
- @Override
- public boolean isMultiEdge(Node u, Node v) {
- // Vérifie dans les deux directions
- return super.isMultiEdge(u, v) || super.isMultiEdge(v, u);
- }
-
- @Override
- public UndirectedGraph copy() {
- Map> copy = new HashMap<>();
- for (Node u : adjEdList.keySet()) {
- List list = new ArrayList<>();
- for (Edge e : adjEdList.get(u))
- list.add(new Edge(e.getNodeFrom(), e.getNodeTo(), e.getWeight()));
- copy.put(u, list);
- }
- return new UndirectedGraph(copy);
- }
-
- @Override
- public UndirectedGraph getTransitiveClosure() {
- UndirectedGraph closure = this.copy();
- List nodes = closure.getAllNodes();
- int n = nodes.size();
- for (Node k : nodes) {
- for (Node i : nodes) {
- for (Node j : nodes) {
- if (closure.existsEdge(i, k) && closure.existsEdge(k, j)) {
- if (!closure.existsEdge(i, j))
- closure.addEdge(i, j);
- }
- }
- }
- }
- return closure;
- }
-
- /**
- * Lit un fichier DOT et retourne le graphe non orienté associé.
- * Le format attend est le suivant :
- * - Ligne contenant un seul nombre : nœud isolé
- * - Ligne contenant trois nombres : arête (u, v, poids)
- * Les arêtes sont automatiquement dupliquées pour satisfaire la propriété
- * de graphe non orienté.
- * Si une erreur survient lors de la lecture du fichier, une exception est
- * levée.
- *
- * @param filename Le nom du fichier à lire.
- * @return Le graphe non orienté associé au fichier.
- * @throws RuntimeException si une erreur survient lors de la lecture du
- * fichier.
- */
-
- public static UndirectedGraph fromDotFile(String filename) {
- Map> adjEdList = new HashMap<>();
- UndirectedGraph g = new UndirectedGraph(adjEdList);
-
- try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
- String line;
-
- while ((line = reader.readLine()) != null) {
- line = line.trim();
- if (line.isEmpty())
- continue; // skip empty lines
-
- // --- Case 1: isolated node
- if (line.matches("^\\d+$")) {
- int id = Integer.parseInt(line);
- Node node = g.getOrCreateNode(id);
- adjEdList.putIfAbsent(node, new ArrayList<>());
- continue;
- }
-
- // --- Case 2: edge line
- String[] tokens = line.replaceAll("[^0-9]+", " ").trim().split("\\s+");
- if (tokens.length < 2)
- continue; // invalid line → skip
-
- int fromId = Integer.parseInt(tokens[0]);
- int toId = Integer.parseInt(tokens[1]);
-
- Node from = g.getOrCreateNode(fromId);
- Node to = g.getOrCreateNode(toId);
-
- Edge edge;
- if (tokens.length >= 3) {
- int weight = Integer.parseInt(tokens[2]);
- edge = new Edge(from, to, weight);
- } else {
- edge = new Edge(from, to);
- }
-
- // Add to adjacency list
- adjEdList.computeIfAbsent(from, k -> new ArrayList<>()).add(edge);
- adjEdList.putIfAbsent(to, new ArrayList<>()); // ensure target node exists
- }
-
- } catch (IOException e) {
- throw new RuntimeException("Error reading file: " + filename, e);
- }
-
- return g;
- }
-
- /**
- * Generates a DOT-format string representation of this graph.
- *
- * Example output for an undirected graph:
- *
- *
- * graph G {
- * rankdir=LR;
- * 1 -- 2 [label="5", len="5"];
- * 3 -- 4 [label="3", len="3"];
- * 4;
- * }
- *
- *
- *
- * @return the DOT-format representation of the graph
- */
- public String toDotString() {
- StringBuilder sb = new StringBuilder();
- sb.append("graph G {\n");
- sb.append("\trankdir=LR\n");
-
- // Liste triée des arêtes
- List edges = getAllEdges();
- edges.sort(Comparator.comparingInt((Edge e) -> e.getNodeFrom().getId())
- .thenComparingInt(e -> e.getNodeTo().getId()));
-
- // Affichage des arêtes
- for (Edge e : edges) {
- sb.append("\t")
- .append(e.getNodeFrom().getId())
- .append(" -- ")
- .append(e.getNodeTo().getId());
- if (e.isWeighted()) {
- sb.append(" [label=").append(e.getWeight())
- .append(", len=").append(e.getWeight()).append("]");
- }
- sb.append(";\n");
- }
-
- // Affichage des nœuds isolés (ceux qui n’ont aucune arête)
- Set nodesWithEdges = new HashSet<>();
- for (Edge e : edges) {
- nodesWithEdges.add(e.getNodeFrom());
- nodesWithEdges.add(e.getNodeTo());
- }
-
- for (Node node : adjEdList.keySet()) {
- if (!nodesWithEdges.contains(node)) {
- sb.append("\t").append(node.getId()).append(";\n");
- }
- }
-
- sb.append("}\n");
- return sb.toString();
- }
-
- public void toDotFile(String filename) {
- super.toDotFile(filename);
- }
-
- public void toDotFile(String filename, String ext) {
- super.toDotFile(filename, ".gv");
- }
-
- // ======================
- // Utilitaires
- // ======================
-
- public boolean isDirected() {
- return false;
- }
-}
diff --git a/src/main/java/m1graphs2025/pw2/Edge.java b/src/main/java/m1graphs2025/pw2/Edge.java
index b5c5d18..df9f2ac 100644
--- a/src/main/java/m1graphs2025/pw2/Edge.java
+++ b/src/main/java/m1graphs2025/pw2/Edge.java
@@ -452,4 +452,13 @@ public class Edge implements Comparable {
return from.getId() + " -> " + to.getId();
}
+ // =================================================================================================================
+ // ============================================Flow Max METH====================================================
+
+ public int getResidualCapacity() {
+ if (this.weight == null) {
+ return Integer.MAX_VALUE; // Capacité infinie pour les arêtes non pondérées
+ }
+ return this.weight;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/pw2/Graph.java b/src/main/java/m1graphs2025/pw2/Graph.java
index f85c9be..5bec828 100644
--- a/src/main/java/m1graphs2025/pw2/Graph.java
+++ b/src/main/java/m1graphs2025/pw2/Graph.java
@@ -1510,11 +1510,6 @@ public class Graph {
nodeVisit.get(u).setFinishTime(time.get());
visited.add(u);
}
- nodeVisit.get(u).setColour(NodeColour.BLACK);
- time.incrementAndGet();
- nodeVisit.get(u).setFinishTime(time.get());
- visited.add(u);
-}
// =================================================================================================================
// ===============================================DOT I/O===========================================================
@@ -1692,31 +1687,10 @@ public class Graph {
*
* @return liste des nœuds triés par ID
*/
- private List sortNodes() {
+ public List sortNodes() {
return adjEdList.keySet().stream()
.sorted(Comparator.comparingInt(Node::getId))
.toList();
}
}
-
-/**
- * Réinitialise les informations de visite dans une carte donnée pour chaque nœud du graphe.
- *
- * @param map La carte à réinitialiser, où chaque nœud sera associé à un nouvel objet {@link NodeVisitInfo}.
- */
-private void initVisitInfo(Map map) {
- for (Node n : adjEdList.keySet())
- map.put(n, new NodeVisitInfo());
-}
-
-/**
- * Trie les nœuds du graphe par leur identifiant dans l'ordre croissant.
- *
- * @return Une liste triée des nœuds du graphe.
- */
-private List sortNodes() {
- List nodes = new ArrayList<>(adjEdList.keySet());
- nodes.sort(Comparator.comparingInt(Node::getId));
- return nodes;
-}}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/pw2/LongestPathFinder.java b/src/main/java/m1graphs2025/pw2/LongestPathFinder.java
new file mode 100644
index 0000000..6a25590
--- /dev/null
+++ b/src/main/java/m1graphs2025/pw2/LongestPathFinder.java
@@ -0,0 +1,74 @@
+package m1graphs2025.pw2;
+
+import java.util.*;
+
+/**
+ * Utility pour rechercher un chemin le plus long dans un graphe orienté acyclique (DAG).
+ * - Si le graphe contient un cycle, la méthode retourne null.
+ * - Les arêtes pondérées utilisent `Edge.getWeight()` si non-null, sinon on considère un poids = 1.
+ */
+public class LongestPathFinder {
+
+ public static List findLongestPath(Graph g, int sourceId, int sinkId) {
+ if (g == null) return null;
+ Node source = g.getNode(sourceId);
+ Node sink = g.getNode(sinkId);
+ if (source == null || sink == null) return null;
+
+ // Kahn pour ordre topologique
+ Map inDeg = new HashMap<>();
+ List all = g.getAllNodes();
+ for (Node n : all) inDeg.put(n, g.inDegree(n));
+
+ Deque q = new ArrayDeque<>();
+ for (Map.Entry e : inDeg.entrySet()) if (e.getValue() == 0) q.add(e.getKey());
+
+ List topo = new ArrayList<>();
+ while (!q.isEmpty()) {
+ Node u = q.removeFirst();
+ topo.add(u);
+ for (Edge out : u.getOutEdges()) {
+ Node v = out.getNodeTo();
+ inDeg.put(v, inDeg.get(v) - 1);
+ if (inDeg.get(v) == 0) q.addLast(v);
+ }
+ }
+
+ if (topo.size() != all.size()) {
+ // graphe non-DAG
+ return null;
+ }
+
+ final int NEG = Integer.MIN_VALUE / 4;
+ Map dist = new HashMap<>();
+ Map pred = new HashMap<>();
+ for (Node n : all) dist.put(n, NEG);
+ dist.put(source, 0);
+
+ for (Node u : topo) {
+ int du = dist.getOrDefault(u, NEG);
+ if (du == NEG) continue;
+ for (Edge e : u.getOutEdges()) {
+ Node v = e.getNodeTo();
+ int w = (e.getWeight() != null) ? e.getWeight() : 1;
+ if (du + w > dist.getOrDefault(v, NEG)) {
+ dist.put(v, du + w);
+ pred.put(v, u);
+ }
+ }
+ }
+
+ if (dist.getOrDefault(sink, NEG) == NEG) return null;
+
+ LinkedList path = new LinkedList<>();
+ Node cur = sink;
+ while (cur != null) {
+ path.addFirst(cur);
+ if (cur.equals(source)) break;
+ cur = pred.get(cur);
+ }
+
+ if (path.isEmpty() || !path.getFirst().equals(source)) return null;
+ return path;
+ }
+}
--
GitLab
From d1b8d7bc0ffcd996fd6ffc3c20761320fe10db59 Mon Sep 17 00:00:00 2001
From: adjemaou
Date: Tue, 25 Nov 2025 14:44:54 +0100
Subject: [PATCH 4/5] ajout menu +cleaning output
---
.gitignore | 1 +
input/example_flow.dot | 43 ++-
output/flow1.gv | 29 +-
output/flow2.gv | 31 +-
output/flow3.gv | 31 +-
output/flow4.gv | 31 +-
output/flow5.gv | 31 +-
output/residualGraph1.gv | 33 +-
output/residualGraph2.gv | 39 +-
output/residualGraph3.gv | 42 ++-
output/residualGraph4.gv | 45 ++-
output/residualGraph5.gv | 44 ++-
src/main/java/m1graphs2025/Edge.java | 336 -----------------
.../BFLongestAugmentingPathFinder.java | 121 ++++++
.../java/m1graphs2025/MaximumFlow/Main.java | 58 ++-
.../MaximumFlow/MaxBottleneckPathFinder.java | 78 ++++
.../java/m1graphs2025/UndirectedGraph.java | 351 ------------------
src/main/java/m1graphs2025/pw2/Edge.java | 9 +
src/main/java/m1graphs2025/pw2/Graph.java | 28 +-
.../m1graphs2025/pw2/LongestPathFinder.java | 74 ++++
20 files changed, 619 insertions(+), 836 deletions(-)
delete mode 100644 src/main/java/m1graphs2025/Edge.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java
delete mode 100644 src/main/java/m1graphs2025/UndirectedGraph.java
create mode 100644 src/main/java/m1graphs2025/pw2/LongestPathFinder.java
diff --git a/.gitignore b/.gitignore
index 480bdf5..efbcfbb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,6 +3,7 @@ target/
!**/src/main/**/target/
!**/src/test/**/target/
.kotlin
+output/*
### IntelliJ IDEA ###
.idea/modules.xml
diff --git a/input/example_flow.dot b/input/example_flow.dot
index 70d7054..9f9e0d6 100644
--- a/input/example_flow.dot
+++ b/input/example_flow.dot
@@ -1,11 +1,32 @@
-digraph flowNetwork {
- rankdir="LR"
- 1-> 2 [label=8, len=8]
- 1-> 3 [label=6, len=6]
- 2-> 4 [label=6, len=6]
- 3-> 4 [label=10, len=10]
- 3-> 5 [label=7, len=7]
- 4-> 5 [label=3, len=3]
- 4-> 6 [label=4, len=4]
- 5-> 6 [label=6, len=6]
- }
\ No newline at end of file
+digraph FlowTest {
+ rankdir="LR";
+ 1 -> 2 [label="15"];
+ 1 -> 3 [label="8"];
+ 1 -> 4 [label="20"];
+
+ 2 -> 5 [label="12"];
+ 2 -> 6 [label="6"];
+
+ 3 -> 2 [label="5"];
+ 3 -> 7 [label="10"];
+
+ 4 -> 7 [label="5"];
+ 4 -> 8 [label="15"];
+
+ 5 -> 9 [label="10"];
+ 5 -> 6 [label="4"];
+
+ 6 -> 9 [label="7"];
+ 6 -> 10 [label="10"];
+
+ 7 -> 5 [label="2"];
+ 7 -> 10 [label="8"];
+ 7 -> 11 [label="5"];
+
+ 8 -> 7 [label="4"];
+ 8 -> 11 [label="12"];
+
+ 9 -> 12 [label="15"];
+ 10 -> 12 [label="10"];
+ 11 -> 12 [label="8"];
+}
diff --git a/output/flow1.gv b/output/flow1.gv
index 97471c0..f4418c4 100644
--- a/output/flow1.gv
+++ b/output/flow1.gv
@@ -1,12 +1,25 @@
digraph flow1 {
rankdir="LR";
label="(1) Flow. Value: 0";
- 1 -> 2 [label="0/8", len=8];
- 1 -> 3 [label="0/6", len=6];
- 2 -> 4 [label="0/6", len=6];
- 3 -> 4 [label="0/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="0/3", len=3];
- 4 -> 6 [label="0/4", len=4];
- 5 -> 6 [label="0/6", len=6];
+ 9 -> 12 [label="0/15", len=15];
+ 10 -> 12 [label="0/10", len=10];
+ 11 -> 12 [label="0/8", len=8];
+ 5 -> 9 [label="0/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="0/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="0/12", len=12];
+ 1 -> 2 [label="0/15", len=15];
+ 1 -> 3 [label="0/8", len=8];
+ 1 -> 4 [label="0/20", len=20];
+ 2 -> 5 [label="0/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="0/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="0/15", len=15];
}
diff --git a/output/flow2.gv b/output/flow2.gv
index 05432c3..80b940f 100644
--- a/output/flow2.gv
+++ b/output/flow2.gv
@@ -1,12 +1,25 @@
digraph flow2 {
rankdir="LR";
- label="(2) Flow. Value: 3";
- 1 -> 2 [label="3/8", len=8];
- 1 -> 3 [label="0/6", len=6];
- 2 -> 4 [label="3/6", len=6];
- 3 -> 4 [label="0/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="0/4", len=4];
- 5 -> 6 [label="3/6", len=6];
+ label="(2) Flow. Value: 10";
+ 9 -> 12 [label="10/15", len=15];
+ 10 -> 12 [label="0/10", len=10];
+ 11 -> 12 [label="0/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="0/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="0/12", len=12];
+ 1 -> 2 [label="10/15", len=15];
+ 1 -> 3 [label="0/8", len=8];
+ 1 -> 4 [label="0/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="0/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="0/15", len=15];
}
diff --git a/output/flow3.gv b/output/flow3.gv
index f2d5e10..561cae4 100644
--- a/output/flow3.gv
+++ b/output/flow3.gv
@@ -1,12 +1,25 @@
digraph flow3 {
rankdir="LR";
- label="(3) Flow. Value: 6";
- 1 -> 2 [label="6/8", len=8];
- 1 -> 3 [label="0/6", len=6];
- 2 -> 4 [label="6/6", len=6];
- 3 -> 4 [label="0/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="3/4", len=4];
- 5 -> 6 [label="3/6", len=6];
+ label="(3) Flow. Value: 18";
+ 9 -> 12 [label="10/15", len=15];
+ 10 -> 12 [label="0/10", len=10];
+ 11 -> 12 [label="8/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="0/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="8/12", len=12];
+ 1 -> 2 [label="10/15", len=15];
+ 1 -> 3 [label="0/8", len=8];
+ 1 -> 4 [label="8/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="0/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="8/15", len=15];
}
diff --git a/output/flow4.gv b/output/flow4.gv
index f1254b3..edb242d 100644
--- a/output/flow4.gv
+++ b/output/flow4.gv
@@ -1,12 +1,25 @@
digraph flow4 {
rankdir="LR";
- label="(4) Flow. Value: 7";
- 1 -> 2 [label="6/8", len=8];
- 1 -> 3 [label="1/6", len=6];
- 2 -> 4 [label="6/6", len=6];
- 3 -> 4 [label="1/10", len=10];
- 3 -> 5 [label="0/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="4/4", len=4];
- 5 -> 6 [label="3/6", len=6];
+ label="(4) Flow. Value: 26";
+ 9 -> 12 [label="10/15", len=15];
+ 10 -> 12 [label="8/10", len=10];
+ 11 -> 12 [label="8/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="0/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="8/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="8/12", len=12];
+ 1 -> 2 [label="10/15", len=15];
+ 1 -> 3 [label="8/8", len=8];
+ 1 -> 4 [label="8/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="0/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="8/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="8/15", len=15];
}
diff --git a/output/flow5.gv b/output/flow5.gv
index 52e2408..e8a5766 100644
--- a/output/flow5.gv
+++ b/output/flow5.gv
@@ -1,12 +1,25 @@
digraph flow5 {
rankdir="LR";
- label="(5) Flow. Value: 10";
- 1 -> 2 [label="6/8", len=8];
- 1 -> 3 [label="4/6", len=6];
- 2 -> 4 [label="6/6", len=6];
- 3 -> 4 [label="1/10", len=10];
- 3 -> 5 [label="3/7", len=7];
- 4 -> 5 [label="3/3", len=3];
- 4 -> 6 [label="4/4", len=4];
- 5 -> 6 [label="6/6", len=6];
+ label="(5) Flow. Value: 31";
+ 9 -> 12 [label="15/15", len=15];
+ 10 -> 12 [label="8/10", len=10];
+ 11 -> 12 [label="8/8", len=8];
+ 5 -> 9 [label="10/10", len=10];
+ 5 -> 6 [label="0/4", len=4];
+ 6 -> 9 [label="5/7", len=7];
+ 6 -> 10 [label="0/10", len=10];
+ 7 -> 5 [label="0/2", len=2];
+ 7 -> 10 [label="8/8", len=8];
+ 7 -> 11 [label="0/5", len=5];
+ 8 -> 7 [label="0/4", len=4];
+ 8 -> 11 [label="8/12", len=12];
+ 1 -> 2 [label="15/15", len=15];
+ 1 -> 3 [label="8/8", len=8];
+ 1 -> 4 [label="8/20", len=20];
+ 2 -> 5 [label="10/12", len=12];
+ 2 -> 6 [label="5/6", len=6];
+ 3 -> 2 [label="0/5", len=5];
+ 3 -> 7 [label="8/10", len=10];
+ 4 -> 7 [label="0/5", len=5];
+ 4 -> 8 [label="8/15", len=15];
}
diff --git a/output/residualGraph1.gv b/output/residualGraph1.gv
index fb128fe..6a24e21 100644
--- a/output/residualGraph1.gv
+++ b/output/residualGraph1.gv
@@ -1,14 +1,27 @@
digraph residualGraph1 {
rankdir="LR";
label="(1) residual graph.
- Augmenting path: [1, 2, 4, 5, 6].
- Residual capacity: 3";
- 4 -> 5 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
- 4 -> 6 [label="4", len=4];
- 5 -> 6 [label="6", len=6, penwidth=3, color="blue"];
- 1 -> 2 [label="8", len=8, penwidth=3, color="blue"];
- 1 -> 3 [label="6", len=6];
- 2 -> 4 [label="6", len=6, penwidth=3, color="blue"];
- 3 -> 4 [label="10", len=10];
- 3 -> 5 [label="7", len=7];
+ Augmenting path: [1, 2, 5, 9, 12].
+ Residual capacity: 10";
+ 5 -> 9 [label="10", len=10, penwidth=3, color="blue", fontcolor="red"];
+ 5 -> 6 [label="4", len=4];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="15", len=15];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="10", len=10];
+ 2 -> 5 [label="12", len=12, penwidth=3, color="blue"];
+ 2 -> 6 [label="6", len=6];
+ 1 -> 2 [label="15", len=15, penwidth=3, color="blue"];
+ 1 -> 3 [label="8", len=8];
+ 1 -> 4 [label="20", len=20];
+ 11 -> 12 [label="8", len=8];
+ 10 -> 12 [label="10", len=10];
+ 9 -> 12 [label="15", len=15, penwidth=3, color="blue"];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="12", len=12];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 10 [label="8", len=8];
+ 7 -> 11 [label="5", len=5];
+ 6 -> 9 [label="7", len=7];
+ 6 -> 10 [label="10", len=10];
}
diff --git a/output/residualGraph2.gv b/output/residualGraph2.gv
index 7dc73b4..647b18f 100644
--- a/output/residualGraph2.gv
+++ b/output/residualGraph2.gv
@@ -1,17 +1,30 @@
digraph residualGraph2 {
rankdir="LR";
label="(2) residual graph.
- Augmenting path: [1, 2, 4, 6].
- Residual capacity: 3";
- 6 -> 5 [label="3", len=3];
- 5 -> 4 [label="3", len=3];
- 5 -> 6 [label="3", len=3];
- 4 -> 2 [label="3", len=3];
- 4 -> 6 [label="4", len=4, penwidth=3, color="blue"];
- 3 -> 4 [label="10", len=10];
- 3 -> 5 [label="7", len=7];
- 2 -> 1 [label="3", len=3];
- 2 -> 4 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
- 1 -> 2 [label="5", len=5, penwidth=3, color="blue"];
- 1 -> 3 [label="6", len=6];
+ Augmenting path: [1, 4, 8, 11, 12].
+ Residual capacity: 8";
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="10", len=10];
+ 2 -> 1 [label="10", len=10];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="6", len=6];
+ 1 -> 2 [label="5", len=5];
+ 1 -> 3 [label="8", len=8];
+ 1 -> 4 [label="20", len=20, penwidth=3, color="blue"];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 10 [label="8", len=8];
+ 7 -> 11 [label="5", len=5];
+ 6 -> 9 [label="7", len=7];
+ 6 -> 10 [label="10", len=10];
+ 5 -> 6 [label="4", len=4];
+ 5 -> 2 [label="10", len=10];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="15", len=15, penwidth=3, color="blue"];
+ 11 -> 12 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"];
+ 10 -> 12 [label="10", len=10];
+ 9 -> 12 [label="5", len=5];
+ 9 -> 5 [label="10", len=10];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="12", len=12, penwidth=3, color="blue"];
+ 12 -> 9 [label="10", len=10];
}
diff --git a/output/residualGraph3.gv b/output/residualGraph3.gv
index 4facbb5..f692262 100644
--- a/output/residualGraph3.gv
+++ b/output/residualGraph3.gv
@@ -1,17 +1,33 @@
digraph residualGraph3 {
rankdir="LR";
label="(3) residual graph.
- Augmenting path: [1, 3, 4, 6].
- Residual capacity: 1";
- 5 -> 4 [label="3", len=3];
- 5 -> 6 [label="3", len=3];
- 4 -> 2 [label="6", len=6];
- 4 -> 6 [label="1", len=1, penwidth=3, color="blue", fontcolor="red"];
- 6 -> 4 [label="3", len=3];
- 6 -> 5 [label="3", len=3];
- 1 -> 2 [label="2", len=2];
- 1 -> 3 [label="6", len=6, penwidth=3, color="blue"];
- 3 -> 4 [label="10", len=10, penwidth=3, color="blue"];
- 3 -> 5 [label="7", len=7];
- 2 -> 1 [label="6", len=6];
+ Augmenting path: [1, 3, 7, 10, 12].
+ Residual capacity: 8";
+ 6 -> 9 [label="7", len=7];
+ 6 -> 10 [label="10", len=10];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 10 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"];
+ 7 -> 11 [label="5", len=5];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="4", len=4];
+ 8 -> 4 [label="8", len=8];
+ 9 -> 12 [label="5", len=5];
+ 9 -> 5 [label="10", len=10];
+ 2 -> 1 [label="10", len=10];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="6", len=6];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="10", len=10, penwidth=3, color="blue"];
+ 4 -> 1 [label="8", len=8];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="7", len=7];
+ 5 -> 6 [label="4", len=4];
+ 5 -> 2 [label="10", len=10];
+ 1 -> 2 [label="5", len=5];
+ 1 -> 3 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"];
+ 1 -> 4 [label="12", len=12];
+ 10 -> 12 [label="10", len=10, penwidth=3, color="blue"];
+ 11 -> 8 [label="8", len=8];
+ 12 -> 9 [label="10", len=10];
+ 12 -> 11 [label="8", len=8];
}
diff --git a/output/residualGraph4.gv b/output/residualGraph4.gv
index 15f7006..3c5dfbf 100644
--- a/output/residualGraph4.gv
+++ b/output/residualGraph4.gv
@@ -1,18 +1,35 @@
digraph residualGraph4 {
rankdir="LR";
label="(4) residual graph.
- Augmenting path: [1, 3, 5, 6].
- Residual capacity: 3";
- 1 -> 2 [label="2", len=2];
- 1 -> 3 [label="5", len=5, penwidth=3, color="blue"];
- 4 -> 2 [label="6", len=6];
- 4 -> 3 [label="1", len=1];
- 5 -> 4 [label="3", len=3];
- 5 -> 6 [label="3", len=3, penwidth=3, color="blue", fontcolor="red"];
- 2 -> 1 [label="6", len=6];
- 3 -> 1 [label="1", len=1];
- 3 -> 4 [label="9", len=9];
- 3 -> 5 [label="7", len=7, penwidth=3, color="blue"];
- 6 -> 4 [label="4", len=4];
- 6 -> 5 [label="3", len=3];
+ Augmenting path: [1, 2, 6, 9, 12].
+ Residual capacity: 5";
+ 9 -> 12 [label="5", len=5, penwidth=3, color="blue", fontcolor="red"];
+ 9 -> 5 [label="10", len=10];
+ 10 -> 12 [label="2", len=2];
+ 10 -> 7 [label="8", len=8];
+ 7 -> 5 [label="2", len=2];
+ 7 -> 11 [label="5", len=5];
+ 7 -> 3 [label="8", len=8];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="4", len=4];
+ 8 -> 4 [label="8", len=8];
+ 11 -> 8 [label="8", len=8];
+ 12 -> 9 [label="10", len=10];
+ 12 -> 10 [label="8", len=8];
+ 12 -> 11 [label="8", len=8];
+ 1 -> 2 [label="5", len=5, penwidth=3, color="blue", fontcolor="red"];
+ 1 -> 4 [label="12", len=12];
+ 2 -> 1 [label="10", len=10];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="6", len=6, penwidth=3, color="blue"];
+ 5 -> 6 [label="4", len=4];
+ 5 -> 2 [label="10", len=10];
+ 6 -> 9 [label="7", len=7, penwidth=3, color="blue"];
+ 6 -> 10 [label="10", len=10];
+ 3 -> 1 [label="8", len=8];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="2", len=2];
+ 4 -> 1 [label="8", len=8];
+ 4 -> 7 [label="5", len=5];
+ 4 -> 8 [label="7", len=7];
}
diff --git a/output/residualGraph5.gv b/output/residualGraph5.gv
index 485fdda..deba65b 100644
--- a/output/residualGraph5.gv
+++ b/output/residualGraph5.gv
@@ -1,17 +1,35 @@
digraph residualGraph5 {
rankdir="LR";
label="(5) residual graph.
- Augmenting path: none.";
- 2 -> 1 [label="6", len=6];
- 3 -> 1 [label="4", len=4];
- 3 -> 4 [label="9", len=9];
- 3 -> 5 [label="4", len=4];
- 4 -> 2 [label="6", len=6];
- 4 -> 3 [label="1", len=1];
- 5 -> 3 [label="3", len=3];
- 5 -> 4 [label="3", len=3];
- 6 -> 4 [label="4", len=4];
- 6 -> 5 [label="6", len=6];
- 1 -> 2 [label="2", len=2];
- 1 -> 3 [label="2", len=2];
+ Augmenting path: [1, 4, 7, 5, 6, 10, 12].
+ Residual capacity: 2";
+ 7 -> 5 [label="2", len=2, penwidth=3, color="blue", fontcolor="red"];
+ 7 -> 11 [label="5", len=5];
+ 7 -> 3 [label="8", len=8];
+ 8 -> 7 [label="4", len=4];
+ 8 -> 11 [label="4", len=4];
+ 8 -> 4 [label="8", len=8];
+ 5 -> 6 [label="4", len=4, penwidth=3, color="blue"];
+ 5 -> 2 [label="10", len=10];
+ 6 -> 9 [label="2", len=2];
+ 6 -> 10 [label="10", len=10, penwidth=3, color="blue"];
+ 6 -> 2 [label="5", len=5];
+ 3 -> 1 [label="8", len=8];
+ 3 -> 2 [label="5", len=5];
+ 3 -> 7 [label="2", len=2];
+ 4 -> 1 [label="8", len=8];
+ 4 -> 7 [label="5", len=5, penwidth=3, color="blue"];
+ 4 -> 8 [label="7", len=7];
+ 1 -> 4 [label="12", len=12, penwidth=3, color="blue"];
+ 2 -> 1 [label="15", len=15];
+ 2 -> 5 [label="2", len=2];
+ 2 -> 6 [label="1", len=1];
+ 11 -> 8 [label="8", len=8];
+ 12 -> 9 [label="15", len=15];
+ 12 -> 10 [label="8", len=8];
+ 12 -> 11 [label="8", len=8];
+ 9 -> 5 [label="10", len=10];
+ 9 -> 6 [label="5", len=5];
+ 10 -> 12 [label="2", len=2, penwidth=3, color="blue", fontcolor="red"];
+ 10 -> 7 [label="8", len=8];
}
diff --git a/src/main/java/m1graphs2025/Edge.java b/src/main/java/m1graphs2025/Edge.java
deleted file mode 100644
index 4a34376..0000000
--- a/src/main/java/m1graphs2025/Edge.java
+++ /dev/null
@@ -1,336 +0,0 @@
-package m1graphs2025;
-
-import java.util.*;
-
-/**
- * Représente une arête (Edge) dans un graphe.
- * Une arête relie deux nœuds (source et destination) et peut éventuellement
- * porter un poids si le graphe est pondéré.
- *
- * Cette classe implémente l’interface {@link Comparable} afin de permettre
- * le tri des arêtes selon leur poids, puis selon leurs nœuds.
- *
- */
-public class Edge implements Comparable {
-
- // ======================
- // Attributs
- // ======================
-
- /** Nœud source de l’arête */
- private Node from;
-
- /** Nœud destination de l’arête */
- private Node to;
-
- /** Poids de l’arête (null si non pondérée) */
- private Integer weight;
-
- // ======================
- // Constructeurs
- // ======================
-
- /**
- * Crée une arête non pondérée entre deux nœuds.
- *
- * @param from Le nœud source.
- * @param to Le nœud destination.
- */
- public Edge(Node from, Node to) {
- this.from = from;
- this.to = to;
- this.weight = null;
- }
-
- /**
- * Crée une arête vide (sans nœuds ni poids).
- */
- public Edge() {
- this.from = null;
- this.to = null;
- this.weight = null;
- }
-
- /**
- * Crée une arête pondérée entre deux nœuds.
- *
- * @param from Le nœud source.
- * @param to Le nœud destination.
- * @param weight Le poids de l’arête.
- */
- public Edge(Node from, Node to, Integer weight) {
- this.from = from;
- this.to = to;
- this.weight = weight;
- }
-
- /**
- * Crée une arête non pondérée à partir des identifiants des nœuds dans un
- * graphe donné.
- *
- * @param fromId L’identifiant du nœud source.
- * @param toId L’identifiant du nœud destination.
- * @param g Le graphe auquel appartiennent les nœuds.
- * @throws IllegalArgumentException si le graphe est null.
- */
- public Edge(int fromId, int toId, Graph g) {
- if (g == null) {
- throw new IllegalArgumentException("Le graphe ne peut pas être null.");
- }
- Node from = g.getNode(fromId);
- Node to = g.getNode(toId);
-
- if (from == null) {
- from = new Node(fromId, g);
- g.addNode(from);
- }
- if (to == null) {
- to = new Node(toId, g);
- g.addNode(to);
- }
-
- this.from = from;
- this.to = to;
- this.weight = 0; // ou une valeur par défaut
- }
-
- /**
- * Crée une arête pondérée à partir des identifiants des nœuds dans un graphe
- * donné.
- *
- * @param fromId L’identifiant du nœud source.
- * @param toId L’identifiant du nœud destination.
- * @param weight Le poids de l’arête.
- * @param g Le graphe auquel appartiennent les nœuds.
- * @throws IllegalArgumentException si le graphe est null.
- */
- public Edge(int fromId, int toId, int weight, Graph g) {
- if (g == null) {
- throw new IllegalArgumentException("Le graphe ne peut pas être null.");
- }
- Node from = g.getNode(fromId);
- Node to = g.getNode(toId);
-
- if (from == null) {
- from = new Node(fromId, g);
- g.addNode(from);
- }
- if (to == null) {
- to = new Node(toId, g);
- g.addNode(to);
- }
-
- this.from = from;
- this.to = to;
- this.weight = weight;
- }
-
- // ======================
- // Getters / Setters
- // ======================
-
- /**
- * Retourne le nœud source de l’arête.
- *
- * @return Le nœud source.
- */
- public Node getNodeFrom() {
- return from;
- }
-
- /**
- * Retourne le nœud destination de l’arête.
- *
- * @return Le nœud destination.
- */
- public Node getNodeTo() {
- return to;
- }
-
- /**
- * Retourne le poids de l’arête.
- *
- * @return Le poids de l’arête, ou null si elle n’est pas pondérée.
- */
- public Integer getWeight() {
- return weight;
- }
-
- /**
- * Définit le nœud source de l’arête.
- *
- * @param from Le nouveau nœud source.
- */
- public void setNodeFrom(Node from) {
- this.from = from;
- }
-
- /**
- * Définit le nœud destination de l’arête.
- *
- * @param to Le nouveau nœud destination.
- */
- public void setNodeTo(Node to) {
- this.to = to;
- }
-
- /**
- * Définit le poids de l’arête.
- *
- * @param weight Le nouveau poids.
- */
- public void setWeight(Integer weight) {
- this.weight = weight;
- }
-
- // ======================
- // Méthodes fonctionnelles
- // ======================
-
- /**
- * Retourne le nœud source de l’arête.
- *
- * @return Le nœud source.
- */
- public Node from() {
- return from;
- }
-
- /**
- * Retourne le nœud destination de l’arête.
- *
- * @return Le nœud destination.
- */
- public Node to() {
- return to;
- }
-
- /**
- * Retourne une arête symétrique (inversée) avec la même pondération.
- *
- * @return Une nouvelle arête inversée ou null si les nœuds n’appartiennent pas
- * au même graphe.
- */
- public Edge getSymmetric() {
- Graph g1 = (from != null) ? from.getGraph() : null;
- Graph g2 = (to != null) ? to.getGraph() : null;
-
- if (Objects.equals(g1, g2)) {
- Node symFrom = new Node(to.getId(), g1, to.getName());
- Node symTo = new Node(from.getId(), g1, from.getName());
- return new Edge(symFrom, symTo, this.weight);
- }
- return null;
- }
-
- /**
- * Vérifie si l’arête est une boucle (relie un nœud à lui-même).
- *
- * @return true si l’arête est une boucle, false sinon.
- */
- public boolean isSelfLoop() {
- return from != null && to != null && from.getId() == to.getId();
- }
-
- /**
- * Vérifie s’il existe une autre arête reliant les mêmes nœuds
- * mais avec un poids différent (arête multiple).
- *
- * @return true si une arête multiple existe, false sinon.
- */
- public boolean isMultiEdge() {
- if (from == null || from.getGraph() == null)
- return false;
-
- Graph graph = from.getGraph();
- List edges = graph.adjEdList.get(from);
-
- if (edges == null)
- return false;
-
- for (Edge edge : edges) {
- boolean sameNodes = edge.from.getId() == this.from.getId()
- && edge.to.getId() == this.to.getId();
- boolean differentWeight = !Objects.equals(edge.getWeight(), this.weight);
-
- if (sameNodes && differentWeight) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Indique si l’arête est pondérée.
- *
- * @return true si l’arête est pondérée, false sinon.
- */
- public boolean isWeighted() {
- return this.weight != null;
- }
-
- // ======================
- // Méthodes redéfinies
- // ======================
-
- /**
- * Compare deux arêtes selon leur poids, puis les identifiants des nœuds.
- * Gère correctement les arêtes non pondérées (poids null).
- *
- * @param other L’autre arête à comparer.
- * @return Un entier négatif, zéro ou un entier positif selon l’ordre.
- */
- @Override
- public int compareTo(Edge other) {
- int w1 = (this.weight == null) ? 0 : this.weight;
- int w2 = (other.weight == null) ? 0 : other.weight;
-
- int cmp = Integer.compare(w1, w2);
- if (cmp != 0)
- return cmp;
-
- cmp = Integer.compare(this.from.getId(), other.from.getId());
- if (cmp != 0)
- return cmp;
-
- return Integer.compare(this.to.getId(), other.to.getId());
- }
-
- /**
- * Vérifie l’égalité entre deux arêtes :
- * même nœud source, même destination, même poids.
- *
- * @param o L’objet à comparer.
- * @return true si les arêtes sont égales, false sinon.
- */
- @Override
- public boolean equals(Object o) {
- if (this == o)
- return true;
- if (!(o instanceof Edge edge))
- return false;
- return Objects.equals(from, edge.from)
- && Objects.equals(to, edge.to)
- && Objects.equals(weight, edge.weight);
- }
-
- /**
- * Retourne le code de hachage de l’arête.
- *
- * @return Le code de hachage.
- */
- @Override
- public int hashCode() {
- return Objects.hash(from, to, weight);
- }
-
- /**
- * Retourne une représentation textuelle de l’arête.
- *
- * @return Une chaîne de caractères représentant l’arête.
- */
- @Override
- public String toString() {
- return "Edge(" + from.getId() + " -> " + to.getId() + ", w=" + weight + ")";
- }
-}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java
new file mode 100644
index 0000000..44a61d6
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java
@@ -0,0 +1,121 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * Trouve un chemin augmentant en transformant le problème du plus long chemin
+ * en un plus court chemin en utilisant des poids négatifs (coût = -1 par arête)
+ * et l'algorithme de Bellman-Ford (limité à n-1 relaxations, donc simple path).
+ *
+ * Attention: si le graphe contient des cycles, Bellman-Ford peut détecter des
+ * cycles négatifs — nous ignorons la détection et utilisons les distances après
+ * n-1 itérations (correspondant aux plus courts chemins simples).
+ */
+public class BFLongestAugmentingPathFinder implements AugmentingPathFinder {
+
+ @Override
+ public List findAugmentingPath(ResidualGraph a3ResidualGraph, int sourceId, int sinkId) {
+
+
+ // Approach:
+ // 1) Run Bellman-Ford with cost = -1 per (positive-capacity) residual edge for exactly n-1 relaxations.
+ // This yields distances dist[] where dist[v] equals -L for some simple path length L (if reachable).
+ // 2) Build the equality graph H containing edges (u->v) that satisfy dist[v] == dist[u] - 1.
+ // Any path in H from source to sink corresponds to a path achieving the target distance.
+ // 3) Find a simple path in H from source to sink using BFS over partial paths (iterative,
+ // avoids recursion and prefers shorter expansions). This guarantees no infinite loops.
+
+
+
+ if (a3ResidualGraph == null) return null;
+ Graph g = a3ResidualGraph.getGraph();
+ if (g == null) return null;
+
+ // Topological DP approach (works only if residual is a DAG)
+ List nodes = g.getAllNodes();
+ int n = nodes.size();
+ if (n == 0) return null;
+
+ Map idToIndex = new HashMap<>();
+ for (int i = 0; i < nodes.size(); i++) idToIndex.put(nodes.get(i).getId(), i);
+ if (!idToIndex.containsKey(sourceId) || !idToIndex.containsKey(sinkId)) return null;
+ int src = idToIndex.get(sourceId);
+ int tgt = idToIndex.get(sinkId);
+
+ // Build in-degree considering only positive residual edges
+ int[] indeg = new int[n];
+ for (int i = 0; i < n; i++) indeg[i] = 0;
+ List edges = new ArrayList<>();
+ for (Node u : nodes) {
+ for (Edge e : u.getOutEdges()) {
+ Integer w = e.getWeight();
+ if (w != null && w > 0) {
+ int vi = idToIndex.get(e.getNodeTo().getId());
+ indeg[vi]++;
+ edges.add(e);
+ }
+ }
+ }
+
+ Deque q = new ArrayDeque<>();
+ for (int i = 0; i < n; i++) if (indeg[i] == 0) q.addLast(i);
+ List topo = new ArrayList<>();
+ while (!q.isEmpty()) {
+ int u = q.removeFirst();
+ topo.add(u);
+ Node nu = nodes.get(u);
+ for (Edge e : nu.getOutEdges()) {
+ Integer w = e.getWeight();
+ if (w == null || w <= 0) continue;
+ int vi = idToIndex.get(e.getNodeTo().getId());
+ indeg[vi]--;
+ if (indeg[vi] == 0) q.addLast(vi);
+ }
+ }
+
+ if (topo.size() != n) {
+ // residual graph contains cycles — cannot apply topological DP safely
+ // fallback to BFS to guarantee a simple augmenting path
+ AugmentingPathFinder bfs = new BFSPathFinder();
+ return bfs.findAugmentingPath(a3ResidualGraph, sourceId, sinkId);
+ }
+
+ // DP: longest path length from source to each node (in number of edges)
+ final int NEG = Integer.MIN_VALUE / 4;
+ int[] dp = new int[n];
+ int[] pred = new int[n];
+ Arrays.fill(dp, NEG);
+ Arrays.fill(pred, -1);
+ dp[src] = 0;
+
+ for (int u : topo) {
+ if (dp[u] == NEG) continue; // unreachable from source
+ Node nu = nodes.get(u);
+ for (Edge e : nu.getOutEdges()) {
+ Integer w = e.getWeight();
+ if (w == null || w <= 0) continue;
+ int v = idToIndex.get(e.getNodeTo().getId());
+ if (dp[u] + 1 > dp[v]) {
+ dp[v] = dp[u] + 1;
+ pred[v] = u;
+ }
+ }
+ }
+
+ if (dp[tgt] == NEG) return null;
+
+ // reconstruct path from pred[]
+ LinkedList path = new LinkedList<>();
+ int cur = tgt;
+ int steps = 0;
+ while (cur != -1 && steps <= n) {
+ path.addFirst(nodes.get(cur));
+ if (cur == src) break;
+ cur = pred[cur];
+ steps++;
+ }
+ if (path.isEmpty() || path.getFirst().getId() != sourceId) return null;
+ return path;
+ }
+}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/Main.java b/src/main/java/m1graphs2025/MaximumFlow/Main.java
index 29d96b5..ae34533 100644
--- a/src/main/java/m1graphs2025/MaximumFlow/Main.java
+++ b/src/main/java/m1graphs2025/MaximumFlow/Main.java
@@ -2,6 +2,10 @@ package m1graphs2025.MaximumFlow;
import m1graphs2025.pw2.*;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Scanner;
+
/**
* Main : point d'entrée du programme sans arguments.
*
@@ -19,6 +23,8 @@ public class Main {
// S'assure que le chemin se termine par '/'
if (!outFolder.endsWith("/")) outFolder += "/";
+ clearDirectory(outFolder);
+
try {
// Lecture du DOT en utilisant DotReader (PW2)
@@ -39,11 +45,28 @@ public class Main {
// Export DOT
DotExporter exporter = new DotExporter();
+ AugmentingPathFinder finder;
+
+ System.out.println("Selectionner un algorithme de rechereche : \n1- BFS\n2- DFS\n3- Bellman-Ford (recommended)");
+ Scanner sc = new Scanner(System.in);
+ int x = sc.nextInt();
+ while (true){
+ if (x == 1) {
+ finder = new BFSPathFinder(); // Edmonds-Karp (recommandé)
+ break;
+ }
+ if (x == 2) {
+ finder = new DFSPathFinder(); // Edmonds-Karp (recommandé)
+ break;
+ }
+
+ if (x == 3) {
+
+ finder = new MaxBottleneckPathFinder(); // plus long chemin (Bellman-Ford)
+ break;
+ }
+ }
- // Choix de l'algorithme de recherche de chemin augmentant
- //AugmentingPathFinder finder = new BFSPathFinder(); // Edmonds-Karp (recommandé)
- AugmentingPathFinder finder = new DFSPathFinder(); // Ford-Fulkerson classique
- // AugmentingPathFinder finder = new MaxCapacityPathFinder(); // plus grande capacité
// Lancement de l'algorithme
FordFulkerson algo = new FordFulkerson(network, finder, exporter);
@@ -60,4 +83,31 @@ public class Main {
e.printStackTrace();
}
}
+
+ public static void clearDirectory(String folderPath) {
+ try {
+ Path dir = Path.of(folderPath);
+ if (!Files.exists(dir)) {
+ Files.createDirectories(dir);
+ return;
+ }
+
+ // Supprime récursivement tout le contenu
+ Files.walk(dir)
+ .sorted((a, b) -> b.compareTo(a)) // supprime d'abord les fichiers, puis les dossiers
+ .forEach(path -> {
+ if (!path.equals(dir)) {
+ try { Files.delete(path); }
+ catch (Exception e) {
+ System.err.println("Impossible de supprimer : " + path);
+ }
+ }
+ });
+
+ } catch (Exception e) {
+ System.err.println("Erreur lors du nettoyage du dossier : " + folderPath);
+ e.printStackTrace();
+ }
+ }
+
}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java
new file mode 100644
index 0000000..2d74ec7
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java
@@ -0,0 +1,78 @@
+package m1graphs2025.MaximumFlow;
+
+import m1graphs2025.pw2.*;
+import java.util.*;
+
+/**
+ * MaxBottleneckPathFinder :
+ * Trouve un chemin augmentant en maximisant la capacité résiduelle minimale
+ * sur le chemin (Maximum Bottleneck Path).
+ *
+ * Cet algo est le plus efficace pour réduire le nombre d'étapes
+ * lors du calcul du flot maximum.
+ */
+public class MaxBottleneckPathFinder implements AugmentingPathFinder {
+
+ @Override
+ public List findAugmentingPath(ResidualGraph residual, int sourceId, int sinkId) {
+ Graph g = residual.getGraph();
+
+ Node source = g.getNode(sourceId);
+ Node sink = g.getNode(sinkId);
+ if (source == null || sink == null) return null;
+
+ // capacité max rencontrée pour chaque nœud
+ Map best = new HashMap<>();
+ // parent pour reconstruire le chemin
+ Map parent = new HashMap<>();
+
+ for (Node n : g.getAllNodes()) {
+ best.put(n.getId(), 0);
+ }
+ best.put(sourceId, Integer.MAX_VALUE);
+
+ // max-heap : on explore toujours le sommet ayant la meilleure capacité
+ PriorityQueue pq = new PriorityQueue<>(
+ (a, b) -> Integer.compare(best.get(b.getId()), best.get(a.getId()))
+ );
+ pq.add(source);
+
+ while (!pq.isEmpty()) {
+ Node u = pq.poll();
+ int ucap = best.get(u.getId());
+
+ if (u.getId() == sinkId) break;
+
+ for (Edge e : g.getOutEdges(u)) {
+ if (e.getResidualCapacity() > 0) {
+ Node v = e.getNodeTo();
+ int bottleneck = Math.min(ucap, e.getResidualCapacity());
+
+ if (bottleneck > best.get(v.getId())) {
+ best.put(v.getId(), bottleneck);
+ parent.put(v.getId(), u.getId());
+ pq.add(v);
+ }
+ }
+ }
+ }
+
+ if (!parent.containsKey(sinkId)) return null;
+
+ return reconstructPath(g, parent, sourceId, sinkId);
+ }
+
+ private List reconstructPath(Graph g, Map parent, int s, int t) {
+ List path = new ArrayList<>();
+ int cur = t;
+
+ while (cur != s) {
+ path.add(g.getNode(cur));
+ cur = parent.get(cur);
+ }
+ path.add(g.getNode(s));
+
+ Collections.reverse(path);
+ return path;
+ }
+}
diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java
deleted file mode 100644
index 5746bed..0000000
--- a/src/main/java/m1graphs2025/UndirectedGraph.java
+++ /dev/null
@@ -1,351 +0,0 @@
-package m1graphs2025;
-
-import java.io.BufferedReader;
-import java.io.FileReader;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.*;
-
-/**
- * Représente un graphe non orienté en héritant de Graph.
- * Chaque arête (u, v) est automatiquement dupliquée en (v, u).
- */
-public class UndirectedGraph extends Graph {
-
- // ======================
- // CONSTRUCTEURS
- // ======================
- public UndirectedGraph(Map> adjEdList) {
- super(adjEdList);
- }
-
- public UndirectedGraph() {
- super(new HashMap<>()); // graphe vide
- }
-
- public UndirectedGraph(int... successorArray) {
- int taille = successorArray.length;
- Map> adjEdList = new HashMap<>();
- int i = 0;
- int id = 1;
-
- // D'abord, on crée tous les nœuds pour les réutiliser (sinon on crée des
- // doublons)
- Map nodes = new HashMap<>();
-
- // Première passe : créer les nœuds
- int tempId = 1;
- while (i < taille) {
- Node node = new Node(tempId, this);
- nodes.put(tempId, node);
-
- while (i < taille && successorArray[i] != 0) {
- int succ = successorArray[i];
- if (!nodes.containsKey(succ)) {
- nodes.put(succ, new Node(succ, this));
- }
- i++;
- }
-
- i++; // sauter le 0
- tempId++;
- }
-
- // Deuxième passe : créer les arêtes bidirectionnelles
- i = 0;
- id = 1;
- while (i < taille) {
- Node node = nodes.get(id);
- List list = adjEdList.computeIfAbsent(node, k -> new ArrayList<>());
-
- while (i < taille && successorArray[i] != 0) {
- Node nodeTo = nodes.get(successorArray[i]);
- Edge edge = new Edge(node, nodeTo);
- list.add(edge);
-
- // Ajout de l’arête inverse (bidirectionnelle)
- List reverseList = adjEdList.computeIfAbsent(nodeTo, k -> new ArrayList<>());
- Edge reverseEdge = new Edge(nodeTo, node);
- reverseList.add(reverseEdge);
-
- i++;
- }
-
- i++; // sauter le 0
- id++;
- }
-
- this.adjEdList = adjEdList;
- }
-
- @Override
- public boolean addNode(Node n) {
- return super.addNode(n);
- }
-
- /**
- * Symétrise toutes les arêtes existantes dans le graphe
- * (utile après construction par tableau de successeurs).
- */
- private void symmetrize() {
- List edges = getAllEdges();
- for (Edge e : edges) {
- Node u = e.getNodeFrom();
- Node v = e.getNodeTo();
- if (!existsEdge(v, u)) {
- if (e.isWeighted())
- addEdge(v, u, e.getWeight());
- else
- addEdge(v, u);
- }
- }
- }
-
- // ======================
- // API DES ARÊTES (Override)
- // ======================
-
- @Override
- public void addEdge(Node from, Node to) {
- super.addEdge(from, to); // u -> v
- super.addEdge(to, from); // v -> u pour non dirigé
- }
-
- @Override
- public void addEdge(Node from, Node to, int weight) {
- super.addEdge(from, to, weight);
- super.addEdge(to, from, weight);
- }
-
- @Override
- public void addEdge(int fromId, int toId) {
- Node from = getNode(fromId);
- Node to = getNode(toId);
- if (from == null)
- from = new Node(fromId, this);
- if (to == null)
- to = new Node(toId, this);
- addEdge(from, to);
- }
-
- @Override
- public void addEdge(int fromId, int toId, int weight) {
- Node from = getNode(fromId);
- Node to = getNode(toId);
- if (from == null)
- from = new Node(fromId, this);
- if (to == null)
- to = new Node(toId, this);
- addEdge(from, to, weight);
- }
-
- @Override
- public boolean removeEdge(Node from, Node to) {
- boolean r1 = super.removeEdge(from, to);
- boolean r2 = super.removeEdge(to, from);
- return r1 || r2;
- }
-
- @Override
- public boolean removeEdge(int fromId, int toId) {
- boolean r1 = super.removeEdge(fromId, toId);
- boolean r2 = super.removeEdge(toId, fromId);
- return r1 || r2;
- }
-
- @Override
- public boolean removeEdge(Edge e) {
- boolean r1 = super.removeEdge(e);
- Edge sym = e.getSymmetric();
- boolean r2 = (sym != null) && super.removeEdge(sym);
- return r1 || r2;
- }
-
- @Override
- public boolean existsEdge(Node u, Node v) {
- return super.existsEdge(u, v) || super.existsEdge(v, u);
- }
-
- @Override
- public boolean existsEdge(int uId, int vId) {
- return existsEdge(getNode(uId), getNode(vId));
- }
-
- @Override
- public boolean isMultiEdge(Node u, Node v) {
- // Vérifie dans les deux directions
- return super.isMultiEdge(u, v) || super.isMultiEdge(v, u);
- }
-
- @Override
- public UndirectedGraph copy() {
- Map> copy = new HashMap<>();
- for (Node u : adjEdList.keySet()) {
- List list = new ArrayList<>();
- for (Edge e : adjEdList.get(u))
- list.add(new Edge(e.getNodeFrom(), e.getNodeTo(), e.getWeight()));
- copy.put(u, list);
- }
- return new UndirectedGraph(copy);
- }
-
- @Override
- public UndirectedGraph getTransitiveClosure() {
- UndirectedGraph closure = this.copy();
- List nodes = closure.getAllNodes();
- int n = nodes.size();
- for (Node k : nodes) {
- for (Node i : nodes) {
- for (Node j : nodes) {
- if (closure.existsEdge(i, k) && closure.existsEdge(k, j)) {
- if (!closure.existsEdge(i, j))
- closure.addEdge(i, j);
- }
- }
- }
- }
- return closure;
- }
-
- /**
- * Lit un fichier DOT et retourne le graphe non orienté associé.
- * Le format attend est le suivant :
- * - Ligne contenant un seul nombre : nœud isolé
- * - Ligne contenant trois nombres : arête (u, v, poids)
- * Les arêtes sont automatiquement dupliquées pour satisfaire la propriété
- * de graphe non orienté.
- * Si une erreur survient lors de la lecture du fichier, une exception est
- * levée.
- *
- * @param filename Le nom du fichier à lire.
- * @return Le graphe non orienté associé au fichier.
- * @throws RuntimeException si une erreur survient lors de la lecture du
- * fichier.
- */
-
- public static UndirectedGraph fromDotFile(String filename) {
- Map> adjEdList = new HashMap<>();
- UndirectedGraph g = new UndirectedGraph(adjEdList);
-
- try (BufferedReader reader = new BufferedReader(new FileReader(filename))) {
- String line;
-
- while ((line = reader.readLine()) != null) {
- line = line.trim();
- if (line.isEmpty())
- continue; // skip empty lines
-
- // --- Case 1: isolated node
- if (line.matches("^\\d+$")) {
- int id = Integer.parseInt(line);
- Node node = g.getOrCreateNode(id);
- adjEdList.putIfAbsent(node, new ArrayList<>());
- continue;
- }
-
- // --- Case 2: edge line
- String[] tokens = line.replaceAll("[^0-9]+", " ").trim().split("\\s+");
- if (tokens.length < 2)
- continue; // invalid line → skip
-
- int fromId = Integer.parseInt(tokens[0]);
- int toId = Integer.parseInt(tokens[1]);
-
- Node from = g.getOrCreateNode(fromId);
- Node to = g.getOrCreateNode(toId);
-
- Edge edge;
- if (tokens.length >= 3) {
- int weight = Integer.parseInt(tokens[2]);
- edge = new Edge(from, to, weight);
- } else {
- edge = new Edge(from, to);
- }
-
- // Add to adjacency list
- adjEdList.computeIfAbsent(from, k -> new ArrayList<>()).add(edge);
- adjEdList.putIfAbsent(to, new ArrayList<>()); // ensure target node exists
- }
-
- } catch (IOException e) {
- throw new RuntimeException("Error reading file: " + filename, e);
- }
-
- return g;
- }
-
- /**
- * Generates a DOT-format string representation of this graph.
- *
- * Example output for an undirected graph:
- *
- *
- * graph G {
- * rankdir=LR;
- * 1 -- 2 [label="5", len="5"];
- * 3 -- 4 [label="3", len="3"];
- * 4;
- * }
- *
- *
- *
- * @return the DOT-format representation of the graph
- */
- public String toDotString() {
- StringBuilder sb = new StringBuilder();
- sb.append("graph G {\n");
- sb.append("\trankdir=LR\n");
-
- // Liste triée des arêtes
- List edges = getAllEdges();
- edges.sort(Comparator.comparingInt((Edge e) -> e.getNodeFrom().getId())
- .thenComparingInt(e -> e.getNodeTo().getId()));
-
- // Affichage des arêtes
- for (Edge e : edges) {
- sb.append("\t")
- .append(e.getNodeFrom().getId())
- .append(" -- ")
- .append(e.getNodeTo().getId());
- if (e.isWeighted()) {
- sb.append(" [label=").append(e.getWeight())
- .append(", len=").append(e.getWeight()).append("]");
- }
- sb.append(";\n");
- }
-
- // Affichage des nœuds isolés (ceux qui n’ont aucune arête)
- Set nodesWithEdges = new HashSet<>();
- for (Edge e : edges) {
- nodesWithEdges.add(e.getNodeFrom());
- nodesWithEdges.add(e.getNodeTo());
- }
-
- for (Node node : adjEdList.keySet()) {
- if (!nodesWithEdges.contains(node)) {
- sb.append("\t").append(node.getId()).append(";\n");
- }
- }
-
- sb.append("}\n");
- return sb.toString();
- }
-
- public void toDotFile(String filename) {
- super.toDotFile(filename);
- }
-
- public void toDotFile(String filename, String ext) {
- super.toDotFile(filename, ".gv");
- }
-
- // ======================
- // Utilitaires
- // ======================
-
- public boolean isDirected() {
- return false;
- }
-}
diff --git a/src/main/java/m1graphs2025/pw2/Edge.java b/src/main/java/m1graphs2025/pw2/Edge.java
index b5c5d18..df9f2ac 100644
--- a/src/main/java/m1graphs2025/pw2/Edge.java
+++ b/src/main/java/m1graphs2025/pw2/Edge.java
@@ -452,4 +452,13 @@ public class Edge implements Comparable {
return from.getId() + " -> " + to.getId();
}
+ // =================================================================================================================
+ // ============================================Flow Max METH====================================================
+
+ public int getResidualCapacity() {
+ if (this.weight == null) {
+ return Integer.MAX_VALUE; // Capacité infinie pour les arêtes non pondérées
+ }
+ return this.weight;
+ }
}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/pw2/Graph.java b/src/main/java/m1graphs2025/pw2/Graph.java
index f85c9be..5bec828 100644
--- a/src/main/java/m1graphs2025/pw2/Graph.java
+++ b/src/main/java/m1graphs2025/pw2/Graph.java
@@ -1510,11 +1510,6 @@ public class Graph {
nodeVisit.get(u).setFinishTime(time.get());
visited.add(u);
}
- nodeVisit.get(u).setColour(NodeColour.BLACK);
- time.incrementAndGet();
- nodeVisit.get(u).setFinishTime(time.get());
- visited.add(u);
-}
// =================================================================================================================
// ===============================================DOT I/O===========================================================
@@ -1692,31 +1687,10 @@ public class Graph {
*
* @return liste des nœuds triés par ID
*/
- private List sortNodes() {
+ public List sortNodes() {
return adjEdList.keySet().stream()
.sorted(Comparator.comparingInt(Node::getId))
.toList();
}
}
-
-/**
- * Réinitialise les informations de visite dans une carte donnée pour chaque nœud du graphe.
- *
- * @param map La carte à réinitialiser, où chaque nœud sera associé à un nouvel objet {@link NodeVisitInfo}.
- */
-private void initVisitInfo(Map map) {
- for (Node n : adjEdList.keySet())
- map.put(n, new NodeVisitInfo());
-}
-
-/**
- * Trie les nœuds du graphe par leur identifiant dans l'ordre croissant.
- *
- * @return Une liste triée des nœuds du graphe.
- */
-private List sortNodes() {
- List nodes = new ArrayList<>(adjEdList.keySet());
- nodes.sort(Comparator.comparingInt(Node::getId));
- return nodes;
-}}
\ No newline at end of file
diff --git a/src/main/java/m1graphs2025/pw2/LongestPathFinder.java b/src/main/java/m1graphs2025/pw2/LongestPathFinder.java
new file mode 100644
index 0000000..6a25590
--- /dev/null
+++ b/src/main/java/m1graphs2025/pw2/LongestPathFinder.java
@@ -0,0 +1,74 @@
+package m1graphs2025.pw2;
+
+import java.util.*;
+
+/**
+ * Utility pour rechercher un chemin le plus long dans un graphe orienté acyclique (DAG).
+ * - Si le graphe contient un cycle, la méthode retourne null.
+ * - Les arêtes pondérées utilisent `Edge.getWeight()` si non-null, sinon on considère un poids = 1.
+ */
+public class LongestPathFinder {
+
+ public static List findLongestPath(Graph g, int sourceId, int sinkId) {
+ if (g == null) return null;
+ Node source = g.getNode(sourceId);
+ Node sink = g.getNode(sinkId);
+ if (source == null || sink == null) return null;
+
+ // Kahn pour ordre topologique
+ Map inDeg = new HashMap<>();
+ List all = g.getAllNodes();
+ for (Node n : all) inDeg.put(n, g.inDegree(n));
+
+ Deque q = new ArrayDeque<>();
+ for (Map.Entry e : inDeg.entrySet()) if (e.getValue() == 0) q.add(e.getKey());
+
+ List topo = new ArrayList<>();
+ while (!q.isEmpty()) {
+ Node u = q.removeFirst();
+ topo.add(u);
+ for (Edge out : u.getOutEdges()) {
+ Node v = out.getNodeTo();
+ inDeg.put(v, inDeg.get(v) - 1);
+ if (inDeg.get(v) == 0) q.addLast(v);
+ }
+ }
+
+ if (topo.size() != all.size()) {
+ // graphe non-DAG
+ return null;
+ }
+
+ final int NEG = Integer.MIN_VALUE / 4;
+ Map dist = new HashMap<>();
+ Map pred = new HashMap<>();
+ for (Node n : all) dist.put(n, NEG);
+ dist.put(source, 0);
+
+ for (Node u : topo) {
+ int du = dist.getOrDefault(u, NEG);
+ if (du == NEG) continue;
+ for (Edge e : u.getOutEdges()) {
+ Node v = e.getNodeTo();
+ int w = (e.getWeight() != null) ? e.getWeight() : 1;
+ if (du + w > dist.getOrDefault(v, NEG)) {
+ dist.put(v, du + w);
+ pred.put(v, u);
+ }
+ }
+ }
+
+ if (dist.getOrDefault(sink, NEG) == NEG) return null;
+
+ LinkedList path = new LinkedList<>();
+ Node cur = sink;
+ while (cur != null) {
+ path.addFirst(cur);
+ if (cur.equals(source)) break;
+ cur = pred.get(cur);
+ }
+
+ if (path.isEmpty() || !path.getFirst().equals(source)) return null;
+ return path;
+ }
+}
--
GitLab
From ee78001e181fbdc249ae0372cc9bc58046fbf388 Mon Sep 17 00:00:00 2001
From: adjemaou
Date: Wed, 26 Nov 2025 16:30:53 +0100
Subject: [PATCH 5/5] finishibng
---
.../m1graphs2025/MaximumFlow/DotExporter.java | 17 +-
.../m1graphs2025/MaximumFlow/FlowNetwork.java | 23 ---
.../java/m1graphs2025/MaximumFlow/README.md | 160 ++++++++++++++++++
3 files changed, 175 insertions(+), 25 deletions(-)
create mode 100644 src/main/java/m1graphs2025/MaximumFlow/README.md
diff --git a/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java b/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java
index 1a21f85..5da378d 100644
--- a/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java
+++ b/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java
@@ -23,6 +23,8 @@ public class DotExporter {
try (FileWriter writer = new FileWriter(filename)) {
writer.write("digraph flow" + step + " {\n");
writer.write("\trankdir=\"LR\";\n");
+ writer.write("\t{rank = min s}\n");
+ writer.write("\t{rank = max t}\n");
writer.write("\tlabel=\"(" + step + ") Flow. Value: " + totalFlow + "\";\n");
for (Edge e : g.getAllEdges()) {
@@ -30,8 +32,9 @@ public class DotExporter {
Node v = e.getNodeTo();
int f = network.getFlow(u, v);
int c = network.getCapacity(u, v);
- writer.write("\t" + u.getId() + " -> " + v.getId()
+ writer.write("\t" + nodeIdLabel(g, u) + " -> " + nodeIdLabel(g, v)
+ " [label=\"" + f + "/" + c + "\", len=" + c + "];\n");
+
}
writer.write("}\n");
@@ -53,6 +56,8 @@ public class DotExporter {
try (FileWriter writer = new FileWriter(filename)) {
writer.write("digraph residualGraph" + step + " {\n");
writer.write("\trankdir=\"LR\";\n");
+ writer.write("\t{rank = min s}\n");
+ writer.write("\t{rank = max t}\n");
// Label : si chemin fourni, on l'affiche sinon on dit "none"
if (path != null && !path.isEmpty()) {
@@ -83,8 +88,9 @@ public class DotExporter {
}
// Écriture dans le fichier DOT
- writer.write("\t" + u.getId() + " -> " + v.getId()
+ writer.write("\t" + nodeIdLabel(g, u) + " -> " + nodeIdLabel(g, v)
+ " [label=\"" + w + "\", len=" + w + style + "];\n");
+
}
writer.write("}\n");
@@ -112,4 +118,11 @@ public class DotExporter {
sb.append("]");
return sb.toString();
}
+ // Remplace l'id du nœud par 's' ou 't' si nécessaire
+ private String nodeIdLabel(Graph g, Node n) {
+ if (n.getId() == g.smallestNodeId()) return "s";
+ if (n.getId() == g.largestNodeId()) return "t";
+ return String.valueOf(n.getId());
+ }
+
}
diff --git a/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java b/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java
index 23c681c..b0954e0 100644
--- a/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java
+++ b/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java
@@ -112,30 +112,7 @@ public class FlowNetwork {
return total;
}
- // Export du flow courant en DOT, label f/c sur chaque arête
- public void toDotFileFlow(String filename, int step) {
- int totalFlow = computeTotalFlow();
-
- try (FileWriter writer = new FileWriter(filename)) {
- writer.write("digraph flow" + step + " {\n");
- writer.write(" rankdir=\"LR\";\n");
- writer.write(" label=\"(" + step + ") Flow. Value: " + totalFlow + "\";\n");
-
- for (Edge e : baseGraph.getAllEdges()) {
- Node u = e.getNodeFrom();
- Node v = e.getNodeTo();
- int f = getFlow(u, v);
- int c = getCapacity(u, v);
- writer.write(" " + u.getId() + " -> " + v.getId()
- + " [label=\"" + f + "/" + c + "\", len=" + c + "];\n");
- }
- writer.write("}\n");
- System.out.println("Flow DOT file saved: " + filename);
- } catch (IOException ex) {
- System.err.println("[Erreur] Impossible d'écrire le fichier DOT : " + ex.getMessage());
- }
- }
// Accesseur FlowMap (utile pour inspections/export)
public FlowMap getFlowMap() {
diff --git a/src/main/java/m1graphs2025/MaximumFlow/README.md b/src/main/java/m1graphs2025/MaximumFlow/README.md
new file mode 100644
index 0000000..0cf1dd4
--- /dev/null
+++ b/src/main/java/m1graphs2025/MaximumFlow/README.md
@@ -0,0 +1,160 @@
+1. Project Overview
+
+This project implements several augmenting-path search algorithms to solve the **maximum flow problem** using the **Ford–Fulkerson method**.
+
+It relies on the graph API developed in PW2, including:
+
+- DOT file parsing,
+- `Graph`, `Node`, `Edge` structures,
+- Residual graph construction,
+- DOT export for each step.
+
+The program visualizes each step of the algorithm and allows testing multiple search strategies.
+
+---
+
+## 2. Repository Structure
+
+/src/m1graphs2025/
+pw2/ → Graph API
+MaximumFlow/
+Main.java → Program entry point
+FordFulkerson.java → Ford–Fulkerson implementation
+ResidualGraph.java → Residual graph structure
+FlowNetwork.java → Flow network structure
+FlowNetworkValidator.java
+AugmentingPathFinder.java
+BFSPathFinder.java → Edmonds–Karp
+DFSPathFinder.java → Depth-first search
+MaxBottleneckPathFinder.java → Longest-path heuristic
+DotExporter.java → DOT output generator
+
+/input/
+example_flow.dot → Example flow network
+(additional test graphs)
+
+/output/
+(generated DOT files)
+
+/report.pdf → Required report
+/README.md → This file
+
+Toujours afficher les détails
+
+
+---
+
+## 3. Compilation
+
+From the project root:
+
+### Using `javac`
+```bash
+javac -d out $(find src -name "*.java")
+
+Run the program:
+
+Toujours afficher les détails
+
+java -cp out m1graphs2025.MaximumFlow.Main
+
+4. Execution
+
+Run:
+
+Toujours afficher les détails
+
+java -cp out m1graphs2025.MaximumFlow.Main
+
+You will be asked to select the augmenting path algorithm:
+
+Toujours afficher les détails
+
+Select a search algorithm:
+1 – BFS
+2 – DFS
+3 – Bellman-Ford (recommended)
+
+Then:
+
+ The input is loaded from /input/example_flow.dot
+
+ Each step produces DOT files in /output
+
+ Output files include:
+
+ flowX.gv → current flow with labels f/c
+
+ residualGraphX.gv → residual graph with highlighted path
+
+5. Visualization
+
+DOT files are compatible with GraphViz:
+
+Toujours afficher les détails
+
+dot -Tpng output/flow1.gv -o flow1.png
+
+The generated graph shows:
+
+ Source as s and sink as t
+
+ Edges labeled as f/c
+
+ Augmenting paths highlighted in blue
+
+6. Implemented Algorithms
+✔ 1. BFSPathFinder (Edmonds–Karp)
+
+ Complexity: O(V E²)
+
+ Very stable and recommended.
+
+✔ 2. DFSPathFinder
+
+ May converge quickly or get stuck.
+
+ Behavior depends on edge ordering.
+
+✔ 3. MaxBottleneckPathFinder (Heuristic)
+
+ Finds an augmenting path maximizing the minimum residual capacity.
+
+ Inspired by "longest path in DAG" dynamic programming.
+
+ Often reduces the number of iterations drastically.
+
+Algorithm Complexity Notes
+BFS (EK) O(VE²) stable, recommended
+DFS unbounded risky but fast on some graphs
+MaxBottleneck O(VE) per iteration very efficient on structured networks
+7. Test Graphs
+
+You should include several .dot graphs in /input, such as:
+
+ simple flow networks
+
+ networks with cycles
+
+ networks with multiple bottlenecks
+
+ large networks (10+ nodes)
+
+ one-path-only networks
+
+8. Notes
+
+ /output is automatically cleared before each execution.
+
+ Source and sink are printed as s and t.
+
+ DOT outputs follow the formatting expected by the assignment.
+
+9. Author
+
+Project developed by Djemaoui Ahmed,
+Master 1 Computer Science — Université de Franche-Comté.
+"""
+
+path = Path("/mnt/data/README.md")
+path.write_text(content, encoding="utf-8")
--
GitLab