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