From 295507685bc1d4d8fd84778eecaf61a1921b9f36 Mon Sep 17 00:00:00 2001 From: dcisse2 Date: Thu, 23 Oct 2025 17:52:15 +0200 Subject: [PATCH 1/9] [UPDATE] --- .idea/vcs.xml | 1 - m1graphs25 | 1 - 2 files changed, 2 deletions(-) delete mode 160000 m1graphs25 diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 95c2cb1..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/m1graphs25 b/m1graphs25 deleted file mode 160000 index 3baf9d1..0000000 --- a/m1graphs25 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3baf9d19e40b5b9ef6e31f7deb15413679dcf68e -- GitLab From 9b2df9e5b8837c9cbadc64dddae8d2b64aa35c45 Mon Sep 17 00:00:00 2001 From: dcisse2 Date: Thu, 30 Oct 2025 00:42:15 +0100 Subject: [PATCH 2/9] [UPDATE] --- src/main/java/m1graphs2025/Edge.java | 2 +- src/main/java/m1graphs2025/Graph.java | 99 ++++++--- src/main/java/m1graphs2025/TestGraphsPW2.java | 10 +- .../java/m1graphs2025/UndirectedGraph.java | 196 +++++++++++++++--- src/main/resources/isolatedNodes.gv | 8 + src/main/resources/multiGraph.gv | 18 ++ src/main/resources/simpleGraph.gv | 12 ++ .../undirWeightedMultiGraph.gv | 0 src/main/resources/weightedMultiGraph.gv | 18 ++ src/main/resources/weightedSimpleGraph.gv | 12 ++ .../java/m1graphs2025/TestGraphPart1.java | 183 ++++++++++++++++ .../java/m1graphs2025/TestGraphPart2.java | 58 ++++++ .../java/m1graphs2025/TestGraphPart3.java | 50 +++++ .../java/m1graphs2025/TestGraphPart4.java | 111 ++++++++++ .../java/m1graphs2025/TestGraphPart5.java | 71 +++++++ 15 files changed, 780 insertions(+), 68 deletions(-) create mode 100644 src/main/resources/isolatedNodes.gv create mode 100644 src/main/resources/multiGraph.gv create mode 100644 src/main/resources/simpleGraph.gv rename src/main/{java/m1graphs2025 => resources}/undirWeightedMultiGraph.gv (100%) create mode 100644 src/main/resources/weightedMultiGraph.gv create mode 100644 src/main/resources/weightedSimpleGraph.gv create mode 100644 src/test/java/m1graphs2025/TestGraphPart1.java create mode 100644 src/test/java/m1graphs2025/TestGraphPart2.java create mode 100644 src/test/java/m1graphs2025/TestGraphPart3.java create mode 100644 src/test/java/m1graphs2025/TestGraphPart4.java create mode 100644 src/test/java/m1graphs2025/TestGraphPart5.java diff --git a/src/main/java/m1graphs2025/Edge.java b/src/main/java/m1graphs2025/Edge.java index 14ef5e0..0527c3d 100644 --- a/src/main/java/m1graphs2025/Edge.java +++ b/src/main/java/m1graphs2025/Edge.java @@ -134,7 +134,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 * au même graphe. */ diff --git a/src/main/java/m1graphs2025/Graph.java b/src/main/java/m1graphs2025/Graph.java index f565262..614a37e 100644 --- a/src/main/java/m1graphs2025/Graph.java +++ b/src/main/java/m1graphs2025/Graph.java @@ -928,7 +928,7 @@ public class Graph { // ====================== public List getDFSWithVisitInfo(Map nodeVisit, - Map edgeVisit) { + Map edgeVisit) { List visited = new ArrayList<>(); AtomicInteger time = new AtomicInteger(0); initVisitInfo(nodeVisit); @@ -940,8 +940,8 @@ public class Graph { } public List getDFSWithVisitInfo(Node start, - Map nodeVisit, - Map edgeVisit) { + Map nodeVisit, + Map edgeVisit) { List visited = new ArrayList<>(); AtomicInteger time = new AtomicInteger(0); initVisitInfo(nodeVisit); @@ -950,9 +950,9 @@ public class Graph { } private void DFSVisitEnriched(Node u, AtomicInteger time, - Map nodeVisit, - Map edgeVisit, - List visited) { + Map nodeVisit, + Map edgeVisit, + List visited) { time.incrementAndGet(); nodeVisit.get(u).setDiscoveryTime(time.get()); nodeVisit.get(u).setColour(NodeColour.GRAY); @@ -984,49 +984,86 @@ public class Graph { // ====================== public static Graph fromDotFile(String filename) { - BufferedReader dot = new BufferedReader(new FileReader(filename)); Map> adjEdList = new HashMap<>(); Graph g = new Graph(adjEdList); - BufferedReader dot = new BufferedReader(new FileReader(filename)); - List nodes = new ArrayList<>(); - - String line = dot.readLine(); - while (line != null) { - int iteration = 0; - Edge edge = new Edge(); - Node nFrom = new Node(); - Node nTo = new Node(); - String[] tokens = line.split("[^0-9]"); - if (!tokens[0].equals("")) { - if (!nodes.contains(Integer.parseInt(tokens[0]))) { - List listEdges = new ArrayList<>(); - } - nFrom.setId(Integer.parseInt(tokens[0])); - edge.setNodeFrom(nFrom); - nTo.setId(Integer.parseInt(tokens[1])); - edge.setNodeTo(nTo); - if (tokens[2] != "") { - edge.setWeight(Integer.parseInt(tokens[2])); + + try (BufferedReader dot = new BufferedReader(new FileReader(filename))) { + String line; + + while ((line = dot.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) continue; // ignore lignes vides + + // Cas 1 : Ligne contenant seulement un numéro → nœud isolé + if (line.matches("^\\d+$")) { + int id = Integer.parseInt(line); + Node isolated = new Node(id, g, ""); + adjEdList.putIfAbsent(isolated, 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 + + int fromId = Integer.parseInt(tokens[0]); + int toId = Integer.parseInt(tokens[1]); + int weight = Integer.parseInt(tokens[2]); + Node nFrom = new Node(fromId, g, ""); + Node nTo = new Node(toId, g, ""); + Edge edge = new Edge(nFrom, nTo, weight); + + // Ajout de l’arête dans la liste d’adjacence + adjEdList.computeIfAbsent(nFrom, k -> new ArrayList<>()).add(edge); + + // S’assurer que le nœud destination existe + adjEdList.putIfAbsent(nTo, new ArrayList<>()); + } + } catch (IOException e) { + throw new RuntimeException("Erreur lors de la lecture du fichier : " + filename, e); } + + return g; } + public String toDotString() { StringBuilder sb = new StringBuilder(); sb.append("digraph 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(e.getNodeFrom().getId()).append(" -> ").append(e.getNodeTo().getId()); + 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(" [label=").append(e.getWeight()) + .append(", len=").append(e.getWeight()).append("]"); } - sb.append("\n"); + 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(); } diff --git a/src/main/java/m1graphs2025/TestGraphsPW2.java b/src/main/java/m1graphs2025/TestGraphsPW2.java index 6f9fb9b..96a9d7c 100644 --- a/src/main/java/m1graphs2025/TestGraphsPW2.java +++ b/src/main/java/m1graphs2025/TestGraphsPW2.java @@ -1,4 +1,4 @@ -package m1graphs2025; +/**package m1graphs2025; import java.util.Arrays; import java.util.Collections; @@ -407,8 +407,8 @@ public class TestGraphsPW2 { * ); * System.out.println( * "*-----------------------------------------------------------------------*"); - * - * + * + * * // lecture example but undirected * UndirectedGraph gToVisit = new UndirectedGraph( * 2, 3, 4, 6, 0, //1 @@ -424,6 +424,6 @@ public class TestGraphsPW2 { * System.out.println(gToVisit.toDotString()); */ - } + /**} -} +}**/ diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java index 595e22f..45ac02e 100644 --- a/src/main/java/m1graphs2025/UndirectedGraph.java +++ b/src/main/java/m1graphs2025/UndirectedGraph.java @@ -1,5 +1,11 @@ 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.*; /** @@ -20,12 +26,60 @@ public class UndirectedGraph extends Graph { } public UndirectedGraph(int... successorArray) { - super(successorArray); - // Ici, successorArray est interprété comme orienté, - // donc on doit symétriser après construction - symmetrize(); + 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); @@ -55,39 +109,32 @@ public class UndirectedGraph extends Graph { @Override public void addEdge(Node from, Node to) { - super.addEdge(from, to); - // Ajout symétrique - if (!existsEdge(to, from)) - super.addEdge(to, from); - } - - @Override - public void addEdge(int fromId, int toId) { - super.addEdge(fromId, toId); - if (!existsEdge(toId, fromId)) - super.addEdge(toId, fromId); + 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); - if (!existsEdge(to, from)) - super.addEdge(to, from, weight); + super.addEdge(to, from, weight); } @Override - public void addEdge(int fromId, int toId, int weight) { - super.addEdge(fromId, toId, weight); - if (!existsEdge(toId, fromId)) - super.addEdge(toId, fromId, weight); + 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(Edge e) { - super.addEdge(e); - Edge sym = e.getSymmetric(); - if (sym != null && existsEdge(sym)) - super.addEdge(sym); + 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 @@ -114,13 +161,12 @@ public class UndirectedGraph extends Graph { @Override public boolean existsEdge(Node u, Node v) { - // Pour un graphe non orienté, (u,v) == (v,u) return super.existsEdge(u, v) || super.existsEdge(v, u); } @Override - public boolean existsEdge(Edge e) { - return super.existsEdge(e); + public boolean existsEdge(int uId, int vId) { + return existsEdge(getNode(uId), getNode(vId)); } @Override @@ -159,8 +205,96 @@ public class UndirectedGraph extends Graph { return closure; } - public static UndirectedGraph fromDotFile(String filename) { - return fromDotFile(filename, ".gv"); + public static Graph fromDotFile(String filename) { + Map> adjEdList = new HashMap<>(); + Graph g = new UndirectedGraph(adjEdList); + + try (BufferedReader dot = new BufferedReader(new FileReader(filename))) { + String line; + + while ((line = dot.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) continue; // ignore lignes vides + + // Cas 1 : Ligne contenant seulement un numéro → nœud isolé + if (line.matches("^\\d+$")) { + int id = Integer.parseInt(line); + Node isolated = new Node(id, g, ""); + adjEdList.putIfAbsent(isolated, 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 + + int fromId = Integer.parseInt(tokens[0]); + int toId = Integer.parseInt(tokens[1]); + int weight = Integer.parseInt(tokens[2]); + + Node nFrom = new Node(fromId, g, ""); + Node nTo = new Node(toId, g, ""); + Edge edge = new Edge(nFrom, nTo, weight); + + // Ajout de l’arête dans la liste d’adjacence + adjEdList.computeIfAbsent(nFrom, k -> new ArrayList<>()).add(edge); + + // S’assurer que le nœud destination existe + adjEdList.putIfAbsent(nTo, new ArrayList<>()); + } + } catch (IOException e) { + throw new RuntimeException("Erreur lors de la lecture du fichier : " + filename, e); + } + + return g; + } + + 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"); } // ====================== diff --git a/src/main/resources/isolatedNodes.gv b/src/main/resources/isolatedNodes.gv new file mode 100644 index 0000000..221faf2 --- /dev/null +++ b/src/main/resources/isolatedNodes.gv @@ -0,0 +1,8 @@ +digraph { + rankdir=LR + 1 + 2 -> 3 + 4 + 5 + 8 +} \ No newline at end of file diff --git a/src/main/resources/multiGraph.gv b/src/main/resources/multiGraph.gv new file mode 100644 index 0000000..d4bec97 --- /dev/null +++ b/src/main/resources/multiGraph.gv @@ -0,0 +1,18 @@ +digraph { + rankdir=LR + 1 -> 2 + 2 -> 3 + 2 -> 4 + 2 -> 4 + 2 -> 5 + 3 -> 1 + 3 -> 1 + 3 -> 5 + 4 -> 1 + 4 -> 4 + 4 -> 5 + 6 -> 5 + 6 -> 5 + 6 -> 6 + 6 -> 6 +} \ No newline at end of file diff --git a/src/main/resources/simpleGraph.gv b/src/main/resources/simpleGraph.gv new file mode 100644 index 0000000..9658c6d --- /dev/null +++ b/src/main/resources/simpleGraph.gv @@ -0,0 +1,12 @@ +digraph { + rankdir=LR + 1 -> 2 + 2 -> 3 + 2 -> 4 + 2 -> 5 + 3 -> 1 + 3 -> 5 + 4 -> 1 + 4 -> 5 + 6 -> 5 +} \ No newline at end of file diff --git a/src/main/java/m1graphs2025/undirWeightedMultiGraph.gv b/src/main/resources/undirWeightedMultiGraph.gv similarity index 100% rename from src/main/java/m1graphs2025/undirWeightedMultiGraph.gv rename to src/main/resources/undirWeightedMultiGraph.gv diff --git a/src/main/resources/weightedMultiGraph.gv b/src/main/resources/weightedMultiGraph.gv new file mode 100644 index 0000000..5ef48fd --- /dev/null +++ b/src/main/resources/weightedMultiGraph.gv @@ -0,0 +1,18 @@ +digraph { + rankdir=LR + 1 -> 2 [label=4, len=4] + 2 -> 3 [label=1, len=1] + 2 -> 4 [label=2, len=2] + 2 -> 4 [label=50, len=50] + 2 -> 5 [label=3, len=3] + 3 -> 1 [label=3, len=3] + 3 -> 1 [label=6, len=6] + 3 -> 5 [label=2, len=2] + 4 -> 1 [label=0, len=0] + 4 -> 4 [label=14, len=14] + 4 -> 5 [label=2, len=2] + 6 -> 5 [label=7, len=7] + 6 -> 5 [label=10, len=10] + 6 -> 6 [label=5, len=5] + 6 -> 6 [label=8, len=8] +} \ No newline at end of file diff --git a/src/main/resources/weightedSimpleGraph.gv b/src/main/resources/weightedSimpleGraph.gv new file mode 100644 index 0000000..c305506 --- /dev/null +++ b/src/main/resources/weightedSimpleGraph.gv @@ -0,0 +1,12 @@ +digraph { + rankdir=LR + 1 -> 2 [label=4, len=4] + 2 -> 3 [label=1, len=1] + 2 -> 4 [label=2, len=2] + 2 -> 5 [label=3, len=3] + 3 -> 1 [label=6, len=6] + 3 -> 5 [label=2, len=2] + 4 -> 1 [label=0, len=0] + 4 -> 5 [label=2, len=2] + 6 -> 5 [label=7, len=7] +} \ No newline at end of file diff --git a/src/test/java/m1graphs2025/TestGraphPart1.java b/src/test/java/m1graphs2025/TestGraphPart1.java new file mode 100644 index 0000000..b2d8bb0 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart1.java @@ -0,0 +1,183 @@ +package m1graphs2025; + +import java.util.Collections; +import java.util.List; + +public class TestGraphPart1 { + + public static void main(String[] args) { + System.out.println("*--------------------------------------------------------------------*"); + System.out.println("************ PART 1. UNWEIGTED DIRECTED GRAPHS ***********************"); + System.out.println("*--------------------------------------------------------------------*"); + System.out.println("\n>>>>>>>> SIMPLE GRAPH >>>>>>>>>>>>>>>>>>>>>>>>"); + System.out.println(">>>>>>>> Creating the subject example graph in G"); + Graph g = new Graph(2, 4, 0, 0, 6, 0, 2, 3, 5, 8, 0, 0, 4, 7, 0, 3, 0, 7, 0); + System.out.println(">>>> Graph information"); + System.out.println(">> DOT representation\n" + g.toDotString()); + System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + System.out.println(">> Nodes: "); + List nodes = g.getAllNodes(); + Collections.sort(nodes); + for (Node n : nodes) + System.out.println("Node " + n + ": degree " + g.degree(n) + " (in: " + g.inDegree(n) + "/ out: " + + g.outDegree(n) + ")"); + + List edges; + System.out.println(">> Edges: "); + System.out.println("---------------------------"); + System.out.println("Out-edges per node"); + for (Node n : nodes) { + edges = g.getOutEdges(n); + Collections.sort(edges); + System.out.println("" + n + ": " + edges); + } + + System.out.println("In-edges per node"); + for (Node n : nodes) { + edges = g.getInEdges(n); + Collections.sort(edges); + System.out.println("" + n + ": " + edges); + } + + ///////////////////////////////////////////////////// + + System.out.println("\n>>>>>>>> creating isolated node 12"); + if (!g.addNode(12)) + System.out.println("Error: failed to create node 12"); + System.out.println("Graph now:"); + System.out.println(g.toDotString()); + System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + nodes = g.getAllNodes(); + Collections.sort(nodes); + System.out.println("Nodes list: " + nodes); + + System.out.println("\n>>>>>>>> Removing node 3"); + g.removeNode(3); + System.out.println("Graph now:"); + System.out.println(g.toDotString()); + System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + nodes = g.getAllNodes(); + Collections.sort(nodes); + System.out.println("Nodes list: " + nodes); + + System.out.println(">> Edges: "); + System.out.println("---------------------------"); + System.out.println("Out-edges per node"); + for (Node n : nodes) { + edges = g.getOutEdges(n); + Collections.sort(edges); + System.out.println("" + n + ": " + edges); + } + + System.out.println("In-edges per node"); + for (Node n : nodes) { + edges = g.getInEdges(n); + Collections.sort(edges); + System.out.println("" + n + ": " + edges); + } + + System.out.println( + "\n>>>>>>>> Recreating edges (4, 3), (3, 6), (7, 3), adding edge (12, 3), creating edge (3, 25)"); + g.addEdge(new Edge(4, 3, g)); + g.addEdge(new Edge(3, 6, g)); + g.addEdge(new Edge(7, 3, g)); + g.addEdge(new Edge(12, 3, g)); + g.addEdge(3, 25); + System.out.println("Graph now:"); + System.out.println(g.toDotString()); + System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + nodes = g.getAllNodes(); + Collections.sort(nodes); + System.out.println("Nodes list: " + nodes); + + System.out.println(""); + System.out.println("\n>>>>>>>> Edges removal"); + System.out.println(">>>> Removing existing edges (7, 3) and (4, 8)"); + g.removeEdge(7, 3); + g.removeEdge(4, 8); + System.out.println(">>>> Removing absent edge (3, 4)"); + g.removeEdge(3, 4); + System.out.println( + ">>>> Removing edges whith 1 or 2 not existing end-points: (-3, 4), (6, 0), (4, 11), (-1, -2), (13, 3), (9, 10)"); + g.removeEdge(-3, 4); + g.removeEdge(6, 0); + g.removeEdge(4, 11); + g.removeEdge(-1, -2); + g.removeEdge(13, 3); + g.removeEdge(9, 10); + + System.out.println("Graph now:"); + System.out.println(g.toDotString()); + System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + nodes = g.getAllNodes(); + Collections.sort(nodes); + System.out.println("Nodes list: " + nodes); + + System.out.println( + "\nTesting that getSuccessors and getSuccessorsMulti give the same result for the simple graph:"); + boolean same = true; + for (Node u : nodes) { + List succs = g.getSuccessors(u), succsMulti = g.getSuccessorsMulti(u); + // sort the lists so that nodes always appear in the same order + Collections.sort(succs); + Collections.sort(succsMulti); + same = same && succs.equals(succsMulti); + } + System.out.println("\tgetSuccessors and getSuccessorsMulti " + (same ? "are identical" : "differ")); + + System.out.println("\n>>>>>>>> MULTIGRAPH: adding a self-loop on node 6, and a second edge (1, 4)"); + g.addEdge(6, 6); + g.addEdge(1, 4); + System.out.println("Graph now:"); + System.out.println(g.toDotString()); + System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + nodes = g.getAllNodes(); + Collections.sort(nodes); + System.out.println("Nodes list: " + nodes); + System.out.println( + "Degree of node 6: " + g.degree(6) + " (in: " + g.inDegree(6) + "/ out: " + g.outDegree(6) + ")"); + + System.out.println(">> Edges: "); + System.out.println("---------------------------"); + System.out.println("Out-edges per node"); + for (Node n : nodes) { + edges = g.getOutEdges(n); + Collections.sort(edges); + System.out.println("" + n + ": " + edges); + } + + System.out.println("In-edges per node"); + for (Node n : nodes) { + edges = g.getInEdges(n); + Collections.sort(edges); + System.out.println("" + n + ": " + edges); + } + + System.out.println("\n>>>>>>>>>> Get the reverse graph"); + System.out.println(g.getReverse().toDotString()); + + System.out.println(">>>>>>>>>> Get the transitive closure"); + System.out.println(g.getTransitiveClosure().toDotString()); + + System.out.println(">>>>>>>>>> Emptying the graph by removing all its nodes"); + nodes = g.getAllNodes(); + for (Node u : nodes) + g.removeNode(u); + System.out.println("Graph now:"); + System.out.println(g.toDotString()); + + System.out.println(">>>> Searching for node 7:"); + if (g.usesNode(7)) + System.out.println("\tNode 7 exists"); + else + System.out.println("\tThere is no Node 7"); + + System.out.println(">>>> Searching for edge (4, 2):"); + if (g.existsEdge(4, 2)) + System.out.println("\tEdge (4, 2) exists"); + else + System.out.println("\tThere is no edge (4, 2)"); + + System.out.println(); + } +} diff --git a/src/test/java/m1graphs2025/TestGraphPart2.java b/src/test/java/m1graphs2025/TestGraphPart2.java new file mode 100644 index 0000000..d764977 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart2.java @@ -0,0 +1,58 @@ +package m1graphs2025; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class TestGraphPart2 { + + public static void main(String[] args) { + System.out.println("*------------------------------------------------------------------*"); + System.out.println("********* PART 2. READING GRAPHS FROM DOT FILES ****************"); + System.out.println("*------------------------------------------------------------------*"); + System.out.println("\n>>> Graph with isolated nodes: reading file 'isolatedNodes.gv'"); + Graph gin = Graph.fromDotFile("src/main/resources/isolatedNodes.gv"); + if (gin == null) + System.out.println("Null graph was created from 'isolatedNodes.gv'"); + else { + System.out.println("Read: OK. The graph with isolated nodes has been read as:"); + System.out.println("---------------------"); + System.out.println(gin.toDotString()); + System.out.println("---------------------"); + } + + System.out.println(">>> Simple graph: reading file 'simpleGraph.gv'"); + Graph sg = Graph.fromDotFile("src/main/resources/simpleGraph.gv"); + if (sg == null) + System.out.println("Null graph was created from 'simpleGraph.gv'"); + else { + System.out.println("Read: OK. The simple graph has been read as:"); + System.out.println("---------------------"); + System.out.println(sg.toDotString()); + System.out.println("---------------------"); + } + + System.out.println("\n>>> Multi-graph: reading file 'multiGraph.gv'"); + Graph mg = Graph.fromDotFile("src/main/resources/multiGraph.gv"); + if (mg == null) + System.out.println("Null graph was created from 'multiGraph.gv'"); + else { + System.out.println("Read: OK. The multi-graph has been read as:"); + System.out.println("---------------------"); + System.out.println(mg.toDotString()); + System.out.println("---------------------"); + } + + System.out.println("Comparing single and multi successors per node for 'multiGraph.gv'"); + for (Node u : mg.getAllNodes()) { + List succs = mg.getSuccessors(u), succsMulti = mg.getSuccessorsMulti(u); + // sort the lists so that nodes always appear in the same order + Collections.sort(succs); + Collections.sort(succsMulti); + System.out.println("" + u + " single successors: " + succs); + System.out.println("" + u + " multi successors: " + succsMulti); + } + + System.out.println("\n\n"); + } +} diff --git a/src/test/java/m1graphs2025/TestGraphPart3.java b/src/test/java/m1graphs2025/TestGraphPart3.java new file mode 100644 index 0000000..84ed56a --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart3.java @@ -0,0 +1,50 @@ +package m1graphs2025; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class TestGraphPart3 { + + public static void main(String[] args) { + System.out.println("*----------------------------------------------------------------------*"); + System.out.println("************* PART 3. WEIGHTED DIRECTED GRAPHS ***********************"); + System.out.println("*----------------------------------------------------------------------*"); + int totalEdgesWeight = 0; + + System.out.println("\n>>>>>>>>>>"); + System.out.println("Reading a weighted directed simple graph from DOT file 'weightedSimpleGraph.gv'"); + Graph wsg = Graph.fromDotFile("src/main/resources/weightedSimpleGraph.gv"); + if (wsg == null) + System.out.println("Null graph was created from 'weightedSimpleGraph.gv'"); + else { + System.out.println("Read: OK. The weighted directed simple graph has been read as:"); + System.out.println("---------------------"); + System.out.println(wsg.toDotString()); + System.out.println("---------------------"); + + for (Edge e : wsg.getAllEdges()) + totalEdgesWeight += e.getWeight(); + System.out.println("The sum of all edges weights equals " + totalEdgesWeight); + } + + System.out.println("\n>>>>>>>>>>"); + System.out.println("Reading a weighted directed multi graph from DOT file 'weightedMultiGraph.gv'"); + Graph wmg = Graph.fromDotFile("src/main/resources/weightedMultiGraph.gv"); + if (wmg == null) + System.out.println("Null graph was created from 'weightedMultiGraph.gv'"); + else { + System.out.println("Read: OK. The weighted directed multi graph has been read as:"); + System.out.println("---------------------"); + System.out.println(wmg.toDotString()); + System.out.println("---------------------"); + + totalEdgesWeight = 0; + for (Edge e : wmg.getAllEdges()) + totalEdgesWeight += e.getWeight(); + System.out.println("The sum of all edges weights equals " + totalEdgesWeight); + } + + System.out.println("\n\n"); + } +} diff --git a/src/test/java/m1graphs2025/TestGraphPart4.java b/src/test/java/m1graphs2025/TestGraphPart4.java new file mode 100644 index 0000000..7200435 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart4.java @@ -0,0 +1,111 @@ +package m1graphs2025; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class TestGraphPart4 { + + public static void main(String[] args) { + + System.out.println("*-------------------------------------------------------------------------*"); + System.out.println("************ PART 4. UNDIRECTED UNWEIGHTED GRAPHS ***********************"); + System.out.println("*-------------------------------------------------------------------------*"); + + System.out.println("\nCreating an undirected simple graph 'usg' from scracth"); + UndirectedGraph usg = new UndirectedGraph(2, 3, 0, 3, 4, 0, 4, 0, 0); + System.out.println(usg.toDotString()); + System.out.println("usg has " + usg.nbNodes() + " nodes and " + usg.nbEdges() + " edges."); + + System.out.println("\n>>>>>> usg: Counting degrees and showing successors"); + for (Node u : usg.getAllNodes()) { + System.out.println("Node " + u + ". Degree: " + usg.degree(u.getId()) + " (In: " + usg.inDegree(u.getId()) + + " / Out: " + usg.outDegree(u.getId()) + ")"); + System.out.println("\tSuccessors: " + usg.getSuccessors(u)); + } + + System.out.println(">>>>>> usg: Edges of the graph"); + System.out.println("// N.B. The edges are printed as though they were directed. This is due to the toString()\n" + + "// method that was not overridden. It is possible to do better but not important.\n" + + "// What is important is that each edge appears only once per direction."); + System.out.println("All edges of the graph: " + usg.getAllEdges()); + System.out.println("Out-edges per node"); + for (Node u : usg.getAllNodes()) + System.out.println("" + u + ": " + usg.getOutEdges(u)); + System.out.println("In-edges per node"); + for (Node u : usg.getAllNodes()) + System.out.println("" + u + ": " + usg.getInEdges(u)); + System.out.println("Incident edges per node"); + for (Node u : usg.getAllNodes()) + System.out.println("" + u + ": " + usg.getIncidentEdges(u)); + + System.out.println("Creating an undirected multi-graph with self-loops 'umg' from scratch"); + UndirectedGraph umg = new UndirectedGraph(1, 1, 2, 2, 3, 0, 2, 3, 0, 0); + + String dotUMG = umg.toDotString(); + System.out.println(dotUMG); + System.out.println("umg has " + umg.nbNodes() + " nodes and " + umg.nbEdges() + " edges."); + + System.out.println("\n>>>>>> umg: Counting degrees and showing successors"); + for (Node u : umg.getAllNodes()) { + System.out.println("Node " + u + ". Degree: " + umg.degree(u.getId()) + " (In: " + umg.inDegree(u.getId()) + + " / Out: " + umg.outDegree(u.getId()) + ")"); + System.out.println("\tSuccessors: " + umg.getSuccessors(u)); + } + + System.out.println(">>>>>> umg: Edges of the graph"); + System.out.println("All edges of the graph: " + umg.getAllEdges()); + System.out.println("Out-edges per node"); + for (Node u : umg.getAllNodes()) + System.out.println("" + u + ": " + umg.getOutEdges(u)); + System.out.println("In-edges per node"); + for (Node u : umg.getAllNodes()) + System.out.println("" + u + ": " + umg.getInEdges(u)); + System.out.println("Incident edges per node"); + for (Node u : umg.getAllNodes()) + System.out.println("" + u + ": " + umg.getIncidentEdges(u)); + + System.out.println("\n>>>>>> umg: Successor Array, Adjacency Matrix, and Graph Reverse"); + System.out.println("umg Successor array\n" + Arrays.toString(umg.toSuccessorArray())); + + System.out.println("umg Adjacency Matrix"); + for (int[] row : umg.toAdjMatrix()) + System.out.println("\t" + Arrays.toString(row)); + + System.out.println("Testing via toDotString() the equality with the reverse graph"); + String dotRUMG = umg.getReverse().toDotString(); + System.out.println("DOT of the reverse of umg\n" + dotRUMG); + System.out.println("Graph gu and its reverse " + (dotUMG.equals(dotRUMG) ? "are identical" : "differ")); + + System.out.println("-----------------\n NOW a disconnected GRAPH \n----------------"); + System.out.println("Building 'guDisc', a disconnected undirected graph with multi-edges and self-loops"); + UndirectedGraph guDisc = new UndirectedGraph(1, 1, 2, 2, 6, 0, 2, 3, 6, 0, 0, 6, 0, 6, 0, 0, 0, 9, 10, 0, 0, 0); + System.out.println(guDisc.toDotString()); + + // delete + // Graph guDisc2 = new Graph(1,1,2,2,6,0, 2,3,6,0, 0, 6,0, 6,0, 0, 0, 9,10,0, 0, + // 0); + // System.out.println(guDisc2.toDotString()); + // System.exit(0); + // end delete + + System.out.println("Comparing single and multi successors per node for guDisc"); + for (Node u : guDisc.getAllNodes()) { + List succs = guDisc.getSuccessors(u), succsMulti = guDisc.getSuccessorsMulti(u); + // sort the lists so that nodes always appear in the same order + Collections.sort(succs); + Collections.sort(succsMulti); + System.out.println("" + u + " single successors: " + succs); + System.out.println("" + u + " multi successors: " + succsMulti); + } + + System.out.println(">>>> DFS of guDisc: " + guDisc.getDFS()); + System.out.println(">>>> BFS of guDisc: " + guDisc.getBFS()); + + System.out.println(">>>>>>> Computing guDisc's transitive closure"); + UndirectedGraph guDiscTC = (UndirectedGraph) guDisc.getTransitiveClosure(); + System.out.println(guDiscTC.toDotString()); + + System.out.println("\n\n"); + } +} diff --git a/src/test/java/m1graphs2025/TestGraphPart5.java b/src/test/java/m1graphs2025/TestGraphPart5.java new file mode 100644 index 0000000..c9e4468 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart5.java @@ -0,0 +1,71 @@ +package m1graphs2025; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +public class TestGraphPart5 { + + public static void main(String[] args) { + + System.out.println("*-----------------------------------------------------------------------*"); + System.out.println("************ PART 5. UNDIRECTED WEIGHTED GRAPHS ***********************"); + System.out.println("*-----------------------------------------------------------------------*"); + + System.out.println("\n>>>>>> Reading 'uwmg' an undirected weighted multi-graph with self loops\n" + + "from file 'undirWeightedMultiGraph.gv'"); + + UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("src/main/resources/undirWeightedMultiGraph.gv"); + if (uwmg == null) + System.out.println("Null graph was created from 'undirWeightedMultiGraph.gv'"); + else { + System.out.println("Read: OK. The undirected weighted multi-graph has been read as:"); + System.out.println("---------------------"); + System.out.println(uwmg.toDotString()); + System.out.println("---------------------"); + + Integer totalEdgesWeight = 0; + for (Edge e : uwmg.getAllEdges()) + totalEdgesWeight += e.getWeight(); + System.out.println("The sum of all edges weights equals " + totalEdgesWeight); + } + + System.out.println("\nComparing single and multi successors per node for uwmg"); + for (Node u : uwmg.getAllNodes()) { + List succs = uwmg.getSuccessors(u), succsMulti = uwmg.getSuccessorsMulti(u); + // sort the lists so that nodes always appear in the same order + Collections.sort(succs); + Collections.sort(succsMulti); + System.out.println("" + u + " single successors: " + succs); + System.out.println("" + u + " multi successors: " + succsMulti); + } + + /* + * TO BE CONTINUED ... + * System.out.println("\n\n"); + * System.out.println( + * "*-----------------------------------------------------------------------*"); + * System.out. + * println("************ PART 6. DFS and Node Visit Info ***********************" + * ); + * System.out.println( + * "*-----------------------------------------------------------------------*"); + * + * + * // lecture example but undirected + * UndirectedGraph gToVisit = new UndirectedGraph( + * 2, 3, 4, 6, 0, //1 + * 5, 6, 0, //2 + * 4, 0, //3 + * 8, 0, //4 + * 6, 7, 8, 0, //5 + * 7, 0, //6 + * 8,0, //7 + * 0 //8 + * ); + * System.out.println("\nAn undirected graph to visit:"); + * System.out.println(gToVisit.toDotString()); + */ + + } +} -- GitLab From bf6646e2568cd77bd0ed40e9192c9b0126eda6ae Mon Sep 17 00:00:00 2001 From: adjemaou Date: Fri, 31 Oct 2025 22:05:04 +0100 Subject: [PATCH 3/9] fixing part2 --- src/main/java/m1graphs2025/Graph.java | 165 ++++++++++++------ .../java/m1graphs2025/UndirectedGraph.java | 6 +- 2 files changed, 111 insertions(+), 60 deletions(-) diff --git a/src/main/java/m1graphs2025/Graph.java b/src/main/java/m1graphs2025/Graph.java index 614a37e..a4bf39c 100644 --- a/src/main/java/m1graphs2025/Graph.java +++ b/src/main/java/m1graphs2025/Graph.java @@ -101,10 +101,14 @@ public class Graph { if (entry != null && entry.getId() == id) return entry; } - System.err.println("Aucun nœud trouvé avec l’id " + id + "."); + return null; } + public Node getOrCreateNode(int id) { + return getNode(id) != null ? getNode(id) : new Node(id, this); + } + public boolean addNode(Node n) { if (n == null) { System.err.println("Erreur: tentative d’ajout d’un nœud null."); @@ -928,7 +932,7 @@ public class Graph { // ====================== public List getDFSWithVisitInfo(Map nodeVisit, - Map edgeVisit) { + Map edgeVisit) { List visited = new ArrayList<>(); AtomicInteger time = new AtomicInteger(0); initVisitInfo(nodeVisit); @@ -940,8 +944,8 @@ public class Graph { } public List getDFSWithVisitInfo(Node start, - Map nodeVisit, - Map edgeVisit) { + Map nodeVisit, + Map edgeVisit) { List visited = new ArrayList<>(); AtomicInteger time = new AtomicInteger(0); initVisitInfo(nodeVisit); @@ -950,9 +954,9 @@ public class Graph { } private void DFSVisitEnriched(Node u, AtomicInteger time, - Map nodeVisit, - Map edgeVisit, - List visited) { + Map nodeVisit, + Map edgeVisit, + List visited) { time.incrementAndGet(); nodeVisit.get(u).setDiscoveryTime(time.get()); nodeVisit.get(u).setColour(NodeColour.GRAY); @@ -983,88 +987,135 @@ public class Graph { // 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: + * 1 -> 2 [label="5"]; + * 3 + * (isolated node) + * + * @param filename the path to the DOT file + * @return a Graph instance representing the file content + * @throws RuntimeException if file reading fails + */ public static Graph fromDotFile(String filename) { Map> adjEdList = new HashMap<>(); Graph g = new Graph(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 a directed graph: + * + *

+     * digraph G {
+     *     rankdir=LR;
+     *     1 -> 2 [label="5"];
+     *     2 -> 3;
+     *     4;
+     * }
+     * 
+ *

+ * + * @return the DOT-format representation of the graph + */ public String toDotString() { + boolean isDirected = true; // adapt this if you support undirected graphs + String arrow = isDirected ? " -> " : " -- "; + StringBuilder sb = new StringBuilder(); - sb.append("digraph 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"); - } + sb.append(isDirected ? "digraph {\n" : "graph {\n"); + sb.append(" rankdir=LR;\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 (Map.Entry> entry : adjEdList.entrySet()) { + Node from = entry.getKey(); + List edges = entry.getValue(); - for (Node node : adjEdList.keySet()) { - if (!nodesWithEdges.contains(node)) { - sb.append("\t").append(node.getId()).append(";\n"); + // Case: isolated node (no outgoing edges) + if (edges.isEmpty()) { + sb.append(" ").append(from.getId()).append(";\n"); + continue; + } + + for (Edge e : edges) { + sb.append(" ") + .append(e.from().getId()) + .append(arrow) + .append(e.to().getId()); + + // Add weight label if present + if (e.getWeight() != null) { + sb.append(" [label=\"").append(e.getWeight()).append("\"]"); + } + sb.append(";\n"); } } - sb.append("}\n"); + sb.append("}"); return sb.toString(); } diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java index 45ac02e..9774326 100644 --- a/src/main/java/m1graphs2025/UndirectedGraph.java +++ b/src/main/java/m1graphs2025/UndirectedGraph.java @@ -204,10 +204,10 @@ public class UndirectedGraph extends Graph { } return closure; } - - public static Graph fromDotFile(String filename) { + // ====================== + public static UndirectedGraph fromDotFile(String filename) { Map> adjEdList = new HashMap<>(); - Graph g = new UndirectedGraph(adjEdList); + UndirectedGraph g = new UndirectedGraph(adjEdList); try (BufferedReader dot = new BufferedReader(new FileReader(filename))) { String line; -- GitLab From 989d3b6d6ec6b3ebfc1307fd13c2ecb3706836e2 Mon Sep 17 00:00:00 2001 From: dcisse2 Date: Sun, 2 Nov 2025 11:17:24 +0100 Subject: [PATCH 4/9] [ADD] Ajout de la javaDoc --- .idea/vcs.xml | 1 + m1graphs25 | 1 + src/main/java/m1graphs2025/Edge.java | 161 +++- src/main/java/m1graphs2025/Graph.java | 762 +++++++++++++++--- src/main/java/m1graphs2025/Node.java | 42 +- src/main/java/m1graphs2025/NodeVisitInfo.java | 95 ++- .../java/m1graphs2025/UndirectedGraph.java | 386 ++++++--- src/main/resources/lectureDFS.gv | 16 + .../java/m1graphs2025/TestGraphPart6.java | 34 + .../java/m1graphs2025/TestsGraphPW2.java} | 271 ++++--- 10 files changed, 1329 insertions(+), 440 deletions(-) create mode 160000 m1graphs25 create mode 100644 src/main/resources/lectureDFS.gv create mode 100644 src/test/java/m1graphs2025/TestGraphPart6.java rename src/{main/java/m1graphs2025/TestGraphsPW2.java => test/java/m1graphs2025/TestsGraphPW2.java} (64%) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7..95c2cb1 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,5 +2,6 @@ + \ No newline at end of file diff --git a/m1graphs25 b/m1graphs25 new file mode 160000 index 0000000..bf6646e --- /dev/null +++ b/m1graphs25 @@ -0,0 +1 @@ +Subproject commit bf6646e2568cd77bd0ed40e9192c9b0126eda6ae diff --git a/src/main/java/m1graphs2025/Edge.java b/src/main/java/m1graphs2025/Edge.java index 0527c3d..1dada9e 100644 --- a/src/main/java/m1graphs2025/Edge.java +++ b/src/main/java/m1graphs2025/Edge.java @@ -2,19 +2,11 @@ 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 Comparable afin de permettre - * le tri des arêtes selon leur poids, puis selon leurs nœuds. - */ public class Edge implements Comparable { - // ====================== - // Attributs - // ====================== + // ================================================================================================================= + // ====================================================Attributs==================================================== + // ================================================================================================================= /** Nœud source de l’arête */ private Node from; @@ -25,31 +17,54 @@ public class Edge implements Comparable { /** Poids de l’arête (null si non pondérée) */ private Integer weight; - // ====================== - // Constructeurs - // ====================== + // ================================================================================================================= + // ==============================================Constructeurs====================================================== + // ================================================================================================================= - /** Arête non pondérée */ + /** + * Crée une arête non pondérée reliant deux nœuds donnés. + * + * @param from nœud source de l’arête + * @param to nœud destination de l’arête + */ public Edge(Node from, Node to) { this.from = from; this.to = to; this.weight = null; } + /** + * Constructeur par défaut : crée une arête nulle. + */ public Edge() { this.from = null; this.to = null; this.weight = null; } - /** Arête pondérée */ + /** + * Crée une arête pondérée reliant deux nœuds donnés. + * + * @param from nœud source de l’arête + * @param to nœud destination de l’arête + * @param weight 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 pondérée reliant deux nœuds identifiés par leurs IDs dans un graphe donné. + * Si les nœuds n’existent pas dans le graphe, ils sont créés. + * Le poids par défaut est 0. + * + * @param fromId ID du nœud source + * @param toId ID du nœud destination + * @param g graphe contenant 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."); @@ -68,9 +83,19 @@ public class Edge implements Comparable { this.from = from; this.to = to; - this.weight = 0; // ou une valeur par défaut + this.weight = 0; } + /** + * Crée une arête pondérée reliant deux nœuds identifiés par leurs IDs dans un graphe donné. + * Si les nœuds n’existent pas dans le graphe, ils sont créés. + * + * @param fromId ID du nœud source + * @param toId ID du nœud destination + * @param weight poids de l’arête + * @param g graphe contenant 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."); @@ -92,42 +117,82 @@ public class Edge implements Comparable { this.weight = weight; } - // ====================== - // Getters / Setters - // ====================== + // ================================================================================================================= + // =======================================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, ou null si l’arête n’est pas pondérée + */ public Integer getWeight() { return weight; } + /** + * Définit le nœud source de l’arête. + * + * @param from le nœud source + */ public void setNodeFrom(Node from) { this.from = from; } + /** + * Définit le nœud destination de l’arête. + * + * @param to le nœud destination + */ public void setNodeTo(Node to) { this.to = to; } + /** + * Définit le poids de l’arête. + * + * @param weight le poids à assigner, ou null si non pondérée + */ public void setWeight(Integer weight) { this.weight = weight; } - // ====================== - // Méthodes fonctionnelles - // ====================== + // ================================================================================================================= + // =====================================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,8 +200,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 - * au même graphe. + * @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; @@ -152,14 +216,17 @@ 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(); } /** - * Vérifie s’il existe une autre arête reliant les mêmes nœuds - * mais avec un poids différent (arête multiple). + * 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 l’arête est multiple, false sinon */ public boolean isMultiEdge() { if (from == null || from.getGraph() == null) @@ -183,18 +250,26 @@ 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 a un poids, false sinon + */ public boolean isWeighted() { return this.weight != null; } - // ====================== - // Méthodes redéfinies - // ====================== + // ================================================================================================================= + // =============================================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). + * Les arêtes non pondérées (poids null) sont considérées comme ayant un poids de 0. + * + * @param other l’autre arête à comparer + * @return un entier négatif, zéro ou positif si cette arête est respectivement + * inférieure, égale ou supérieure à l’autre arête */ @Override public int compareTo(Edge other) { @@ -213,8 +288,12 @@ public class Edge implements Comparable { } /** - * Vérifie l’égalité entre deux arêtes : - * même nœud source, même destination, même poids. + * Vérifie l’égalité entre deux arêtes. + * Deux arêtes sont égales si elles ont le même nœud source, le même nœud destination + * et le 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 +306,24 @@ public class Edge implements Comparable { && Objects.equals(weight, edge.weight); } + /** + * Calcule le code de hachage de l’arête, basé sur le nœud source, le nœud destination et le poids. + * + * @return le code de hachage + */ @Override public int hashCode() { return Objects.hash(from, to, weight); } + /** + * Retourne une représentation textuelle de l’arête sous la forme "from -> to". + * + * @return la chaîne représentant l’arête + */ @Override public String toString() { - return "Edge(" + from.getId() + " -> " + to.getId() + ", w=" + weight + ")"; + return from.getId() + " -> " + to.getId(); } } \ No newline at end of file diff --git a/src/main/java/m1graphs2025/Graph.java b/src/main/java/m1graphs2025/Graph.java index a4bf39c..c0ab447 100644 --- a/src/main/java/m1graphs2025/Graph.java +++ b/src/main/java/m1graphs2025/Graph.java @@ -13,23 +13,33 @@ import java.util.regex.Pattern; import m1graphs2025.NodeVisitInfo.NodeColour; -/** - * Classe représentant un graphe orienté à l’aide de listes d’adjacence. - */ public class Graph { protected Map> adjEdList; - // ====================== - // CONSTRUCTEURS - // ====================== + // ================================================================================================================= + // ===========================================CONSTRUCTEURS========================================================= + // ================================================================================================================= + /** + * Construit un graphe en utilisant directement une liste d’adjacence fournie. + * + * @param adjEdList Map associant chaque nœud à la liste de ses arêtes sortantes + */ public Graph(Map> adjEdList) { this.adjEdList = adjEdList; } /** - * Constructeur alternatif à partir d’un tableau de successeurs. + * Construit un graphe à partir d’un tableau de successeurs. + * Chaque nœud est numéroté à partir de 1 et le tableau utilise 0 pour + * séparer les listes de successeurs de chaque nœud. + * + * Par exemple, un tableau [2, 3, 0, 3, 0] représente : + * - le nœud 1 a pour successeurs 2 et 3 + * - le nœud 2 a pour successeur 3 + * + * @param successorArray tableau des successeurs avec 0 comme séparateur de listes */ public Graph(int... successorArray) { int taille = successorArray.length; @@ -56,14 +66,25 @@ public class Graph { this.adjEdList = adjEdList; } - // ====================== - // API DES NŒUDS - // ====================== + // ================================================================================================================= + // =======================================API DES NŒUDS============================================================= + // ================================================================================================================= + /** + * Retourne le nombre total de nœuds présents dans le graphe. + * + * @return le nombre de nœuds dans la liste d’adjacence. + */ public int nbNodes() { return this.adjEdList.size(); } + /** + * Vérifie si un nœud spécifique est présent dans le graphe. + * + * @param n le nœud à tester. + * @return {@code true} si le nœud appartient au graphe, {@code false} sinon. + */ public boolean usesNode(Node n) { if (n == null) { System.err.println("Erreur: tentative de vérification d’un nœud null."); @@ -72,6 +93,12 @@ public class Graph { return usesNode(n.getId()); } + /** + * Vérifie si un nœud d’identifiant donné est présent dans le graphe. + * + * @param id identifiant du nœud à rechercher (doit être > 0). + * @return {@code true} si le nœud existe, {@code false} sinon. + */ public boolean usesNode(int id) { if (id <= 0) { System.err.println("Erreur: identifiant de nœud invalide (" + id + ")."); @@ -80,6 +107,12 @@ public class Graph { return getNode(id) != null; } + /** + * Vérifie si un nœud appartient bien à ce graphe (même instance). + * + * @param n le nœud à tester. + * @return {@code true} si le nœud est utilisé et appartient à ce graphe. + */ public boolean holdsNode(Node n) { if (n == null) { System.err.println("Erreur: tentative de vérification d’un nœud null."); @@ -88,6 +121,12 @@ public class Graph { return usesNode(n) && n.getGraph() == this; } + /** + * Recherche un nœud du graphe à partir de son identifiant. + * + * @param id identifiant du nœud recherché. + * @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 + ")."); @@ -101,14 +140,25 @@ public class Graph { if (entry != null && entry.getId() == id) return entry; } - return null; } + /** + * Récupère un nœud existant ou le crée s’il n’existe pas encore. + * + * @param id identifiant du nœud. + * @return le nœud existant ou un nouveau nœud créé avec ce graphe. + */ public Node getOrCreateNode(int id) { - return getNode(id) != null ? getNode(id) : new Node(id, this); - } + return getNode(id) != null ? getNode(id) : new Node(id, this); + } + /** + * Ajoute un nœud existant au graphe. + * + * @param n le nœud à ajouter. + * @return {@code true} si l’ajout a réussi, {@code false} sinon. + */ public boolean addNode(Node n) { if (n == null) { System.err.println("Erreur: tentative d’ajout d’un nœud null."); @@ -132,6 +182,12 @@ public class Graph { return false; } + /** + * Crée et ajoute un nœud à partir de son identifiant. + * + * @param id identifiant du nœud à créer. + * @return {@code true} si l’ajout a réussi, {@code false} sinon. + */ public boolean addNode(int id) { if (id <= 0) { System.err.println("Erreur: identifiant de nœud invalide (" + id + ")."); @@ -152,6 +208,12 @@ public class Graph { return false; } + /** + * Supprime un nœud du graphe ainsi que toutes ses arêtes associées. + * + * @param n le nœud à supprimer. + * @return {@code true} si la suppression a réussi, {@code false} sinon. + */ public boolean removeNode(Node n) { if (n == null) { System.err.println("Erreur: tentative de suppression d’un nœud null."); @@ -177,10 +239,21 @@ public class Graph { } } + /** + * Supprime un nœud dont l’identifiant est 0 (inutile ici, conservé pour compatibilité). + * + * @return {@code false} car l’identifiant 0 est invalide. + */ public boolean removeNode() { return removeNode(0); } + /** + * Supprime un nœud identifié par son identifiant numérique. + * + * @param id identifiant du nœud à supprimer. + * @return {@code true} si la suppression a réussi, {@code false} sinon. + */ public boolean removeNode(int id) { if (id <= 0) { System.err.println("Erreur: identifiant de nœud invalide (" + id + ")."); @@ -201,10 +274,20 @@ public class Graph { } } + /** + * Retourne la liste de tous les nœuds du graphe. + * + * @return une nouvelle liste contenant tous les nœuds. + */ public List getAllNodes() { return new ArrayList<>(adjEdList.keySet()); } + /** + * Retourne l’identifiant le plus élevé parmi tous les nœuds du graphe. + * + * @return l’identifiant maximum, ou {@link 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 +303,11 @@ public class Graph { return maxId; } + /** + * Retourne l’identifiant le plus petit parmi tous les nœuds du graphe. + * + * @return l’identifiant minimum, ou {@link 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 +323,12 @@ public class Graph { return minId; } + /** + * Retourne la liste des successeurs d’un nœud. + * + * @param n nœud dont on veut les successeurs. + * @return la liste des nœuds successeurs, ou une liste vide si le nœud est nul ou invalide. + */ public List getSuccessors(Node n) { if (n == null) { System.err.println("Erreur: nœud nul dans getSuccessors()."); @@ -249,6 +343,12 @@ public class Graph { } } + /** + * Retourne la liste des successeurs d’un nœud à partir de son identifiant. + * + * @param id identifiant du nœud. + * @return la liste des nœuds successeurs, ou une liste vide si l’identifiant est invalide. + */ public List getSuccessors(int id) { Node u = getNode(id); if (u == null) { @@ -258,6 +358,12 @@ public class Graph { return getSuccessors(u); } + /** + * Retourne la liste des successeurs multiples d’un nœud (en tenant compte des multi-arêtes). + * + * @param n nœud dont on veut les successeurs multiples. + * @return liste des successeurs, éventuellement avec doublons. + */ public List getSuccessorsMulti(Node n) { if (n == null) { System.err.println("Erreur: nœud nul dans getSuccessorsMulti()."); @@ -272,6 +378,12 @@ public class Graph { } } + /** + * Retourne la liste des successeurs multiples à partir d’un identifiant. + * + * @param id identifiant du nœud. + * @return liste des successeurs multiples, ou liste vide si l’identifiant est invalide. + */ public List getSuccessorsMulti(int id) { Node u = getNode(id); if (u == null) { @@ -281,6 +393,13 @@ public class Graph { return getSuccessorsMulti(u); } + /** + * Vérifie si deux nœuds sont adjacents (s’il existe une arête de u vers v). + * + * @param u nœud source. + * @param v nœud destination. + * @return {@code true} s’il existe une arête de u vers v, {@code 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,13 @@ public class Graph { } } + /** + * Vérifie si deux nœuds identifiés par leurs IDs sont adjacents. + * + * @param uId identifiant du nœud source. + * @param vId identifiant du nœud destination. + * @return {@code true} si une arête existe, {@code false} sinon. + */ public boolean adjacent(int uId, int vId) { Node u = getNode(uId); Node v = getNode(vId); @@ -305,6 +431,12 @@ public class Graph { return adjacent(u, v); } + /** + * Calcule le degré entrant d’un nœud. + * + * @param n le nœud concerné. + * @return le nombre d’arêtes entrantes du nœud. + */ public int inDegree(Node n) { if (n == null) { System.err.println("Erreur: nœud nul dans inDegree()."); @@ -318,6 +450,12 @@ public class Graph { } } + /** + * Calcule le degré entrant d’un nœud à partir de son identifiant. + * + * @param id identifiant du nœud. + * @return le degré entrant, ou 0 si le nœud n’existe pas. + */ public int inDegree(int id) { Node n = getNode(id); if (n == null) { @@ -327,6 +465,12 @@ public class Graph { return inDegree(n); } + /** + * Calcule le degré sortant d’un nœud. + * + * @param n le nœud concerné. + * @return le nombre d’arêtes sortantes. + */ public int outDegree(Node n) { if (n == null) { System.err.println("Erreur: nœud nul dans outDegree()."); @@ -340,6 +484,12 @@ public class Graph { } } + /** + * Calcule le degré sortant d’un nœud à partir de son identifiant. + * + * @param id identifiant du nœud. + * @return le degré sortant, ou 0 si le nœud n’existe pas. + */ public int outDegree(int id) { Node n = getNode(id); if (n == null) { @@ -349,6 +499,12 @@ public class Graph { return outDegree(n); } + /** + * Calcule le degré total (entrant + sortant) d’un nœud. + * + * @param n le nœud concerné. + * @return le degré total du nœud. + */ public int degree(Node n) { if (n == null) { System.err.println("Erreur: nœud nul dans degree()."); @@ -362,6 +518,12 @@ public class Graph { } } + /** + * Calcule le degré total d’un nœud à partir de son identifiant. + * + * @param id identifiant du nœud. + * @return le degré total, ou 0 si le nœud n’existe pas. + */ public int degree(int id) { Node n = getNode(id); if (n == null) { @@ -371,10 +533,19 @@ public class Graph { return degree(n); } - // ====================== - // API DES ARÊTES - // ====================== + // ================================================================================================================= + // ==========================================API DES ARÊTES========================================================= + // ================================================================================================================= + /** + * Calcule le nombre total d’arêtes présentes dans le graphe. + *

+ * Cette méthode parcourt la liste d’adjacence et additionne le nombre + * d’arêtes sortantes pour chaque nœud. + *

+ * + * @return le nombre total d’arêtes présentes dans le graphe + */ public int nbEdges() { int count = 0; for (List edges : adjEdList.values()) @@ -382,6 +553,18 @@ public class Graph { return count; } + /** + * Vérifie si une arête existe entre deux nœuds donnés. + *

+ * Cette méthode recherche dans la liste des arêtes sortantes du nœud {@code u} + * si une arête relie ce dernier au nœud {@code v}. + *

+ * + * @param u nœud source + * @param v nœud destination + * @return {@code true} si une arête relie {@code u} à {@code v}, sinon {@code false} + * @throws IllegalArgumentException si l’un des nœuds est {@code null} + */ public boolean existsEdge(Node u, Node v) { try { if (u == null || v == null) { @@ -398,6 +581,18 @@ public class Graph { } } + /** + * Vérifie si une arête existe entre deux nœuds identifiés par leurs identifiants. + *

+ * Cette méthode récupère les objets {@link Node} correspondants aux identifiants fournis + * puis vérifie la présence d’une arête entre eux. + *

+ * + * @param uId identifiant du nœud source + * @param vId identifiant du nœud destination + * @return {@code true} si une arête relie {@code uId} à {@code vId}, sinon {@code false} + * @throws IllegalArgumentException si les identifiants sont négatifs ou si un nœud n’existe pas + */ public boolean existsEdge(int uId, int vId) { try { if (uId < 0 || vId < 0) { @@ -415,6 +610,17 @@ public class Graph { } } + /** + * Vérifie si une arête donnée existe déjà dans le graphe. + *

+ * Cette méthode vérifie dans la liste des arêtes sortantes du nœud source + * si l’arête spécifiée est présente. + *

+ * + * @param e arête à vérifier + * @return {@code true} si l’arête existe déjà, sinon {@code false} + * @throws IllegalArgumentException si l’arête fournie est {@code null} + */ public boolean existsEdge(Edge e) { try { if (e == null) { @@ -427,6 +633,17 @@ public class Graph { } } + /** + * Vérifie s’il existe plusieurs arêtes (multi-arêtes) entre deux nœuds donnés. + *

+ * Une multi-arête correspond à plusieurs arêtes distinctes reliant le même couple de nœuds. + *

+ * + * @param u nœud source + * @param v nœud destination + * @return {@code true} s’il existe plusieurs arêtes entre {@code u} et {@code v}, sinon {@code false} + * @throws IllegalArgumentException si l’un des nœuds est {@code null} + */ public boolean isMultiEdge(Node u, Node v) { try { if (u == null || v == null) { @@ -440,6 +657,18 @@ public class Graph { } } + /** + * Vérifie s’il existe plusieurs arêtes (multi-arêtes) entre deux nœuds identifiés par leurs identifiants. + *

+ * Cette méthode récupère les objets {@link Node} correspondant aux identifiants spécifiés + * puis vérifie la présence de multi-arêtes entre eux. + *

+ * + * @param uId identifiant du nœud source + * @param vId identifiant du nœud destination + * @return {@code true} s’il existe plusieurs arêtes entre {@code uId} et {@code vId}, sinon {@code false} + * @throws IllegalArgumentException si les identifiants sont négatifs ou si les nœuds n’existent pas + */ public boolean isMultiEdge(int uId, int vId) { try { if (uId < 0 || vId < 0) { @@ -457,6 +686,14 @@ public class Graph { } } + /** + * Ajoute une arête entre deux nœuds spécifiés. + * Si les nœuds n'existent pas encore dans le graphe, ils sont ajoutés automatiquement. + * + * @param from le nœud source + * @param to le nœud destination + * @throws IllegalArgumentException si l'un des nœuds est {@code null} + */ public void addEdge(Node from, Node to) { try { if (from == null || to == null) { @@ -472,6 +709,14 @@ public class Graph { } } + /** + * Ajoute une arête entre deux nœuds identifiés par leurs identifiants. + * Si les nœuds n'existent pas encore, ils sont créés avant l'ajout de l'arête. + * + * @param fromId identifiant du nœud source + * @param toId identifiant du nœud destination + * @throws IllegalArgumentException si les identifiants sont négatifs + */ public void addEdge(int fromId, int toId) { try { if (fromId < 0 || toId < 0) { @@ -489,6 +734,12 @@ public class Graph { } } + /** + * Ajoute une arête spécifique dans le graphe si elle n’existe pas déjà. + * + * @param e l’arête à ajouter + * @throws IllegalArgumentException si l’arête est {@code null} ou existe déjà + */ public void addEdge(Edge e) { try { if (e == null) { @@ -504,6 +755,15 @@ public class Graph { } } + /** + * Ajoute une arête pondérée entre deux nœuds. + * Si les nœuds n'existent pas, ils sont automatiquement ajoutés au graphe. + * + * @param from nœud source + * @param to nœud destination + * @param weight poids de l’arête (doit être positif ou nul) + * @throws IllegalArgumentException si les nœuds sont {@code null} ou si le poids est négatif + */ public void addEdge(Node from, Node to, int weight) { try { if (from == null || to == null) { @@ -522,6 +782,15 @@ public class Graph { } } + /** + * Ajoute une arête pondérée entre deux nœuds identifiés par leurs identifiants. + * Si les nœuds n'existent pas, ils sont créés avant l'ajout de l’arête. + * + * @param fromId identifiant du nœud source + * @param toId identifiant du nœud destination + * @param weight poids de l’arête (doit être positif ou nul) + * @throws IllegalArgumentException si un identifiant est négatif ou si le poids est négatif + */ public void addEdge(int fromId, int toId, int weight) { try { if (fromId < 0 || toId < 0) { @@ -542,6 +811,15 @@ public class Graph { } } + /** + * Ajoute une arête entre deux nœuds identifiés, appartenant à un autre graphe. + * Si les nœuds n'existent pas dans ce graphe, ils sont créés en référence au graphe fourni. + * + * @param fromId identifiant du nœud source + * @param toId identifiant du nœud destination + * @param g graphe auquel appartiennent les nœuds + * @throws IllegalArgumentException si le graphe est {@code null} ou si les identifiants sont négatifs + */ public void addEdge(int fromId, int toId, Graph g) { try { if (g == null) { @@ -562,6 +840,14 @@ public class Graph { } } + /** + * Supprime l'arête reliant deux nœuds spécifiés s'il en existe une. + * + * @param from le nœud source + * @param to le nœud destination + * @return {@code true} si une arête a été supprimée, {@code false} sinon + * @throws IllegalArgumentException si l’un des nœuds est {@code null} + */ public boolean removeEdge(Node from, Node to) { try { if (from == null || to == null) { @@ -575,6 +861,15 @@ public class Graph { } } + /** + * Supprime une arête identifiée par les identifiants numériques de ses nœuds source et destination. + * Si les nœuds n'existent pas dans le graphe, la suppression ne sera pas effectuée. + * + * @param fromId identifiant du nœud source + * @param toId identifiant du nœud destination + * @return {@code true} si une arête a été supprimée, {@code false} sinon + * @throws IllegalArgumentException si les identifiants sont négatifs ou si les nœuds n’existent pas + */ public boolean removeEdge(int fromId, int toId) { try { if (fromId < 0 || toId < 0) { @@ -592,6 +887,13 @@ public class Graph { } } + /** + * Supprime une arête spécifique du graphe. + * + * @param e l’arête à supprimer + * @return {@code true} si l’arête a été supprimée, {@code false} sinon + * @throws IllegalArgumentException si l’arête est {@code null} + */ public boolean removeEdge(Edge e) { try { if (e == null) { @@ -604,6 +906,14 @@ public class Graph { } } + /** + * Retourne la liste des arêtes sortantes à partir d’un nœud donné. + * + * @param n le nœud dont on souhaite obtenir les arêtes sortantes + * @return une liste contenant les arêtes sortantes du nœud, + * ou une liste vide si le nœud n’a pas d’arêtes sortantes + * @throws IllegalArgumentException si le nœud est {@code null} + */ public List getOutEdges(Node n) { try { if (n == null) { @@ -616,6 +926,14 @@ public class Graph { } } + /** + * Retourne la liste des arêtes sortantes d’un nœud identifié par son identifiant numérique. + * + * @param id identifiant du nœud + * @return une liste contenant les arêtes sortantes du nœud, + * ou une liste vide si le nœud n’existe pas ou n’a pas d’arêtes sortantes + * @throws IllegalArgumentException si l’identifiant est négatif ou que le nœud n’existe pas + */ public List getOutEdges(int id) { try { if (id < 0) { @@ -632,6 +950,14 @@ public class Graph { } } + /** + * Retourne la liste des arêtes entrantes vers un nœud donné. + * + * @param n le nœud cible dont on souhaite obtenir les arêtes entrantes + * @return une liste des arêtes pointant vers le nœud donné, + * ou une liste vide si aucune arête n’entre dans ce nœud + * @throws IllegalArgumentException si le nœud est {@code null} + */ public List getInEdges(Node n) { try { if (n == null) { @@ -651,6 +977,14 @@ public class Graph { } } + /** + * Retourne la liste des arêtes entrantes vers un nœud identifié par son identifiant numérique. + * + * @param id identifiant du nœud + * @return une liste des arêtes entrantes vers le nœud, + * ou une liste vide si aucune n’existe + * @throws IllegalArgumentException si l’identifiant est négatif ou que le nœud n’existe pas + */ public List getInEdges(int id) { try { if (id < 0) { @@ -667,6 +1001,13 @@ public class Graph { } } + /** + * Retourne toutes les arêtes incidentes à un nœud (entrantes + sortantes). + * + * @param n le nœud concerné + * @return une liste contenant toutes les arêtes incidentes au nœud donné + * @throws IllegalArgumentException si le nœud est {@code null} + */ public List getIncidentEdges(Node n) { try { if (n == null) { @@ -681,6 +1022,14 @@ public class Graph { } } + /** + * Retourne toutes les arêtes incidentes à un nœud identifié par son identifiant numérique. + * + * @param id identifiant du nœud + * @return une liste contenant toutes les arêtes incidentes au nœud, + * ou une liste vide si aucune n’existe + * @throws IllegalArgumentException si l’identifiant est négatif ou que le nœud n’existe pas + */ public List getIncidentEdges(int id) { try { if (id < 0) { @@ -697,6 +1046,16 @@ public class Graph { } } + /** + * Retourne la liste de toutes les arêtes reliant un nœud source {@code u} à un nœud destination {@code v}. + * Cette méthode est utile dans les graphes multiarêtes. + * + * @param u le nœud source + * @param v le nœud destination + * @return une liste des arêtes reliant {@code u} à {@code v}, + * ou une liste vide si aucune arête ne les relie + * @throws IllegalArgumentException si l’un des deux nœuds est {@code null} + */ public List getEdges(Node u, Node v) { try { if (u == null || v == null) { @@ -714,6 +1073,12 @@ public class Graph { } } + /** + * Retourne toutes les arêtes du graphe, indépendamment de leur direction ou de leur poids. + * + * @return une liste contenant toutes les arêtes du graphe, + * ou une liste vide si le graphe ne contient aucune arête + */ public List getAllEdges() { try { List result = new ArrayList<>(); @@ -726,10 +1091,16 @@ public class Graph { } } - // ====================== - // Représentation et transformations - // ====================== + // ================================================================================================================= + // ========================================Représentation et transformations======================================== + // ================================================================================================================= + /** + * Convertit le graphe en tableau de successeurs. + * Chaque liste de successeurs d'un nœud est terminée par un 0. + * + * @return un tableau d'entiers représentant les successeurs de chaque nœud + */ public int[] toSuccessorArray() { List list = new ArrayList<>(); List nodes = new ArrayList<>(adjEdList.keySet()); @@ -743,17 +1114,28 @@ public class Graph { return list.stream().mapToInt(Integer::intValue).toArray(); } + /** + * Génère la matrice d'adjacence du graphe. + * La cellule [i][j] contient le nombre d’arêtes allant du nœud i+1 vers le nœud j+1. + * + * @return une matrice carrée représentant les connexions entre les nœuds + */ public int[][] toAdjMatrix() { - int n = largestNodeId() + 1; + int n = largestNodeId(); int[][] matrix = new int[n][n]; for (Node u : adjEdList.keySet()) { for (Edge e : adjEdList.get(u)) { - matrix[u.getId()][e.getNodeTo().getId()]++; + matrix[u.getId() - 1][e.getNodeTo().getId() - 1]++; } } return matrix; } + /** + * Retourne le graphe inverse où toutes les arêtes sont inversées. + * + * @return un nouveau graphe représentant l’inverse du graphe actuel + */ public Graph getReverse() { Map> reversed = new HashMap<>(); for (Node n : adjEdList.keySet()) @@ -768,6 +1150,12 @@ public class Graph { return new Graph(reversed); } + /** + * Calcule la fermeture transitive du graphe. + * Ajoute une arête directe entre deux nœuds si un chemin existe entre eux. + * + * @return un nouveau graphe représentant la fermeture transitive + */ public Graph getTransitiveClosure() { Graph closure = this.copy(); List nodes = closure.getAllNodes(); @@ -785,6 +1173,12 @@ public class Graph { return closure; } + /** + * Vérifie si le graphe est un multigraphe. + * Un multigraphe contient au moins deux arêtes entre les mêmes nœuds. + * + * @return true si le graphe contient des arêtes multiples, false sinon + */ public boolean isMultiGraph() { for (Node u : adjEdList.keySet()) { List edges = adjEdList.get(u); @@ -799,6 +1193,12 @@ public class Graph { return false; } + /** + * Vérifie si le graphe contient des boucles. + * Une boucle est une arête reliant un nœud à lui-même. + * + * @return true si au moins une boucle est présente, false sinon + */ public boolean hasSelfLoops() { for (Node u : adjEdList.keySet()) { for (Edge e : adjEdList.get(u)) { @@ -809,10 +1209,22 @@ public class Graph { return false; } + /** + * Vérifie si le graphe est simple. + * Un graphe simple ne contient ni arêtes multiples ni boucles. + * + * @return true si le graphe est simple, false sinon + */ public boolean isSimpleGraph() { return !isMultiGraph() && !hasSelfLoops(); } + /** + * Crée une version simple du graphe. + * Supprime toutes les boucles et fusionne les arêtes multiples en une seule. + * + * @return un nouveau graphe simple dérivé de ce graphe + */ public Graph toSimpleGraph() { Graph g = new Graph(new HashMap<>()); for (Node u : adjEdList.keySet()) @@ -830,6 +1242,12 @@ public class Graph { return g; } + /** + * Crée une copie profonde (deep copy) du graphe. + * Les nœuds et les arêtes sont dupliqués pour éviter les références partagées. + * + * @return une copie indépendante du graphe actuel + */ public Graph copy() { Map> copy = new HashMap<>(); for (Node u : adjEdList.keySet()) { @@ -841,51 +1259,95 @@ public class Graph { return new Graph(copy); } - // ====================== - // Parcours DFS / BFS - // ====================== + // ================================================================================================================= + // ==========================================Parcours DFS / BFS===================================================== + // ================================================================================================================= + /** + * Effectue un parcours en profondeur (DFS) sur l'ensemble du graphe. + * Les nœuds sont visités dans l'ordre trié par identifiant. + * + * @return la liste des nœuds visités dans l'ordre DFS + */ public List getDFS() { - List visitedNodeList = new ArrayList<>(); - Map infos = initVisitInfo(); + List visitedNodes = new ArrayList<>(); + Map visitInfo = initVisitInfo(); AtomicInteger time = new AtomicInteger(0); - for (Node n : sortNodes()) { - if (infos.get(n).getColour() == NodeColour.WHITE) - DFSVisit(n, time, infos, visitedNodeList); + + for (Node node : sortNodes()) { + if (visitInfo.get(node).getColour() == NodeColour.WHITE) { + dfsVisit(node, time, visitInfo, visitedNodes); + } } - return visitedNodeList; + + return visitedNodes; } + /** + * Effectue un parcours en profondeur (DFS) à partir d'un nœud de départ donné. + * + * @param start le nœud de départ + * @return la liste des nœuds visités dans l'ordre DFS + */ public List getDFS(Node start) { - List visited = new ArrayList<>(); - Map infos = initVisitInfo(); + if (start == null) return new ArrayList<>(); + + List visitedNodes = new ArrayList<>(); + Map visitInfo = initVisitInfo(); AtomicInteger time = new AtomicInteger(0); - DFSVisit(start, time, infos, visited); - return visited; + + dfsVisit(start, time, visitInfo, visitedNodes); + return visitedNodes; } + /** + * Effectue un parcours en profondeur (DFS) à partir d'un nœud identifié par son ID. + * + * @param startId l'identifiant du nœud de départ + * @return la liste des nœuds visités dans l'ordre DFS + */ public List getDFS(int startId) { - Node s = getNode(startId); - return s == null ? new ArrayList<>() : getDFS(s); + Node startNode = getNode(startId); + return getDFS(startNode); } - private void DFSVisit(Node u, AtomicInteger time, Map infos, List visited) { + /** + * Fonction récursive utilisée par le DFS pour visiter un nœud et ses successeurs. + * + * @param current le nœud actuellement visité + * @param time compteur global de temps pour marquer découverte et fin + * @param visitInfo informations de visite pour chaque nœud + * @param visitedNodes liste des nœuds visités + */ + private void dfsVisit(Node current, AtomicInteger time, + Map visitInfo, List visitedNodes) { + time.incrementAndGet(); - NodeVisitInfo info = infos.get(u); - info.setDiscoveryTime(time.get()); - info.setColour(NodeColour.GRAY); - for (Node v : getSuccessors(u)) { - if (infos.get(v).getColour() == NodeColour.WHITE) { - infos.get(v).setPredecessor(u); - DFSVisit(v, time, infos, visited); + NodeVisitInfo currentInfo = visitInfo.get(current); + currentInfo.setDiscoveryTime(time.get()); + currentInfo.setColour(NodeColour.GRAY); + + visitedNodes.add(current); + + for (Node neighbor : getSuccessors(current)) { + NodeVisitInfo neighborInfo = visitInfo.get(neighbor); + if (neighborInfo.getColour() == NodeColour.WHITE) { + neighborInfo.setPredecessor(current); + dfsVisit(neighbor, time, visitInfo, visitedNodes); } } - info.setColour(NodeColour.BLACK); + + currentInfo.setColour(NodeColour.BLACK); time.incrementAndGet(); - info.setFinishTime(time.get()); - visited.add(u); + currentInfo.setFinishTime(time.get()); } + /** + * Effectue un parcours en largeur (BFS) sur l'ensemble du graphe. + * Les nœuds sont visités dans l'ordre trié par identifiant. + * + * @return la liste des nœuds visités dans l'ordre BFS + */ public List getBFS() { List visited = new ArrayList<>(); Map infos = initVisitInfo(); @@ -895,6 +1357,12 @@ public class Graph { return visited; } + /** + * Effectue un parcours en largeur (BFS) à partir d'un nœud de départ donné. + * + * @param start le nœud de départ + * @return la liste des nœuds visités dans l'ordre BFS + */ public List getBFS(Node start) { List visited = new ArrayList<>(); Map infos = initVisitInfo(); @@ -902,11 +1370,24 @@ public class Graph { return visited; } + /** + * Effectue un parcours en largeur (BFS) à partir d'un nœud identifié par son ID. + * + * @param startId l'identifiant du nœud de départ + * @return la liste des nœuds visités dans l'ordre BFS + */ public List getBFS(int startId) { Node s = getNode(startId); return s == null ? new ArrayList<>() : getBFS(s); } + /** + * Fonction utilisée par le BFS pour visiter les nœuds accessibles à partir d'un nœud source. + * + * @param s le nœud de départ + * @param infos informations de visite pour chaque nœud + * @param visited 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); @@ -927,12 +1408,20 @@ public class Graph { } } - // ====================== - // DFS enrichi (59-60) - // ====================== + // ================================================================================================================= + // ===========================================DFS enrichi (59-60)=================================================== + // ================================================================================================================= + /** + * Effectue un parcours en profondeur (DFS) sur l'ensemble du graphe, + * en enregistrant des informations détaillées sur les nœuds et les arêtes. + * + * @param nodeVisit Map contenant les informations de visite pour chaque nœud + * @param edgeVisit Map contenant les types de visite pour chaque arête + * @return la liste des nœuds visités dans l'ordre DFS + */ public List getDFSWithVisitInfo(Map nodeVisit, - Map edgeVisit) { + Map edgeVisit) { List visited = new ArrayList<>(); AtomicInteger time = new AtomicInteger(0); initVisitInfo(nodeVisit); @@ -943,9 +1432,18 @@ public class Graph { return visited; } + /** + * Effectue un parcours en profondeur (DFS) à partir d'un nœud de départ donné, + * en enregistrant des informations détaillées sur les nœuds et les arêtes. + * + * @param start le nœud de départ + * @param nodeVisit Map contenant les informations de visite pour chaque nœud + * @param edgeVisit Map contenant les types de visite pour chaque arête + * @return la liste des nœuds visités dans l'ordre DFS + */ public List getDFSWithVisitInfo(Node start, - Map nodeVisit, - Map edgeVisit) { + Map nodeVisit, + Map edgeVisit) { List visited = new ArrayList<>(); AtomicInteger time = new AtomicInteger(0); initVisitInfo(nodeVisit); @@ -953,13 +1451,24 @@ public class Graph { return visited; } + /** + * Fonction récursive utilisée par le DFS enrichi pour visiter un nœud et ses successeurs, + * en enregistrant le type de chaque arête (TREE, BACKWARD, FORWARD, CROSS). + * + * @param u le nœud actuellement visité + * @param time compteur global de temps pour marquer découverte et fin + * @param nodeVisit Map contenant les informations de visite pour chaque nœud + * @param edgeVisit Map contenant les types de visite pour chaque arête + * @param visited liste des nœuds visités + */ private void DFSVisitEnriched(Node u, AtomicInteger time, - Map nodeVisit, - Map edgeVisit, - List visited) { + 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(); @@ -977,43 +1486,24 @@ public class Graph { edgeVisit.put(e, EdgeVisitType.CROSS); } } + nodeVisit.get(u).setColour(NodeColour.BLACK); time.incrementAndGet(); nodeVisit.get(u).setFinishTime(time.get()); visited.add(u); } - // ====================== - // DOT I/O - // ====================== + // ================================================================================================================= + // ===============================================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)
  • - *
- *

+ * Crée un graphe à partir d'un fichier DOT. + * Chaque ligne du fichier peut représenter soit un nœud isolé, soit une arête avec un poids optionnel. * - * @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: - * 1 -> 2 [label="5"]; - * 3 - * (isolated node) - * - * @param filename the path to the DOT file - * @return a Graph instance representing the file content - * @throws RuntimeException if file reading fails + * @param filename le chemin du fichier DOT à lire + * @return le graphe construit à partir du fichier + * @throws RuntimeException si une erreur de lecture du fichier se produit */ public static Graph fromDotFile(String filename) { Map> adjEdList = new HashMap<>(); @@ -1027,7 +1517,6 @@ public class Graph { 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); @@ -1035,7 +1524,6 @@ public class Graph { continue; } - // --- Case 2: edge line String[] tokens = line.replaceAll("[^0-9]+", " ").trim().split("\\s+"); if (tokens.length < 2) continue; // invalid line → skip @@ -1067,51 +1555,53 @@ public class Graph { } /** - * Generates a DOT-format string representation of this graph. - *

- * Example output for a directed graph: - * - *

-     * digraph G {
-     *     rankdir=LR;
-     *     1 -> 2 [label="5"];
-     *     2 -> 3;
-     *     4;
-     * }
-     * 
- *

+ * Retourne la représentation du graphe au format DOT sous forme de chaîne. * - * @return the DOT-format representation of the graph + * @return chaîne contenant la description DOT du graphe */ public String toDotString() { - boolean isDirected = true; // adapt this if you support undirected graphs - String arrow = isDirected ? " -> " : " -- "; - StringBuilder sb = new StringBuilder(); - sb.append(isDirected ? "digraph {\n" : "graph {\n"); + sb.append("graph {\n"); sb.append(" rankdir=LR;\n"); - for (Map.Entry> entry : adjEdList.entrySet()) { - Node from = entry.getKey(); - List edges = entry.getValue(); + // Trier les nœuds par ID pour affichage ascendant + List sortedNodes = new ArrayList<>(adjEdList.keySet()); + sortedNodes.sort(Comparator.comparingInt(Node::getId)); - // Case: isolated node (no outgoing edges) + for (Node from : sortedNodes) { + List edges = adjEdList.get(from); + + // Si le nœud n'a pas d'arêtes, c'est un nœud isolé if (edges.isEmpty()) { - sb.append(" ").append(from.getId()).append(";\n"); continue; } for (Edge e : edges) { - sb.append(" ") - .append(e.from().getId()) - .append(arrow) - .append(e.to().getId()); + Node u = e.from(); + Node v = e.to(); + + // Self-loop : toujours afficher + if (u.equals(v)) { + sb.append(" ").append(u.getId()) + .append(" -> ") + .append(v.getId()); + + if (e.getWeight() != null) { + sb.append(" [label=").append(e.getWeight()).append("]"); + } + sb.append("\n"); + continue; + } + + // Arêtes normales : afficher toutes les occurrences + sb.append(" ").append(u.getId()) + .append(" -> ") + .append(v.getId()); - // Add weight label if present if (e.getWeight() != null) { - sb.append(" [label=\"").append(e.getWeight()).append("\"]"); + sb.append(" [label=").append(e.getWeight()).append("]"); } - sb.append(";\n"); + sb.append("\n"); } } @@ -1119,10 +1609,21 @@ public class Graph { return sb.toString(); } + /** + * Écrit le graphe au format DOT dans un fichier avec l'extension par défaut ".gv". + * + * @param filename nom du fichier sans extension + */ public void toDotFile(String filename) { toDotFile(filename, ".gv"); } + /** + * Écrit le graphe au format DOT dans un fichier avec l'extension spécifiée. + * + * @param filename nom du fichier sans extension + * @param ext extension du fichier (ex : ".gv"); si null, ".gv" sera utilisé + */ public void toDotFile(String filename, String ext) { if (ext == null) ext = ".gv"; @@ -1135,10 +1636,15 @@ public class Graph { } } - // ====================== - // Méthodes utilitaires internes - // ====================== + // ================================================================================================================= + // ===================================Méthodes utilitaires internes================================================= + // ================================================================================================================= + /** + * Initialise les informations de visite pour tous les nœuds du graphe. + * + * @return une Map associant chaque nœud à son objet NodeVisitInfo initialisé + */ private Map initVisitInfo() { Map map = new HashMap<>(); for (Node n : adjEdList.keySet()) @@ -1146,14 +1652,26 @@ public class Graph { return map; } + /** + * Initialise les informations de visite pour tous les nœuds du graphe + * dans la Map fournie. + * + * @param map la Map dans laquelle chaque nœud sera associé à un NodeVisitInfo initialisé + */ private void initVisitInfo(Map map) { for (Node n : adjEdList.keySet()) map.put(n, new NodeVisitInfo()); } + /** + * Retourne la liste des nœuds du graphe triés par identifiant croissant. + * + * @return liste des nœuds triés par ID + */ private List sortNodes() { - List nodes = new ArrayList<>(adjEdList.keySet()); - nodes.sort(Comparator.comparingInt(Node::getId)); - return nodes; + return adjEdList.keySet().stream() + .sorted(Comparator.comparingInt(Node::getId)) + .toList(); } + } diff --git a/src/main/java/m1graphs2025/Node.java b/src/main/java/m1graphs2025/Node.java index 40fa05e..1781182 100644 --- a/src/main/java/m1graphs2025/Node.java +++ b/src/main/java/m1graphs2025/Node.java @@ -2,19 +2,11 @@ package m1graphs2025; import java.util.*; -/** - * Représente un nœud (Node) dans un graphe. - * Un nœud est défini par un identifiant unique au sein d’un graphe donné - * et peut posséder un nom optionnel pour faciliter sa lecture. - * - * Cette classe implémente l’interface Comparable afin de permettre - * le tri des nœuds selon leur identifiant. - */ public class Node implements Comparable { - // ====================== - // Attributs - // ====================== + // ================================================================================================================= + // =================================================Attributs======================================================= + // ================================================================================================================= /** Identifiant unique du nœud dans le graphe */ private int id; @@ -25,9 +17,9 @@ public class Node implements Comparable { /** Nom optionnel du nœud (facultatif) */ private String name; - // ====================== - // Constructeurs - // ====================== + // ================================================================================================================= + // ===========================================Constructeurs========================================================= + // ================================================================================================================= /** * Construit un nœud identifié uniquement par son identifiant et son graphe. @@ -67,9 +59,9 @@ public class Node implements Comparable { this.graph = graph; } - // ====================== - // Getters - // ====================== + // ================================================================================================================= + // =============================================Getters============================================================= + // ================================================================================================================= /** * Retourne l’identifiant du nœud. @@ -98,9 +90,9 @@ public class Node implements Comparable { return this.name; } - // ====================== - // Setters - // ====================== + // ================================================================================================================= + // ================================================Setters========================================================== + // ================================================================================================================= /** * Définit un nouvel identifiant pour ce nœud. @@ -120,9 +112,9 @@ public class Node implements Comparable { this.name = name; } - // ====================== - // Méthodes utilitaires - // ====================== + // ================================================================================================================= + // ============================================Méthodes utilitaires================================================= + // ================================================================================================================= /** * Vérifie si un nœud existe déjà dans une liste donnée. @@ -359,9 +351,9 @@ public class Node implements Comparable { @Override public String toString() { if (name != null && !name.isEmpty()) { - return "Node(" + id + ", \"" + name + "\")"; + return id + ", \"" + name ; } else { - return "Node(" + id + ")"; + return String.valueOf(id); } } } diff --git a/src/main/java/m1graphs2025/NodeVisitInfo.java b/src/main/java/m1graphs2025/NodeVisitInfo.java index ba21d1a..e8d4469 100644 --- a/src/main/java/m1graphs2025/NodeVisitInfo.java +++ b/src/main/java/m1graphs2025/NodeVisitInfo.java @@ -18,9 +18,9 @@ public class NodeVisitInfo { private Integer discoveryTime; // Temps de découverte (d) private Integer finishTime; // Temps de fin (f) - // ============================ - // Constructeurs - // ============================ + // ================================================================================================================= + // ===========================================Constructeurs========================================================= + // ================================================================================================================= /** * Constructeur par défaut : @@ -48,52 +48,99 @@ public class NodeVisitInfo { this.finishTime = finishTime; } - // ============================ - // Getters - // ============================ + // ================================================================================================================= + // ===============================================Getters=========================================================== + // ================================================================================================================= + /** + * Retourne la couleur actuelle du nœud (utilisée pour les parcours DFS/BFS). + * + * @return la couleur du nœud + */ public NodeColour getColour() { return colour; } + /** + * Retourne le prédécesseur du nœud dans un parcours (DFS ou BFS). + * + * @return le nœud prédécesseur ou null si aucun + */ public Node getPredecessor() { return predecessor; } + /** + * Retourne le temps de découverte du nœud lors d'un parcours DFS. + * + * @return le temps de découverte ou null si non encore visité + */ public Integer getDiscoveryTime() { return discoveryTime; } + /** + * Retourne le temps de fin du nœud lors d'un parcours DFS. + * + * @return le temps de fin ou null si non encore terminé + */ public Integer getFinishTime() { return finishTime; } - // ============================ - // Setters - // ============================ + // ================================================================================================================= + // ==============================================Setters============================================================ + // ================================================================================================================= + + /** + * Définit la couleur du nœud (utilisée pour les parcours DFS/BFS). + * + * @param colour la couleur à assigner au nœud + */ public void setColour(NodeColour colour) { this.colour = colour; } + /** + * Définit le prédécesseur du nœud dans un parcours (DFS ou BFS). + * + * @param predecessor le nœud prédécesseur + */ public void setPredecessor(Node predecessor) { this.predecessor = predecessor; } + /** + * Définit le temps de découverte du nœud lors d'un parcours DFS. + * + * @param discoveryTime le temps de découverte + */ public void setDiscoveryTime(Integer discoveryTime) { this.discoveryTime = discoveryTime; } + /** + * Définit le temps de fin du nœud lors d'un parcours DFS. + * + * @param finishTime le temps de fin + */ public void setFinishTime(Integer finishTime) { this.finishTime = finishTime; } - // ============================ - // Méthodes utilitaires - // ============================ + + // ================================================================================================================= + // ===========================================Méthodes utilitaires================================================== + // ================================================================================================================= /** - * Réinitialise les informations de visite (utile avant un nouveau parcours). + * Réinitialise toutes les informations de visite du nœud. + * Utile avant de lancer un nouveau parcours DFS ou BFS. + * + * - La couleur est remise à WHITE + * - Le prédécesseur est remis à null + * - Le temps de découverte et de fin sont remis à null */ public void reset() { this.colour = NodeColour.WHITE; @@ -101,4 +148,26 @@ public class NodeVisitInfo { this.discoveryTime = null; this.finishTime = null; } + + /** + * Retourne une représentation textuelle des informations de visite du nœud. + * + * Affiche la couleur, l'identifiant du prédécesseur (ou ∅ si nul), + * le temps de découverte et le temps de fin (ou ∅ si non définis). + * + * @return chaîne décrivant l'état du nœud + */ + @Override + public String toString() { + String predStr = (predecessor != null) ? String.valueOf(predecessor.getId()) : "∅"; + String dStr = (discoveryTime != null) ? discoveryTime.toString() : "∅"; + String fStr = (finishTime != null) ? finishTime.toString() : "∅"; + + return String.format( + "{colour=%s, predecessor=%s, discovery=%s, finished=%s}", + colour, predStr, dStr, fStr + ); + } + + } diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java index 9774326..6e96b34 100644 --- a/src/main/java/m1graphs2025/UndirectedGraph.java +++ b/src/main/java/m1graphs2025/UndirectedGraph.java @@ -7,118 +7,161 @@ 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; -/** - * 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 - // ====================== + // ================================================================================================================= + // ===========================================CONSTRUCTEURS========================================================= + // ================================================================================================================= + + /** + * Construit un graphe non orienté en utilisant une liste d’adjacence fournie. + * + * @param adjEdList Map associant chaque nœud à la liste de ses arêtes sortantes + */ public UndirectedGraph(Map> adjEdList) { super(adjEdList); } + /** + * Construit un graphe non orienté vide. + */ public UndirectedGraph() { super(new HashMap<>()); // graphe vide } + /** + * Construit un graphe non orienté à partir d’un tableau de successeurs. + * Chaque nœud est numéroté à partir de 1 et le tableau utilise 0 pour + * séparer les listes de successeurs de chaque nœud. Les arêtes sont ajoutées + * dans les deux sens pour assurer la non-orientation. + * + * @param successorArray tableau des successeurs avec 0 comme séparateur de listes + */ public UndirectedGraph(int... successorArray) { int taille = successorArray.length; Map> adjEdList = new HashMap<>(); + Map nodes = 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); + Node node = nodes.computeIfAbsent(id, key -> new Node(key, this)); + List list = adjEdList.computeIfAbsent(node, key -> new ArrayList<>()); while (i < taille && successorArray[i] != 0) { - int succ = successorArray[i]; - if (!nodes.containsKey(succ)) { - nodes.put(succ, new Node(succ, this)); - } - i++; - } + int succId = successorArray[i]; + Node nodeTo = nodes.computeIfAbsent(succId, key -> new Node(key, this)); - 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<>()); + list.add(new Edge(node, nodeTo)); - 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); + if (node.getId() != nodeTo.getId()) { + adjEdList + .computeIfAbsent(nodeTo, key -> new ArrayList<>()) + .add(new Edge(nodeTo, node)); + } i++; } - i++; // sauter le 0 id++; + i++; } this.adjEdList = adjEdList; } + // ================================================================================================================= + // =======================================API DES NOEUDS (Override)================================================= + // ================================================================================================================= + /** + * Ajoute un nœud au graphe non orienté. + * + * @param n le nœud à ajouter + * @return true si le nœud a été ajouté avec succès, false sinon + */ @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). + * Calcule le degré d’un nœud dans un graphe non orienté. + * Le degré est le nombre d'arêtes incidentes au nœud. + * + * @param n le nœud dont on souhaite calculer le degré + * @return le degré du nœud, ou 0 si le nœud est null ou une erreur survient */ - 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); - } + @Override + public int degree(Node n) { + if (n == null) { + System.err.println("Erreur: nœud nul dans degree()."); + return 0; + } + try { + return n.degree() / 2; + } catch (Exception e) { + System.err.println("Erreur lors du calcul du degré du nœud " + n.getId() + ": " + e.getMessage()); + return 0; } } - // ====================== - // API DES ARÊTES (Override) - // ====================== + /** + * Calcule le degré d’un nœud identifié par son identifiant dans un graphe non orienté. + * + * @param id l'identifiant du nœud + * @return le degré du nœud, ou 0 si aucun nœud correspondant n’est trouvé + */ + @Override + public int degree(int id) { + Node n = getNode(id); + if (n == null) { + System.err.println("Erreur: aucun nœud trouvé avec l'identifiant " + id + " dans degree()."); + return 0; + } + return degree(n); + } + + // ================================================================================================================= + // ====================================API DES ARÊTES (Override)==================================================== + // ================================================================================================================= + /** + * Ajoute une arête non dirigée entre deux nœuds. + * Cela crée deux arêtes dirigées symétriques dans le graphe sous-jacent. + * + * @param from le nœud source + * @param to le nœud destination + */ @Override public void addEdge(Node from, Node to) { super.addEdge(from, to); // u -> v super.addEdge(to, from); // v -> u pour non dirigé } + /** + * Ajoute une arête non dirigée 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 + */ @Override public void addEdge(Node from, Node to, int weight) { super.addEdge(from, to, weight); super.addEdge(to, from, weight); } + /** + * Ajoute une arête non dirigée entre deux nœuds identifiés par leurs IDs. + * + * @param fromId l'identifiant du nœud source + * @param toId l'identifiant du nœud destination + */ @Override public void addEdge(int fromId, int toId) { Node from = getNode(fromId); @@ -128,6 +171,13 @@ public class UndirectedGraph extends Graph { addEdge(from, to); } + /** + * Ajoute une arête non dirigée pondérée entre deux nœuds identifiés par leurs IDs. + * + * @param fromId l'identifiant du nœud source + * @param toId l'identifiant du nœud destination + * @param weight le poids de l'arête + */ @Override public void addEdge(int fromId, int toId, int weight) { Node from = getNode(fromId); @@ -137,6 +187,13 @@ public class UndirectedGraph extends Graph { addEdge(from, to, weight); } + /** + * Supprime une arête non dirigée entre deux nœuds. + * + * @param from le nœud source + * @param to le nœud destination + * @return true si une des arêtes a été supprimée, false sinon + */ @Override public boolean removeEdge(Node from, Node to) { boolean r1 = super.removeEdge(from, to); @@ -144,6 +201,13 @@ public class UndirectedGraph extends Graph { return r1 || r2; } + /** + * Supprime une arête non dirigée entre deux nœuds identifiés par leurs IDs. + * + * @param fromId l'identifiant du nœud source + * @param toId l'identifiant du nœud destination + * @return true si une des arêtes a été supprimée, false sinon + */ @Override public boolean removeEdge(int fromId, int toId) { boolean r1 = super.removeEdge(fromId, toId); @@ -151,6 +215,12 @@ public class UndirectedGraph extends Graph { return r1 || r2; } + /** + * Supprime une arête non dirigée, y compris son symétrique. + * + * @param e l'arête à supprimer + * @return true si l'arête ou son symétrique a été supprimée, false sinon + */ @Override public boolean removeEdge(Edge e) { boolean r1 = super.removeEdge(e); @@ -159,22 +229,47 @@ public class UndirectedGraph extends Graph { return r1 || r2; } + /** + * Vérifie l'existence d'une arête entre deux nœuds dans un graphe non dirigé. + * + * @param u le premier nœud + * @param v le second nœud + * @return true si une arête existe dans une des deux directions, false sinon + */ @Override public boolean existsEdge(Node u, Node v) { return super.existsEdge(u, v) || super.existsEdge(v, u); } + /** + * Vérifie l'existence d'une arête entre deux nœuds identifiés par leurs IDs. + * + * @param uId l'identifiant du premier nœud + * @param vId l'identifiant du second nœud + * @return true si une arête existe dans une des deux directions, false sinon + */ @Override public boolean existsEdge(int uId, int vId) { return existsEdge(getNode(uId), getNode(vId)); } + /** + * Vérifie si plusieurs arêtes existent entre deux nœuds dans un graphe non dirigé. + * + * @param u le premier nœud + * @param v le second nœud + * @return true si plusieurs arêtes existent dans une des deux directions, false sinon + */ @Override public boolean isMultiEdge(Node u, Node v) { - // Vérifie dans les deux directions return super.isMultiEdge(u, v) || super.isMultiEdge(v, u); } + /** + * Crée une copie complète du graphe non dirigé. + * + * @return une nouvelle instance de UndirectedGraph identique au graphe actuel + */ @Override public UndirectedGraph copy() { Map> copy = new HashMap<>(); @@ -187,6 +282,21 @@ public class UndirectedGraph extends Graph { return new UndirectedGraph(copy); } + // ================================================================================================================= + // ========================================Représentation et transformations======================================== + // ================================================================================================================= + + /** + * Calcule la fermeture transitive d'un graphe non dirigé. + *

+ * La fermeture transitive est un graphe où, pour chaque paire de nœuds + * (i, j), une arête existe si et seulement s'il existe un chemin entre + * i et j dans le graphe original. + *

+ * Pour un graphe non dirigé, toutes les arêtes sont symétriques. + * + * @return un nouvel objet UndirectedGraph représentant la fermeture transitive + */ @Override public UndirectedGraph getTransitiveClosure() { UndirectedGraph closure = this.copy(); @@ -204,9 +314,26 @@ public class UndirectedGraph extends Graph { } return closure; } - // ====================== + + + // ================================================================================================================= + // ===============================================DOT I/O=========================================================== + // ================================================================================================================= + + /** + * Charge un graphe non dirigé à partir d'un fichier DOT. + *

+ * Chaque ligne de type "id1 -- id2 [label=weight];" est interprétée comme une arête + * non dirigée entre les nœuds id1 et id2 avec un poids optionnel. + * Les nœuds isolés (sans arêtes) sont également créés si présents dans le fichier. + * + * @param filename le chemin du fichier DOT à lire + * @return un objet UndirectedGraph correspondant au graphe décrit dans le fichier + * @throws RuntimeException si le fichier ne peut pas être lu + */ public static UndirectedGraph fromDotFile(String filename) { Map> adjEdList = new HashMap<>(); + Map nodes = new HashMap<>(); UndirectedGraph g = new UndirectedGraph(adjEdList); try (BufferedReader dot = new BufferedReader(new FileReader(filename))) { @@ -214,34 +341,44 @@ public class UndirectedGraph extends Graph { while ((line = dot.readLine()) != null) { line = line.trim(); - if (line.isEmpty()) continue; // ignore lignes vides + if (line.isEmpty() || line.startsWith("graph") || line.startsWith("digraph") + || line.equals("{") || line.equals("}")) { + continue; // Ignore les lignes de structure DOT + } - // Cas 1 : Ligne contenant seulement un numéro → nœud isolé - if (line.matches("^\\d+$")) { - int id = Integer.parseInt(line); - Node isolated = new Node(id, g, ""); + if (line.matches("^\\d+;?$")) { + int id = Integer.parseInt(line.replace(";", "")); + Node isolated = nodes.computeIfAbsent(id, key -> new Node(key, g)); adjEdList.putIfAbsent(isolated, 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 + if (line.contains("--")) { + String[] parts = line.split("--"); + if (parts.length < 2) continue; - int fromId = Integer.parseInt(tokens[0]); - int toId = Integer.parseInt(tokens[1]); - int weight = Integer.parseInt(tokens[2]); + int fromId = Integer.parseInt(parts[0].replaceAll("[^0-9]", "")); + String toPart = parts[1].trim(); + int toId = Integer.parseInt(toPart.replaceAll("[^0-9].*$", "")); - Node nFrom = new Node(fromId, g, ""); - Node nTo = new Node(toId, g, ""); - Edge edge = new Edge(nFrom, nTo, weight); + int weight = 1; // poids par défaut + Matcher m = Pattern.compile("label\\s*=\\s*\"?(\\d+)\"?").matcher(line); + if (m.find()) { + weight = Integer.parseInt(m.group(1)); + } - // Ajout de l’arête dans la liste d’adjacence - adjEdList.computeIfAbsent(nFrom, k -> new ArrayList<>()).add(edge); + Node from = nodes.computeIfAbsent(fromId, key -> new Node(key, g)); + Node to = nodes.computeIfAbsent(toId, key -> new Node(key, g)); - // S’assurer que le nœud destination existe - adjEdList.putIfAbsent(nTo, new ArrayList<>()); + adjEdList.computeIfAbsent(from, key -> new ArrayList<>()) + .add(new Edge(from, to, weight)); + if (from.getId() != to.getId()) { + adjEdList.computeIfAbsent(to, key -> new ArrayList<>()) + .add(new Edge(to, from, weight)); + } + } } + } catch (IOException e) { throw new RuntimeException("Erreur lors de la lecture du fichier : " + filename, e); } @@ -249,57 +386,90 @@ public class UndirectedGraph extends Graph { return g; } + /** + * Génère une représentation DOT du graphe non dirigé. + *

+ * Les arêtes sont affichées sous la forme "id1 -- id2", avec un label optionnel + * correspondant au poids. Les doublons sont évités et les self-loops sont affichés. + * + * @return une chaîne au format DOT représentant le graphe + */ public String toDotString() { + String arrow = " -- "; + 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("graph {\n"); + sb.append(" rankdir=LR;\n"); + + Set printedEdges = new HashSet<>(); // pour éviter les doublons + + List sortedNodes = new ArrayList<>(adjEdList.keySet()); + sortedNodes.sort(Comparator.comparingInt(Node::getId)); + + for (Node from : sortedNodes) { + List edges = adjEdList.get(from); + + if (edges.isEmpty()) { + continue; } - 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 (Edge e : edges) { + Node u = e.from(); + Node v = e.to(); - for (Node node : adjEdList.keySet()) { - if (!nodesWithEdges.contains(node)) { - sb.append("\t").append(node.getId()).append(";\n"); + if (u.equals(v)) { + sb.append(" ").append(u.getId()) + .append(arrow) + .append(v.getId()); + if (e.getWeight() != null) { + sb.append(" [label=").append(e.getWeight()).append("]"); + } + sb.append("\n"); + continue; + } + + String edgeKey = u.getId() <= v.getId() ? u.getId() + "-" + v.getId() + : v.getId() + "-" + u.getId(); + if (printedEdges.contains(edgeKey)) continue; + + sb.append(" ").append(u.getId()) + .append(arrow) + .append(v.getId()); + if (e.getWeight() != null) { + sb.append(" [label=").append(e.getWeight()).append("]"); + } + sb.append("\n"); + + printedEdges.add(edgeKey); } } - sb.append("}\n"); + sb.append("}"); return sb.toString(); } + /** + * Exporte le graphe vers un fichier DOT avec l'extension par défaut ".gv". + * + * @param filename le nom du fichier à créer + */ public void toDotFile(String filename) { super.toDotFile(filename); } + /** + * Exporte le graphe vers un fichier DOT avec l'extension spécifiée. + * + * @param filename le nom du fichier à créer + * @param ext l'extension du fichier (ex: ".dot", ".gv") + */ public void toDotFile(String filename, String ext) { super.toDotFile(filename, ".gv"); } - // ====================== - // Utilitaires - // ====================== + // ================================================================================================================= + // =============================================Utilitaires========================================================= + // ================================================================================================================= /** * Indique que ce graphe est non orienté (utile pour certaines fonctions). diff --git a/src/main/resources/lectureDFS.gv b/src/main/resources/lectureDFS.gv new file mode 100644 index 0000000..d4a05a3 --- /dev/null +++ b/src/main/resources/lectureDFS.gv @@ -0,0 +1,16 @@ +digraph { + rankdir=LR + 1 -> 2 + 1 -> 3 + 1 -> 6 + 2 -> 5 + 3 -> 4 + 4 -> 1 + 4 -> 8 + 5 -> 6 + 5 -> 7 + 5 -> 8 + 6 -> 2 + 6 -> 7 + 8 -> 7 +} \ No newline at end of file diff --git a/src/test/java/m1graphs2025/TestGraphPart6.java b/src/test/java/m1graphs2025/TestGraphPart6.java new file mode 100644 index 0000000..0cbe7d2 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart6.java @@ -0,0 +1,34 @@ +package m1graphs2025; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TestGraphPart6 { + + public static void main(String[] args) { + System.out.println("*-----------------------------------------------------------------------*"); + System.out.println("************ PART 6. DFS and Node Visit Info ***********************"); + System.out.println("*-----------------------------------------------------------------------*"); + + // Lecture VS Example + Graph gLecture = Graph.fromDotFile("src/main/resources/lectureDFS.gv"); + System.out.println("Graph read as:"); + System.out.println(gLecture.toDotString()); + + MapnodeVisit = new HashMap(); + MapedgeVisit = new HashMap(); + + gLecture.getDFSWithVisitInfo(nodeVisit, edgeVisit); + + System.out.println("Nodes visit info\n-----------------\n"); + for (Node u: gLecture.getAllNodes()) { + System.out.println(u+": "+nodeVisit.get(u) ); + } + + System.out.println("Edges visit info\n-----------------\n"); + for (Edge e: gLecture.getAllEdges()) { + System.out.println(e+": "+edgeVisit.get(e) ); + } + } +} diff --git a/src/main/java/m1graphs2025/TestGraphsPW2.java b/src/test/java/m1graphs2025/TestsGraphPW2.java similarity index 64% rename from src/main/java/m1graphs2025/TestGraphsPW2.java rename to src/test/java/m1graphs2025/TestsGraphPW2.java index 96a9d7c..04b95d1 100644 --- a/src/main/java/m1graphs2025/TestGraphsPW2.java +++ b/src/test/java/m1graphs2025/TestsGraphPW2.java @@ -1,10 +1,11 @@ -/**package m1graphs2025; +package m1graphs2025; import java.util.Arrays; import java.util.Collections; import java.util.List; -public class TestGraphsPW2 { + +public class TestsGraphPW2 { public static void main(String[] args) { System.out.println("*--------------------------------------------------------------------*"); @@ -14,30 +15,29 @@ public class TestGraphsPW2 { System.out.println(">>>>>>>> Creating the subject example graph in G"); Graph g = new Graph(2, 4, 0, 0, 6, 0, 2, 3, 5, 8, 0, 0, 4, 7, 0, 3, 0, 7, 0); System.out.println(">>>> Graph information"); - System.out.println(">> DOT representation\n" + g.toDotString()); - System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + System.out.println(">> DOT representation\n"+g.toDotString()); + System.out.println(""+g.nbNodes()+" nodes, "+g.nbEdges()+" edges"); System.out.println(">> Nodes: "); List nodes = g.getAllNodes(); Collections.sort(nodes); - for (Node n : nodes) - System.out.println("Node " + n + ": degree " + g.degree(n) + " (in: " + g.inDegree(n) + "/ out: " - + g.outDegree(n) + ")"); + for (Node n: nodes) + System.out.println("Node "+n+": degree "+g.degree(n)+" (in: "+g.inDegree(n)+"/ out: "+g.outDegree(n)+")"); List edges; System.out.println(">> Edges: "); System.out.println("---------------------------"); System.out.println("Out-edges per node"); - for (Node n : nodes) { + for (Node n: nodes) { edges = g.getOutEdges(n); Collections.sort(edges); - System.out.println("" + n + ": " + edges); + System.out.println(""+n+": "+edges); } System.out.println("In-edges per node"); - for (Node n : nodes) { + for (Node n: nodes) { edges = g.getInEdges(n); Collections.sort(edges); - System.out.println("" + n + ": " + edges); + System.out.println(""+n+": "+edges); } ///////////////////////////////////////////////////// @@ -47,38 +47,37 @@ public class TestGraphsPW2 { System.out.println("Error: failed to create node 12"); System.out.println("Graph now:"); System.out.println(g.toDotString()); - System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + System.out.println(""+g.nbNodes()+" nodes, "+g.nbEdges()+" edges"); nodes = g.getAllNodes(); Collections.sort(nodes); - System.out.println("Nodes list: " + nodes); + System.out.println("Nodes list: "+nodes); System.out.println("\n>>>>>>>> Removing node 3"); g.removeNode(3); System.out.println("Graph now:"); System.out.println(g.toDotString()); - System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + System.out.println(""+g.nbNodes()+" nodes, "+g.nbEdges()+" edges"); nodes = g.getAllNodes(); Collections.sort(nodes); - System.out.println("Nodes list: " + nodes); + System.out.println("Nodes list: "+nodes); System.out.println(">> Edges: "); System.out.println("---------------------------"); System.out.println("Out-edges per node"); - for (Node n : nodes) { + for (Node n: nodes) { edges = g.getOutEdges(n); Collections.sort(edges); - System.out.println("" + n + ": " + edges); + System.out.println(""+n+": "+edges); } System.out.println("In-edges per node"); - for (Node n : nodes) { + for (Node n: nodes) { edges = g.getInEdges(n); Collections.sort(edges); - System.out.println("" + n + ": " + edges); + System.out.println(""+n+": "+edges); } - System.out.println( - "\n>>>>>>>> Recreating edges (4, 3), (3, 6), (7, 3), adding edge (12, 3), creating edge (3, 25)"); + System.out.println("\n>>>>>>>> Recreating edges (4, 3), (3, 6), (7, 3), adding edge (12, 3), creating edge (3, 25)"); g.addEdge(new Edge(4, 3, g)); g.addEdge(new Edge(3, 6, g)); g.addEdge(new Edge(7, 3, g)); @@ -86,10 +85,10 @@ public class TestGraphsPW2 { g.addEdge(3, 25); System.out.println("Graph now:"); System.out.println(g.toDotString()); - System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + System.out.println(""+g.nbNodes()+" nodes, "+g.nbEdges()+" edges"); nodes = g.getAllNodes(); Collections.sort(nodes); - System.out.println("Nodes list: " + nodes); + System.out.println("Nodes list: "+nodes); System.out.println(""); System.out.println("\n>>>>>>>> Edges removal"); @@ -98,8 +97,7 @@ public class TestGraphsPW2 { g.removeEdge(4, 8); System.out.println(">>>> Removing absent edge (3, 4)"); g.removeEdge(3, 4); - System.out.println( - ">>>> Removing edges whith 1 or 2 not existing end-points: (-3, 4), (6, 0), (4, 11), (-1, -2), (13, 3), (9, 10)"); + System.out.println(">>>> Removing edges whith 1 or 2 not existing end-points: (-3, 4), (6, 0), (4, 11), (-1, -2), (13, 3), (9, 10)"); g.removeEdge(-3, 4); g.removeEdge(6, 0); g.removeEdge(4, 11); @@ -109,49 +107,49 @@ public class TestGraphsPW2 { System.out.println("Graph now:"); System.out.println(g.toDotString()); - System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + System.out.println(""+g.nbNodes()+" nodes, "+g.nbEdges()+" edges"); nodes = g.getAllNodes(); Collections.sort(nodes); - System.out.println("Nodes list: " + nodes); + System.out.println("Nodes list: "+nodes); - System.out.println( - "\nTesting that getSuccessors and getSuccessorsMulti give the same result for the simple graph:"); + System.out.println("\nTesting that getSuccessors and getSuccessorsMulti give the same result for the simple graph:"); boolean same = true; - for (Node u : nodes) { + for (Node u: nodes) { List succs = g.getSuccessors(u), succsMulti = g.getSuccessorsMulti(u); // sort the lists so that nodes always appear in the same order Collections.sort(succs); Collections.sort(succsMulti); same = same && succs.equals(succsMulti); } - System.out.println("\tgetSuccessors and getSuccessorsMulti " + (same ? "are identical" : "differ")); + System.out.println("\tgetSuccessors and getSuccessorsMulti "+(same?"are identical":"differ")); + + System.out.println("\n>>>>>>>> MULTIGRAPH: adding a self-loop on node 6, and a second edge (1, 4)"); g.addEdge(6, 6); g.addEdge(1, 4); System.out.println("Graph now:"); System.out.println(g.toDotString()); - System.out.println("" + g.nbNodes() + " nodes, " + g.nbEdges() + " edges"); + System.out.println(""+g.nbNodes()+" nodes, "+g.nbEdges()+" edges"); nodes = g.getAllNodes(); Collections.sort(nodes); - System.out.println("Nodes list: " + nodes); - System.out.println( - "Degree of node 6: " + g.degree(6) + " (in: " + g.inDegree(6) + "/ out: " + g.outDegree(6) + ")"); + System.out.println("Nodes list: "+nodes); + System.out.println("Degree of node 6: "+g.degree(6)+" (in: "+g.inDegree(6)+"/ out: "+g.outDegree(6)+")"); System.out.println(">> Edges: "); System.out.println("---------------------------"); System.out.println("Out-edges per node"); - for (Node n : nodes) { + for (Node n: nodes) { edges = g.getOutEdges(n); Collections.sort(edges); - System.out.println("" + n + ": " + edges); + System.out.println(""+n+": "+edges); } System.out.println("In-edges per node"); - for (Node n : nodes) { + for (Node n: nodes) { edges = g.getInEdges(n); Collections.sort(edges); - System.out.println("" + n + ": " + edges); + System.out.println(""+n+": "+edges); } System.out.println("\n>>>>>>>>>> Get the reverse graph"); @@ -162,7 +160,7 @@ public class TestGraphsPW2 { System.out.println(">>>>>>>>>> Emptying the graph by removing all its nodes"); nodes = g.getAllNodes(); - for (Node u : nodes) + for (Node u: nodes) g.removeNode(u); System.out.println("Graph now:"); System.out.println(g.toDotString()); @@ -184,8 +182,8 @@ public class TestGraphsPW2 { System.out.println("********* PART 2. READING GRAPHS FROM DOT FILES ****************"); System.out.println("*------------------------------------------------------------------*"); System.out.println("\n>>> Graph with isolated nodes: reading file 'isolatedNodes.gv'"); - Graph gin = Graph.fromDotFile("isolatedNodes"); - if (gin == null) + Graph gin = Graph.fromDotFile("isolatedNodes.gv"); + if (gin==null) System.out.println("Null graph was created from 'isolatedNodes.gv'"); else { System.out.println("Read: OK. The graph with isolated nodes has been read as:"); @@ -195,8 +193,8 @@ public class TestGraphsPW2 { } System.out.println(">>> Simple graph: reading file 'simpleGraph.gv'"); - Graph sg = Graph.fromDotFile("simpleGraph"); - if (sg == null) + Graph sg = Graph.fromDotFile("simpleGraph.gv"); + if (sg==null) System.out.println("Null graph was created from 'simpleGraph.gv'"); else { System.out.println("Read: OK. The simple graph has been read as:"); @@ -206,8 +204,8 @@ public class TestGraphsPW2 { } System.out.println("\n>>> Multi-graph: reading file 'multiGraph.gv'"); - Graph mg = Graph.fromDotFile("multiGraph"); - if (mg == null) + Graph mg = Graph.fromDotFile("multiGraph.gv"); + if (mg==null) System.out.println("Null graph was created from 'multiGraph.gv'"); else { System.out.println("Read: OK. The multi-graph has been read as:"); @@ -217,15 +215,17 @@ public class TestGraphsPW2 { } System.out.println("Comparing single and multi successors per node for 'multiGraph.gv'"); - for (Node u : mg.getAllNodes()) { + for (Node u: mg.getAllNodes()) { List succs = mg.getSuccessors(u), succsMulti = mg.getSuccessorsMulti(u); // sort the lists so that nodes always appear in the same order Collections.sort(succs); Collections.sort(succsMulti); - System.out.println("" + u + " single successors: " + succs); - System.out.println("" + u + " multi successors: " + succsMulti); + System.out.println(""+u+" single successors: "+succs); + System.out.println(""+u+" multi successors: "+succsMulti); } + + System.out.println("\n\n"); System.out.println("*----------------------------------------------------------------------*"); System.out.println("************* PART 3. WEIGHTED DIRECTED GRAPHS ***********************"); @@ -234,8 +234,8 @@ public class TestGraphsPW2 { System.out.println("\n>>>>>>>>>>"); System.out.println("Reading a weighted directed simple graph from DOT file 'weightedSimpleGraph.gv'"); - Graph wsg = Graph.fromDotFile("weightedSimpleGraph"); - if (wsg == null) + Graph wsg = Graph.fromDotFile("weightedSimpleGraph.gv"); + if (wsg==null) System.out.println("Null graph was created from 'weightedSimpleGraph.gv'"); else { System.out.println("Read: OK. The weighted directed simple graph has been read as:"); @@ -243,15 +243,15 @@ public class TestGraphsPW2 { System.out.println(wsg.toDotString()); System.out.println("---------------------"); - for (Edge e : wsg.getAllEdges()) + for (Edge e: wsg.getAllEdges()) totalEdgesWeight += e.getWeight(); - System.out.println("The sum of all edges weights equals " + totalEdgesWeight); + System.out.println("The sum of all edges weights equals "+totalEdgesWeight); } System.out.println("\n>>>>>>>>>>"); System.out.println("Reading a weighted directed multi graph from DOT file 'weightedMultiGraph.gv'"); - Graph wmg = Graph.fromDotFile("weightedMultiGraph"); - if (wmg == null) + Graph wmg = Graph.fromDotFile("weightedMultiGraph.gv"); + if (wmg==null) System.out.println("Null graph was created from 'weightedMultiGraph.gv'"); else { System.out.println("Read: OK. The weighted directed multi graph has been read as:"); @@ -260,110 +260,113 @@ public class TestGraphsPW2 { System.out.println("---------------------"); totalEdgesWeight = 0; - for (Edge e : wmg.getAllEdges()) + for (Edge e: wmg.getAllEdges()) totalEdgesWeight += e.getWeight(); - System.out.println("The sum of all edges weights equals " + totalEdgesWeight); + System.out.println("The sum of all edges weights equals "+totalEdgesWeight); } + + + System.out.println("\n\n"); System.out.println("*-------------------------------------------------------------------------*"); System.out.println("************ PART 4. UNDIRECTED UNWEIGHTED GRAPHS ***********************"); System.out.println("*-------------------------------------------------------------------------*"); System.out.println("\nCreating an undirected simple graph 'usg' from scracth"); - UndirectedGraph usg = new UndirectedGraph(2, 3, 0, 3, 4, 0, 4, 0, 0); + UndirectedGraph usg = new UndirectedGraph(2,3,0, 3,4,0, 4,0, 0); System.out.println(usg.toDotString()); - System.out.println("usg has " + usg.nbNodes() + " nodes and " + usg.nbEdges() + " edges."); + System.out.println("usg has "+usg.nbNodes()+" nodes and "+usg.nbEdges()+" edges."); System.out.println("\n>>>>>> usg: Counting degrees and showing successors"); - for (Node u : usg.getAllNodes()) { - System.out.println("Node " + u + ". Degree: " + usg.degree(u.getId()) + " (In: " + usg.inDegree(u.getId()) - + " / Out: " + usg.outDegree(u.getId()) + ")"); - System.out.println("\tSuccessors: " + usg.getSuccessors(u)); + for (Node u: usg.getAllNodes()) { + System.out.println("Node "+u+". Degree: "+usg.degree(u.getId())+" (In: "+usg.inDegree(u.getId())+" / Out: "+usg.outDegree(u.getId())+")"); + System.out.println("\tSuccessors: "+usg.getSuccessors(u)); } System.out.println(">>>>>> usg: Edges of the graph"); System.out.println("// N.B. The edges are printed as though they were directed. This is due to the toString()\n" + "// method that was not overridden. It is possible to do better but not important.\n" + "// What is important is that each edge appears only once per direction."); - System.out.println("All edges of the graph: " + usg.getAllEdges()); + System.out.println("All edges of the graph: "+usg.getAllEdges()); System.out.println("Out-edges per node"); - for (Node u : usg.getAllNodes()) - System.out.println("" + u + ": " + usg.getOutEdges(u)); + for (Node u: usg.getAllNodes()) + System.out.println(""+u+": "+usg.getOutEdges(u)); System.out.println("In-edges per node"); - for (Node u : usg.getAllNodes()) - System.out.println("" + u + ": " + usg.getInEdges(u)); + for (Node u: usg.getAllNodes()) + System.out.println(""+u+": "+usg.getInEdges(u)); System.out.println("Incident edges per node"); - for (Node u : usg.getAllNodes()) - System.out.println("" + u + ": " + usg.getIncidentEdges(u)); + for (Node u: usg.getAllNodes()) + System.out.println(""+u+": "+usg.getIncidentEdges(u)); System.out.println("Creating an undirected multi-graph with self-loops 'umg' from scratch"); - UndirectedGraph umg = new UndirectedGraph(1, 1, 2, 2, 3, 0, 2, 3, 0, 0); + UndirectedGraph umg = new UndirectedGraph(1,1,2,2,3,0, 2,3,0, 0); String dotUMG = umg.toDotString(); System.out.println(dotUMG); - System.out.println("umg has " + umg.nbNodes() + " nodes and " + umg.nbEdges() + " edges."); + System.out.println("umg has "+umg.nbNodes()+" nodes and "+umg.nbEdges()+" edges."); + System.out.println("\n>>>>>> umg: Counting degrees and showing successors"); - for (Node u : umg.getAllNodes()) { - System.out.println("Node " + u + ". Degree: " + umg.degree(u.getId()) + " (In: " + umg.inDegree(u.getId()) - + " / Out: " + umg.outDegree(u.getId()) + ")"); - System.out.println("\tSuccessors: " + umg.getSuccessors(u)); + for (Node u: umg.getAllNodes()) { + System.out.println("Node "+u+". Degree: "+umg.degree(u.getId())+" (In: "+umg.inDegree(u.getId())+" / Out: "+umg.outDegree(u.getId())+")"); + System.out.println("\tSuccessors: "+umg.getSuccessors(u)); } System.out.println(">>>>>> umg: Edges of the graph"); - System.out.println("All edges of the graph: " + umg.getAllEdges()); + System.out.println("All edges of the graph: "+umg.getAllEdges()); System.out.println("Out-edges per node"); - for (Node u : umg.getAllNodes()) - System.out.println("" + u + ": " + umg.getOutEdges(u)); + for (Node u: umg.getAllNodes()) + System.out.println(""+u+": "+umg.getOutEdges(u)); System.out.println("In-edges per node"); - for (Node u : umg.getAllNodes()) - System.out.println("" + u + ": " + umg.getInEdges(u)); + for (Node u: umg.getAllNodes()) + System.out.println(""+u+": "+umg.getInEdges(u)); System.out.println("Incident edges per node"); - for (Node u : umg.getAllNodes()) - System.out.println("" + u + ": " + umg.getIncidentEdges(u)); + for (Node u: umg.getAllNodes()) + System.out.println(""+u+": "+umg.getIncidentEdges(u)); System.out.println("\n>>>>>> umg: Successor Array, Adjacency Matrix, and Graph Reverse"); - System.out.println("umg Successor array\n" + Arrays.toString(umg.toSuccessorArray())); + System.out.println("umg Successor array\n"+Arrays.toString(umg.toSuccessorArray())); System.out.println("umg Adjacency Matrix"); - for (int[] row : umg.toAdjMatrix()) - System.out.println("\t" + Arrays.toString(row)); + for (int[] row: umg.toAdjMatrix()) + System.out.println("\t"+Arrays.toString(row)); System.out.println("Testing via toDotString() the equality with the reverse graph"); String dotRUMG = umg.getReverse().toDotString(); - System.out.println("DOT of the reverse of umg\n" + dotRUMG); - System.out.println("Graph gu and its reverse " + (dotUMG.equals(dotRUMG) ? "are identical" : "differ")); + System.out.println("DOT of the reverse of umg\n"+dotRUMG); + System.out.println("Graph gu and its reverse "+(dotUMG.equals(dotRUMG)?"are identical":"differ")); System.out.println("-----------------\n NOW a disconnected GRAPH \n----------------"); System.out.println("Building 'guDisc', a disconnected undirected graph with multi-edges and self-loops"); - UndirectedGraph guDisc = new UndirectedGraph(1, 1, 2, 2, 6, 0, 2, 3, 6, 0, 0, 6, 0, 6, 0, 0, 0, 9, 10, 0, 0, 0); + UndirectedGraph guDisc = new UndirectedGraph(1,1,2,2,6,0, 2,3,6,0, 0, 6,0, 6,0, 0, 0, 9,10,0, 0, 0); System.out.println(guDisc.toDotString()); // delete - // Graph guDisc2 = new Graph(1,1,2,2,6,0, 2,3,6,0, 0, 6,0, 6,0, 0, 0, 9,10,0, 0, - // 0); - // System.out.println(guDisc2.toDotString()); - // System.exit(0); - // end delete +// Graph guDisc2 = new Graph(1,1,2,2,6,0, 2,3,6,0, 0, 6,0, 6,0, 0, 0, 9,10,0, 0, 0); +// System.out.println(guDisc2.toDotString()); +// System.exit(0); + //end delete System.out.println("Comparing single and multi successors per node for guDisc"); - for (Node u : guDisc.getAllNodes()) { + for (Node u: guDisc.getAllNodes()) { List succs = guDisc.getSuccessors(u), succsMulti = guDisc.getSuccessorsMulti(u); // sort the lists so that nodes always appear in the same order Collections.sort(succs); Collections.sort(succsMulti); - System.out.println("" + u + " single successors: " + succs); - System.out.println("" + u + " multi successors: " + succsMulti); + System.out.println(""+u+" single successors: "+succs); + System.out.println(""+u+" multi successors: "+succsMulti); } - System.out.println(">>>> DFS of guDisc: " + guDisc.getDFS()); - System.out.println(">>>> BFS of guDisc: " + guDisc.getBFS()); + + System.out.println(">>>> DFS of guDisc: "+guDisc.getDFS()); + System.out.println(">>>> BFS of guDisc: "+guDisc.getBFS()); System.out.println(">>>>>>> Computing guDisc's transitive closure"); - UndirectedGraph guDiscTC = (UndirectedGraph) guDisc.getTransitiveClosure(); + UndirectedGraph guDiscTC = guDisc.getTransitiveClosure(); System.out.println(guDiscTC.toDotString()); + System.out.println("\n\n"); System.out.println("*-----------------------------------------------------------------------*"); System.out.println("************ PART 5. UNDIRECTED WEIGHTED GRAPHS ***********************"); @@ -373,7 +376,7 @@ public class TestGraphsPW2 { + "from file 'undirWeightedMultiGraph.gv'"); UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("undirWeightedMultiGraph.gv"); - if (uwmg == null) + if (uwmg==null) System.out.println("Null graph was created from 'undirWeightedMultiGraph.gv'"); else { System.out.println("Read: OK. The undirected weighted multi-graph has been read as:"); @@ -382,48 +385,44 @@ public class TestGraphsPW2 { System.out.println("---------------------"); totalEdgesWeight = 0; - for (Edge e : uwmg.getAllEdges()) + for (Edge e: uwmg.getAllEdges()) totalEdgesWeight += e.getWeight(); - System.out.println("The sum of all edges weights equals " + totalEdgesWeight); + System.out.println("The sum of all edges weights equals "+totalEdgesWeight); } System.out.println("\nComparing single and multi successors per node for uwmg"); - for (Node u : uwmg.getAllNodes()) { + for (Node u: uwmg.getAllNodes()) { List succs = uwmg.getSuccessors(u), succsMulti = uwmg.getSuccessorsMulti(u); // sort the lists so that nodes always appear in the same order Collections.sort(succs); Collections.sort(succsMulti); - System.out.println("" + u + " single successors: " + succs); - System.out.println("" + u + " multi successors: " + succsMulti); + System.out.println(""+u+" single successors: "+succs); + System.out.println(""+u+" multi successors: "+succsMulti); } - /* - * TO BE CONTINUED ... - * System.out.println("\n\n"); - * System.out.println( - * "*-----------------------------------------------------------------------*"); - * System.out. - * println("************ PART 6. DFS and Node Visit Info ***********************" - * ); - * System.out.println( - * "*-----------------------------------------------------------------------*"); - * - * - * // lecture example but undirected - * UndirectedGraph gToVisit = new UndirectedGraph( - * 2, 3, 4, 6, 0, //1 - * 5, 6, 0, //2 - * 4, 0, //3 - * 8, 0, //4 - * 6, 7, 8, 0, //5 - * 7, 0, //6 - * 8,0, //7 - * 0 //8 - * ); - * System.out.println("\nAn undirected graph to visit:"); - * System.out.println(gToVisit.toDotString()); - */ - - /**} - -}**/ + + /* TO BE CONTINUED ... + System.out.println("\n\n"); + System.out.println("*-----------------------------------------------------------------------*"); + System.out.println("************ PART 6. DFS and Node Visit Info ***********************"); + System.out.println("*-----------------------------------------------------------------------*"); + + + // lecture example but undirected + UndirectedGraph gToVisit = new UndirectedGraph( + 2, 3, 4, 6, 0, //1 + 5, 6, 0, //2 + 4, 0, //3 + 8, 0, //4 + 6, 7, 8, 0, //5 + 7, 0, //6 + 8,0, //7 + 0 //8 + ); + System.out.println("\nAn undirected graph to visit:"); + System.out.println(gToVisit.toDotString()); + */ + + } + +} -- GitLab From f6bcdb35163a3c110d043afc7f5db05d1e33cc5a Mon Sep 17 00:00:00 2001 From: dcisse2 Date: Sun, 2 Nov 2025 11:59:20 +0100 Subject: [PATCH 5/9] *********** --- m1graphs25 | 2 +- src/main/java/m1graphs2025/Graph.java | 1 + src/main/java/m1graphs2025/UndirectedGraph.java | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/m1graphs25 b/m1graphs25 index bf6646e..483bf8d 160000 --- a/m1graphs25 +++ b/m1graphs25 @@ -1 +1 @@ -Subproject commit bf6646e2568cd77bd0ed40e9192c9b0126eda6ae +Subproject commit 483bf8d204f6d168a86ea622f4cc61d1aa9a5f23 diff --git a/src/main/java/m1graphs2025/Graph.java b/src/main/java/m1graphs2025/Graph.java index c0ab447..ef2372e 100644 --- a/src/main/java/m1graphs2025/Graph.java +++ b/src/main/java/m1graphs2025/Graph.java @@ -1573,6 +1573,7 @@ public class Graph { // Si le nœud n'a pas d'arêtes, c'est un nœud isolé if (edges.isEmpty()) { + sb.append(" ").append(from.getId()).append("\n"); continue; } diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java index 6e96b34..493f310 100644 --- a/src/main/java/m1graphs2025/UndirectedGraph.java +++ b/src/main/java/m1graphs2025/UndirectedGraph.java @@ -410,6 +410,7 @@ public class UndirectedGraph extends Graph { List edges = adjEdList.get(from); if (edges.isEmpty()) { + /*sb.append(" ").append(from.getId()).append("\n");*/ continue; } -- GitLab From 5e91c75df547b9f09ac2104466e5fdcefffed939 Mon Sep 17 00:00:00 2001 From: dcisse2 Date: Sun, 2 Nov 2025 12:01:46 +0100 Subject: [PATCH 6/9] ########## --- m1graphs25 | 1 - 1 file changed, 1 deletion(-) delete mode 160000 m1graphs25 diff --git a/m1graphs25 b/m1graphs25 deleted file mode 160000 index 483bf8d..0000000 --- a/m1graphs25 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 483bf8d204f6d168a86ea622f4cc61d1aa9a5f23 -- GitLab From af51d016561e6a20d5b1b84b006a646a0b5606f1 Mon Sep 17 00:00:00 2001 From: adjemaou Date: Mon, 3 Nov 2025 18:31:12 +0000 Subject: [PATCH 7/9] Update README.md --- README.md | 161 ++++++++++++++++++++++++++++++------------------------ 1 file changed, 90 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index ec2ac13..65410ac 100644 --- a/README.md +++ b/README.md @@ -1,93 +1,112 @@ # M1graphs25 - - -## Getting started - -To make it easy for you to get started with GitLab, here's a list of recommended next steps. - -Already a pro? Just edit this README.md and make it your own. Want to make it easy? [Use the template at the bottom](#editing-this-readme)! - -## Add your files - -- [ ] [Create](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#create-a-file) or [upload](https://docs.gitlab.com/ee/user/project/repository/web_editor.html#upload-a-file) files -- [ ] [Add files using the command line](https://docs.gitlab.com/ee/gitlab-basics/add-file.html#add-a-file-using-the-command-line) or push an existing Git repository with the following command: - +## Description +**M1graphs25** — bibliothèque Java pédagogique pour la représentation et la manipulation de graphes (orientés / non-orientés), conçue dans le cadre du module M1. +Fonctionnalités principales : +- Représentation par listes d’arêtes adjacentes (`Map>`). +- Support des **multigraphes** et des **self-loops** .etc. +- Import / export au format DOT (Graphviz). +- Parcours DFS / BFS, calcul de degrés, matrice d’adjacence, conversion en successeur-array. +- Transformations : graphe inversé, clôture transitive, simplification en simple graph. +- Outils utilitaires (tests unitaires, génération de fichiers `.gv`). + +--- + +## Structure du projet (Maven) ``` -cd existing_repo -git remote add origin https://disc.univ-fcomte.fr/cr700-gitlab/dcisse2/m1graphs25.git -git branch -M main -git push -uf origin main +m1graphs25/ +├── pom.xml +├── src/ +│ ├── main/ +│ │ └── java/ +│ │ └── fr/ufrst/m1info/thg/adjemaou/ +│ │ ├── Graph.java +│ │ ├── Node.java +│ │ ├── Edge.java +│ │ ├── UndirectedGraph.java +│ │ └── ... (autres classes utilitaires) +│ └── test/ +│ └── java/ +│ └── fr/ufrst/m1info/thg/adjemaou/ +│ └── GraphTest.java +└── README.md ``` -## Integrate with your tools - -- [ ] [Set up project integrations](https://disc.univ-fcomte.fr/cr700-gitlab/dcisse2/m1graphs25/-/settings/integrations) - -## Collaborate with your team - -- [ ] [Invite team members and collaborators](https://docs.gitlab.com/ee/user/project/members/) -- [ ] [Create a new merge request](https://docs.gitlab.com/ee/user/project/merge_requests/creating_merge_requests.html) -- [ ] [Automatically close issues from merge requests](https://docs.gitlab.com/ee/user/project/issues/managing_issues.html#closing-issues-automatically) -- [ ] [Enable merge request approvals](https://docs.gitlab.com/ee/user/project/merge_requests/approvals/) -- [ ] [Set auto-merge](https://docs.gitlab.com/ee/user/project/merge_requests/merge_when_pipeline_succeeds.html) +--- -## Test and Deploy +## Prérequis +- Java 17 (ou la version configurée dans `pom.xml`) +- Maven 3.x +- (Optionnel) Graphviz si tu veux générer des images à partir des `.gv`. +... -Use the built-in continuous integration in GitLab. +--- -- [ ] [Get started with GitLab CI/CD](https://docs.gitlab.com/ee/ci/quick_start/index.html) -- [ ] [Analyze your code for known vulnerabilities with Static Application Security Testing (SAST)](https://docs.gitlab.com/ee/user/application_security/sast/) -- [ ] [Deploy to Kubernetes, Amazon EC2, or Amazon ECS using Auto Deploy](https://docs.gitlab.com/ee/topics/autodevops/requirements.html) -- [ ] [Use pull-based deployments for improved Kubernetes management](https://docs.gitlab.com/ee/user/clusters/agent/) -- [ ] [Set up protected environments](https://docs.gitlab.com/ee/ci/environments/protected_environments.html) +## Compilation & tests -*** +Depuis la racine du projet : +```bash +# compiler +mvn clean compile -# Editing this README +# lancer les tests unitaires +mvn test -When you're ready to make this README your own, just edit this file and use the handy template below (or feel free to structure it however you want - this is just a starting point!). Thanks to [makeareadme.com](https://www.makeareadme.com/) for this template. - -## Suggestions for a good README - -Every project is different, so consider which of these sections apply to yours. The sections used in the template are suggestions for most open source projects. Also keep in mind that while a README can be too long and detailed, too long is better than too short. If you think your README is too long, consider utilizing another form of documentation rather than cutting out information. - -## Name -Choose a self-explaining name for your project. - -## Description -Let people know what your project can do specifically. Provide context and add a link to any reference visitors might be unfamiliar with. A list of Features or a Background subsection can also be added here. If there are alternatives to your project, this is a good place to list differentiating factors. - -## Badges -On some READMEs, you may see small images that convey metadata, such as whether or not all the tests are passing for the project. You can use Shields to add some to your README. Many services also have instructions for adding a badge. +# packager (jar) +mvn package +``` -## Visuals -Depending on what you are making, it can be a good idea to include screenshots or even a video (you'll frequently see GIFs rather than actual videos). Tools like ttygif can help, but check out Asciinema for a more sophisticated method. +--- -## Installation -Within a particular ecosystem, there may be a common way of installing things, such as using Yarn, NuGet, or Homebrew. However, consider the possibility that whoever is reading your README is a novice and would like more guidance. Listing specific steps helps remove ambiguity and gets people to using your project as quickly as possible. If it only runs in a specific context like a particular programming language version or operating system or has dependencies that have to be installed manually, also add a Requirements subsection. +## Exemples d’utilisation -## Usage -Use examples liberally, and show the expected output if you can. It's helpful to have inline the smallest example of usage that you can demonstrate, while providing links to more sophisticated examples if they are too long to reasonably include in the README. +### 1. Construire un graphe manuellement +```java +Graph g = new Graph(); +Node n1 = g.addNode(1); +Node n2 = g.addNode(2); +g.addEdge(n1, n2); // arête 1 -> 2 +g.addEdge(1, 3, 5); // arête 1 -> 3 de poids 5 (si méthode disponible) +System.out.println(g.toDotString()); +``` -## Support -Tell people where they can go to for help. It can be any combination of an issue tracker, a chat room, an email address, etc. +--- -## Roadmap -If you have ideas for releases in the future, it is a good idea to list them in the README. +## Format DOT supporté (exemples) +Ton parser accepte des lignes de type : +``` +1 +1 -> 2; +1 -> 3 [label="5"]; +``` +- `1` : nœud isolé +- `1 -> 2;` : arête sans poids (poids par défaut) +- `1 -> 3 [label="5"];` : arête avec poids = 5 + +--- + +## Commandes utiles Git / GitLab +Ajouter et pousser le projet sur GitLab : +```bash +git init +git add . +git commit -m "Initial commit: M1graphs25" +git remote add origin https://disc.univ-fcomte.fr/cr700-gitlab/dcisse2/m1graphs25.git +git branch -M main +git push -u origin main +``` -## Contributing -State if you are open to contributions and what your requirements are for accepting them. +Pour modifier le README dans l’interface GitLab : `Repository -> Files -> New file` (ou utiliser le Web IDE). -For people who want to make changes to your project, it's helpful to have some documentation on how to get started. Perhaps there is a script that they should run or some environment variables that they need to set. Make these steps explicit. These instructions could also be useful to your future self. +--- -You can also document commands to lint the code or run tests. These steps help to ensure high code quality and reduce the likelihood that the changes inadvertently break something. Having instructions for running tests is especially helpful if it requires external setup, such as starting a Selenium server for testing in a browser. +## Tests & validation +- Les tests unitaires JUnit sont placés dans `src/test/java/...` et s’exécutent avec `mvn test`. +- Ajoute des ressources test (fichiers `.gv`) dans `src/test/resources/` si nécessaire pour valider `fromDotFile()`. -## Authors and acknowledgment -Show your appreciation to those who have contributed to the project. +--- -## License -For open source projects, say how it is licensed. -## Project status -If you have run out of energy or time for your project, put a note at the top of the README saying that development has slowed down or stopped completely. Someone may choose to fork your project or volunteer to step in as a maintainer or owner, allowing your project to keep going. You can also make an explicit request for maintainers. +## Auteur(s) +DJEMAOUI AHMED +DRAMANE CISSE" \ No newline at end of file -- GitLab From 45ac68a2e1475d3ebd1a8c22a41caccecbe545a3 Mon Sep 17 00:00:00 2001 From: dcisse2 Date: Wed, 12 Nov 2025 09:57:55 +0100 Subject: [PATCH 8/9] ajout du package maximum flow --- .idea/aws.xml | 11 ++ .idea/encodings.xml | 2 + .idea/material_theme_project_new.xml | 12 ++ .idea/misc.xml | 1 - .idea/vcs.xml | 1 - 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 ++++++++++++++++++ src/main/java/m1graphs2025/Edge.java | 126 ++++++++++++++ src/main/java/m1graphs2025/Graph.java | 22 ++- src/main/java/m1graphs2025/Node.java | 8 + ...tiGraph.gv => undirWeightedMultiGraph.dot} | 0 ...SimpleGraph.gv => weightedSimpleGraph.dot} | 0 .../java/m1graphs2025/TestGraphPart3.java | 6 +- .../java/m1graphs2025/TestGraphPart5.java | 6 +- src/test/java/m1graphs2025/TestsGraphPW2.java | 12 +- 26 files changed, 857 insertions(+), 15 deletions(-) create mode 100644 .idea/aws.xml create mode 100644 .idea/material_theme_project_new.xml create mode 100644 output/step_1_flow.dot create mode 100644 output/step_1_residual.dot create mode 100644 output/step_2_flow.dot create mode 100644 output/step_2_residual.dot create mode 100644 output/step_3_flow.dot create mode 100644 output/step_3_residual.dot create mode 100644 src/main/java/MaximumFlow/DotIO.java create mode 100644 src/main/java/MaximumFlow/FlowMap.java create mode 100644 src/main/java/MaximumFlow/FlowNetwork.java create mode 100644 src/main/java/MaximumFlow/FordFulkerson.java create mode 100644 src/main/java/MaximumFlow/Main.java create mode 100644 src/main/java/MaximumFlow/PathFinder.java create mode 100644 src/main/java/MaximumFlow/ResidualGraph.java rename src/main/resources/{undirWeightedMultiGraph.gv => undirWeightedMultiGraph.dot} (100%) rename src/main/resources/{weightedSimpleGraph.gv => weightedSimpleGraph.dot} (100%) diff --git a/.idea/aws.xml b/.idea/aws.xml new file mode 100644 index 0000000..b63b642 --- /dev/null +++ b/.idea/aws.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml index aa00ffa..226ed61 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -3,5 +3,7 @@ + + \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml new file mode 100644 index 0000000..b857423 --- /dev/null +++ b/.idea/material_theme_project_new.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index 82dbec8..c3f3b0a 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 95c2cb1..94a25f7 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/output/step_1_flow.dot b/output/step_1_flow.dot new file mode 100644 index 0000000..5a13e73 --- /dev/null +++ b/output/step_1_flow.dot @@ -0,0 +1,18 @@ +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 new file mode 100644 index 0000000..d750d65 --- /dev/null +++ b/output/step_1_residual.dot @@ -0,0 +1,15 @@ +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 new file mode 100644 index 0000000..5a13e73 --- /dev/null +++ b/output/step_2_flow.dot @@ -0,0 +1,18 @@ +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 new file mode 100644 index 0000000..5646477 --- /dev/null +++ b/output/step_2_residual.dot @@ -0,0 +1,14 @@ +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 new file mode 100644 index 0000000..5a13e73 --- /dev/null +++ b/output/step_3_flow.dot @@ -0,0 +1,18 @@ +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 new file mode 100644 index 0000000..14f08a2 --- /dev/null +++ b/output/step_3_residual.dot @@ -0,0 +1,13 @@ +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 new file mode 100644 index 0000000..919a184 --- /dev/null +++ b/src/main/java/MaximumFlow/DotIO.java @@ -0,0 +1,69 @@ +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 new file mode 100644 index 0000000..238fa68 --- /dev/null +++ b/src/main/java/MaximumFlow/FlowMap.java @@ -0,0 +1,86 @@ +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 new file mode 100644 index 0000000..f658b00 --- /dev/null +++ b/src/main/java/MaximumFlow/FlowNetwork.java @@ -0,0 +1,132 @@ +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 new file mode 100644 index 0000000..0d92ed7 --- /dev/null +++ b/src/main/java/MaximumFlow/FordFulkerson.java @@ -0,0 +1,27 @@ +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 new file mode 100644 index 0000000..c80ef90 --- /dev/null +++ b/src/main/java/MaximumFlow/Main.java @@ -0,0 +1,37 @@ +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 new file mode 100644 index 0000000..77cde86 --- /dev/null +++ b/src/main/java/MaximumFlow/PathFinder.java @@ -0,0 +1,62 @@ +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 new file mode 100644 index 0000000..23cf5fb --- /dev/null +++ b/src/main/java/MaximumFlow/ResidualGraph.java @@ -0,0 +1,156 @@ +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/Edge.java b/src/main/java/m1graphs2025/Edge.java index 1dada9e..378dcdf 100644 --- a/src/main/java/m1graphs2025/Edge.java +++ b/src/main/java/m1graphs2025/Edge.java @@ -86,6 +86,69 @@ public class Edge implements Comparable { this.weight = 0; } + public Edge(String fromName, String toName, Graph g) { + if (g == null) { + throw new IllegalArgumentException("Le graphe ne peut pas être null."); + } + Node from = g.getNode(fromName); + Node to = g.getNode(toName); + + if (from == null) { + from = new Node(fromName, g); + g.addNode(from); + } + if (to == null) { + to = new Node(toName, g); + g.addNode(to); + } + + this.from = from; + this.to = to; + this.weight = 0; + } + + public Edge(int fromId, String toName, Graph g) { + if (g == null) { + throw new IllegalArgumentException("Le graphe ne peut pas être null."); + } + Node from = g.getNode(fromId); + Node to = g.getNode(toName); + + if (from == null) { + from = new Node(fromId, g); + g.addNode(from); + } + if (to == null) { + to = new Node(toName, g); + g.addNode(to); + } + + this.from = from; + this.to = to; + this.weight = 0; + } + + public Edge(String fromName, int toId, Graph g) { + if (g == null) { + throw new IllegalArgumentException("Le graphe ne peut pas être null."); + } + Node from = g.getNode(fromName); + Node to = g.getNode(toId); + + if (from == null) { + from = new Node(fromName, g); + g.addNode(from); + } + if (to == null) { + to = new Node(toId, g); + g.addNode(to); + } + + this.from = from; + this.to = to; + this.weight = 0; + } + /** * Crée une arête pondérée reliant deux nœuds identifiés par leurs IDs dans un graphe donné. * Si les nœuds n’existent pas dans le graphe, ils sont créés. @@ -117,6 +180,69 @@ public class Edge implements Comparable { this.weight = weight; } + public Edge(String fromName, String toName, int weight, Graph g) { + if (g == null) { + throw new IllegalArgumentException("Le graphe ne peut pas être null."); + } + Node from = g.getNode(fromName); + Node to = g.getNode(toName); + + if (from == null) { + from = new Node(fromName, g); + g.addNode(from); + } + if (to == null) { + to = new Node(toName, g); + g.addNode(to); + } + + this.from = from; + this.to = to; + this.weight = weight; + } + + public Edge(int fromId, String toName, 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(toName); + + if (from == null) { + from = new Node(fromId, g); + g.addNode(from); + } + if (to == null) { + to = new Node(toName, g); + g.addNode(to); + } + + this.from = from; + this.to = to; + this.weight = weight; + } + + public Edge(String fromName, int toId, int weight, Graph g) { + if (g == null) { + throw new IllegalArgumentException("Le graphe ne peut pas être null."); + } + Node from = g.getNode(fromName); + Node to = g.getNode(toId); + + if (from == null) { + from = new Node(fromName, 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========================================================= // ================================================================================================================= diff --git a/src/main/java/m1graphs2025/Graph.java b/src/main/java/m1graphs2025/Graph.java index ef2372e..e1917aa 100644 --- a/src/main/java/m1graphs2025/Graph.java +++ b/src/main/java/m1graphs2025/Graph.java @@ -29,6 +29,10 @@ public class Graph { public Graph(Map> adjEdList) { this.adjEdList = adjEdList; } + public Graph() { + this.adjEdList = new HashMap<>(); + } + /** * Construit un graphe à partir d’un tableau de successeurs. @@ -143,6 +147,22 @@ public class Graph { return null; } + public Node getNode(String name) { + if (name == null || !name.isEmpty()) { + System.err.println("Erreur: identifiant de nœud invalide (" + name + ")."); + return null; + } + if (adjEdList == null || adjEdList.isEmpty()) { + System.err.println("Avertissement: la liste d’adjacence est vide ou non initialisée."); + return null; + } + for (Node entry : adjEdList.keySet()) { + if (entry != null && entry.getName() == name) + return entry; + } + return null; + } + /** * Récupère un nœud existant ou le crée s’il n’existe pas encore. * @@ -1627,7 +1647,7 @@ public class Graph { */ public void toDotFile(String filename, String ext) { if (ext == null) - ext = ".gv"; + ext = ".dot"; if (!ext.startsWith(".")) ext = "." + ext; try { diff --git a/src/main/java/m1graphs2025/Node.java b/src/main/java/m1graphs2025/Node.java index 1781182..1f7e600 100644 --- a/src/main/java/m1graphs2025/Node.java +++ b/src/main/java/m1graphs2025/Node.java @@ -30,10 +30,17 @@ public class Node implements Comparable { public Node(int id, Graph graph) { this.id = id; this.graph = graph; + this.name = null; + } + public Node(String name, Graph graph) { + this.name = name; + this.graph = graph; + this.id = 0; } public Node(){ this.id = 0; this.graph = null; + this.name = null; } /** @@ -57,6 +64,7 @@ public class Node implements Comparable { public Node(Graph graph) { this.id = 0; this.graph = graph; + this.name = null; } // ================================================================================================================= diff --git a/src/main/resources/undirWeightedMultiGraph.gv b/src/main/resources/undirWeightedMultiGraph.dot similarity index 100% rename from src/main/resources/undirWeightedMultiGraph.gv rename to src/main/resources/undirWeightedMultiGraph.dot diff --git a/src/main/resources/weightedSimpleGraph.gv b/src/main/resources/weightedSimpleGraph.dot similarity index 100% rename from src/main/resources/weightedSimpleGraph.gv rename to src/main/resources/weightedSimpleGraph.dot diff --git a/src/test/java/m1graphs2025/TestGraphPart3.java b/src/test/java/m1graphs2025/TestGraphPart3.java index 84ed56a..953be9b 100644 --- a/src/test/java/m1graphs2025/TestGraphPart3.java +++ b/src/test/java/m1graphs2025/TestGraphPart3.java @@ -13,10 +13,10 @@ public class TestGraphPart3 { int totalEdgesWeight = 0; System.out.println("\n>>>>>>>>>>"); - System.out.println("Reading a weighted directed simple graph from DOT file 'weightedSimpleGraph.gv'"); - Graph wsg = Graph.fromDotFile("src/main/resources/weightedSimpleGraph.gv"); + System.out.println("Reading a weighted directed simple graph from DOT file 'weightedSimpleGraph.dot'"); + Graph wsg = Graph.fromDotFile("src/main/resources/weightedSimpleGraph.dot"); if (wsg == null) - System.out.println("Null graph was created from 'weightedSimpleGraph.gv'"); + System.out.println("Null graph was created from 'weightedSimpleGraph.dot'"); else { System.out.println("Read: OK. The weighted directed simple graph has been read as:"); System.out.println("---------------------"); diff --git a/src/test/java/m1graphs2025/TestGraphPart5.java b/src/test/java/m1graphs2025/TestGraphPart5.java index c9e4468..d0fa911 100644 --- a/src/test/java/m1graphs2025/TestGraphPart5.java +++ b/src/test/java/m1graphs2025/TestGraphPart5.java @@ -13,11 +13,11 @@ public class TestGraphPart5 { System.out.println("*-----------------------------------------------------------------------*"); System.out.println("\n>>>>>> Reading 'uwmg' an undirected weighted multi-graph with self loops\n" - + "from file 'undirWeightedMultiGraph.gv'"); + + "from file 'undirWeightedMultiGraph.dot'"); - UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("src/main/resources/undirWeightedMultiGraph.gv"); + UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("src/main/resources/undirWeightedMultiGraph.dot"); if (uwmg == null) - System.out.println("Null graph was created from 'undirWeightedMultiGraph.gv'"); + System.out.println("Null graph was created from 'undirWeightedMultiGraph.dot'"); else { System.out.println("Read: OK. The undirected weighted multi-graph has been read as:"); System.out.println("---------------------"); diff --git a/src/test/java/m1graphs2025/TestsGraphPW2.java b/src/test/java/m1graphs2025/TestsGraphPW2.java index 04b95d1..84ced11 100644 --- a/src/test/java/m1graphs2025/TestsGraphPW2.java +++ b/src/test/java/m1graphs2025/TestsGraphPW2.java @@ -233,10 +233,10 @@ public class TestsGraphPW2 { int totalEdgesWeight = 0; System.out.println("\n>>>>>>>>>>"); - System.out.println("Reading a weighted directed simple graph from DOT file 'weightedSimpleGraph.gv'"); - Graph wsg = Graph.fromDotFile("weightedSimpleGraph.gv"); + System.out.println("Reading a weighted directed simple graph from DOT file 'weightedSimpleGraph.dot'"); + Graph wsg = Graph.fromDotFile("weightedSimpleGraph.dot"); if (wsg==null) - System.out.println("Null graph was created from 'weightedSimpleGraph.gv'"); + System.out.println("Null graph was created from 'weightedSimpleGraph.dot'"); else { System.out.println("Read: OK. The weighted directed simple graph has been read as:"); System.out.println("---------------------"); @@ -373,11 +373,11 @@ public class TestsGraphPW2 { System.out.println("*-----------------------------------------------------------------------*"); System.out.println("\n>>>>>> Reading 'uwmg' an undirected weighted multi-graph with self loops\n" - + "from file 'undirWeightedMultiGraph.gv'"); + + "from file 'undirWeightedMultiGraph.dot'"); - UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("undirWeightedMultiGraph.gv"); + UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("undirWeightedMultiGraph.dot"); if (uwmg==null) - System.out.println("Null graph was created from 'undirWeightedMultiGraph.gv'"); + System.out.println("Null graph was created from 'undirWeightedMultiGraph.dot'"); else { System.out.println("Read: OK. The undirected weighted multi-graph has been read as:"); System.out.println("---------------------"); -- GitLab From 70ffaa0b903501bbc5945934c1fdf52bdff48e23 Mon Sep 17 00:00:00 2001 From: dcisse2 Date: Thu, 20 Nov 2025 04:16:22 +0100 Subject: [PATCH 9/9] =?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