diff --git a/.idea/aws.xml b/.idea/aws.xml new file mode 100644 index 0000000000000000000000000000000000000000..b63b642cfb4254fc0f7058903abc5b481895c4ef --- /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 aa00ffab7828f4818589659c804ec2cfd99baed3..e2d3ca17c6be707c14648f17d476b1ee19e133a5 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,7 +1,10 @@ + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000000000000000000000000000000000..f4d0a6b222b1997d868bb7036926a8ad6d34a118 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ 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 0000000000000000000000000000000000000000..b85742369c3b52211e62f5f1bc71f6b0efdcb6b0 --- /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 82dbec8ad28463aed32007a93ffc07865ae98968..c3f3b0ab1ba765e267b1979ac687f9786d56644c 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,3 @@ - diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 95c2cb156fc0e4f4a4654f8b65304c1bb982adf7..94a25f7f4cb416c083d265558da75d457237d671 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -2,6 +2,5 @@ - \ No newline at end of file diff --git a/README.md b/README.md index ec2ac135010a80e0bb524139d42a45bcfdea0f4b..65410aced0c611df482f5fd391aec0effa158d8b 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 diff --git a/input/example_flow.dot b/input/example_flow.dot new file mode 100644 index 0000000000000000000000000000000000000000..70d70544e9f7b60ff575d94da2d2afffbf0e11ca --- /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/m1graphs25 b/m1graphs25 deleted file mode 160000 index 3baf9d19e40b5b9ef6e31f7deb15413679dcf68e..0000000000000000000000000000000000000000 --- a/m1graphs25 +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 3baf9d19e40b5b9ef6e31f7deb15413679dcf68e diff --git a/output/flow1.gv b/output/flow1.gv new file mode 100644 index 0000000000000000000000000000000000000000..97471c03e627bb7756359b3b93a6b71200ed4fc5 --- /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 0000000000000000000000000000000000000000..05432c366a5b77714e73baf1242da2dab2b81319 --- /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 0000000000000000000000000000000000000000..f2d5e1027d11bda267d2d67b44f18f10ad70cf51 --- /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 0000000000000000000000000000000000000000..f1254b372c9355d5bd6058493a651526deacfe2c --- /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 0000000000000000000000000000000000000000..52e2408f2d6b3628d4ffa1137bd82996f68dfb83 --- /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 0000000000000000000000000000000000000000..fb128fe928a396ec89426b78ca3ae569274bad97 --- /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 0000000000000000000000000000000000000000..7dc73b4e9b3a8cdaae8c7c158d3be66db29fc838 --- /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 0000000000000000000000000000000000000000..4facbb5bec8f5d07e2e123ac890f72936adb0df5 --- /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 0000000000000000000000000000000000000000..15f700667dce2211c3b0b728f5ab1eff3317fb15 --- /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 0000000000000000000000000000000000000000..485fdda53011fe2f5d34ea0ae9fd7e88dadf3a23 --- /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/src/main/java/m1graphs2025/Edge.java b/src/main/java/m1graphs2025/Edge.java deleted file mode 100644 index 14ef5e012baf8941461d3849cd8f4a2920566b49..0000000000000000000000000000000000000000 --- a/src/main/java/m1graphs2025/Edge.java +++ /dev/null @@ -1,240 +0,0 @@ -package m1graphs2025; - -import java.util.*; - -/** - * Représente une arête (Edge) dans un graphe. - * Une arête relie deux nœuds (source et destination) et peut éventuellement - * porter un poids si le graphe est pondéré. - *

- * Cette classe implémente l’interface Comparable afin de permettre - * le tri des arêtes selon leur poids, puis selon leurs nœuds. - */ -public class Edge implements Comparable { - - // ====================== - // Attributs - // ====================== - - /** Nœud source de l’arête */ - private Node from; - - /** Nœud destination de l’arête */ - private Node to; - - /** Poids de l’arête (null si non pondérée) */ - private Integer weight; - - // ====================== - // Constructeurs - // ====================== - - /** Arête non pondérée */ - public Edge(Node from, Node to) { - this.from = from; - this.to = to; - this.weight = null; - } - - public Edge() { - this.from = null; - this.to = null; - this.weight = null; - } - - /** Arête pondérée */ - 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é */ - public Edge(int fromId, int toId, Graph g) { - if (g == null) { - throw new IllegalArgumentException("Le graphe ne peut pas être null."); - } - Node from = g.getNode(fromId); - Node to = g.getNode(toId); - - if (from == null) { - from = new Node(fromId, g); - g.addNode(from); - } - if (to == null) { - to = new Node(toId, g); - g.addNode(to); - } - - this.from = from; - this.to = to; - this.weight = 0; // ou une valeur par défaut - } - - public Edge(int fromId, int toId, int weight, Graph g) { - if (g == null) { - throw new IllegalArgumentException("Le graphe ne peut pas être null."); - } - Node from = g.getNode(fromId); - Node to = g.getNode(toId); - - if (from == null) { - from = new Node(fromId, g); - g.addNode(from); - } - if (to == null) { - to = new Node(toId, g); - g.addNode(to); - } - - this.from = from; - this.to = to; - this.weight = weight; - } - - // ====================== - // Getters / Setters - // ====================== - - public Node getNodeFrom() { - return from; - } - - public Node getNodeTo() { - return to; - } - - public Integer getWeight() { - return weight; - } - - public void setNodeFrom(Node from) { - this.from = from; - } - - public void setNodeTo(Node to) { - this.to = to; - } - - public void setWeight(Integer weight) { - this.weight = weight; - } - - // ====================== - // Méthodes fonctionnelles - // ====================== - - public Node from() { - return from; - } - - public Node to() { - return to; - } - - /** - * Retourne une arête symétrique (inversée) avec la même pondération. - * - * @return une nouvelle arête inversée ou null si les nœuds n’appartiennent pas - * au même graphe. - */ - public Edge getSymmetric() { - Graph g1 = (from != null) ? from.getGraph() : null; - Graph g2 = (to != null) ? to.getGraph() : null; - - if (Objects.equals(g1, g2)) { - Node symFrom = new Node(to.getId(), g1, to.getName()); - Node symTo = new Node(from.getId(), g1, from.getName()); - return new Edge(symFrom, symTo, this.weight); - } - return null; - } - - /** - * Vérifie si l’arête est une boucle (relie un nœud à lui-même). - */ - 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). - */ - public boolean isMultiEdge() { - if (from == null || from.getGraph() == null) - return false; - - Graph graph = from.getGraph(); - List edges = graph.adjEdList.get(from); - - if (edges == null) - return false; - - for (Edge edge : edges) { - boolean sameNodes = edge.from.getId() == this.from.getId() - && edge.to.getId() == this.to.getId(); - boolean differentWeight = !Objects.equals(edge.getWeight(), this.weight); - - if (sameNodes && differentWeight) { - return true; - } - } - return false; - } - - /** Indique si l’arête est pondérée. */ - public boolean isWeighted() { - return this.weight != null; - } - - // ====================== - // Méthodes redéfinies - // ====================== - - /** - * Compare deux arêtes selon leur poids, puis les identifiants des nœuds. - * Gère correctement les arêtes non pondérées (poids null). - */ - @Override - public int compareTo(Edge other) { - int w1 = (this.weight == null) ? 0 : this.weight; - int w2 = (other.weight == null) ? 0 : other.weight; - - int cmp = Integer.compare(w1, w2); - if (cmp != 0) - return cmp; - - cmp = Integer.compare(this.from.getId(), other.from.getId()); - if (cmp != 0) - return cmp; - - return Integer.compare(this.to.getId(), other.to.getId()); - } - - /** - * Vérifie l’égalité entre deux arêtes : - * même nœud source, même destination, même poids. - */ - @Override - public boolean equals(Object o) { - if (this == o) - return true; - if (!(o instanceof Edge edge)) - return false; - return Objects.equals(from, edge.from) - && Objects.equals(to, edge.to) - && Objects.equals(weight, edge.weight); - } - - @Override - public int hashCode() { - return Objects.hash(from, to, weight); - } - - @Override - public String toString() { - return "Edge(" + from.getId() + " -> " + to.getId() + ", w=" + weight + ")"; - } - -} \ No newline at end of file diff --git a/src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..a45a5f1de81e7ea0ea586692b1e7f3d2aed578f7 --- /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 0000000000000000000000000000000000000000..5cd9f0a24db843b0c41e344834c9f030a1aaf5a6 --- /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 0000000000000000000000000000000000000000..95a3a3a5c6781e218aa7a34b4f7a317edf359ca2 --- /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 0000000000000000000000000000000000000000..1a21f85475de967fac02eb2b2bebf4ad6265006f --- /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 0000000000000000000000000000000000000000..89ddf6093a767f7aa51d6a7b1a21c65f2e485cb7 --- /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 0000000000000000000000000000000000000000..23c681c29078137e1d949a3a4afdcc6bab75d46d --- /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 0000000000000000000000000000000000000000..67b8f41edc690d04ccc5939a8b27e820417178b5 --- /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 0000000000000000000000000000000000000000..203683ccb15fed8370189e8c3cf329f1b2d4e506 --- /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 0000000000000000000000000000000000000000..29d96b5f499a5caa7f437415cfe9628a06e8cdfd --- /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 0000000000000000000000000000000000000000..b3c8f651ccab5b9387814be0e68d9b7537d0e69e --- /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 0000000000000000000000000000000000000000..a5d3aa114dcdcfd62e8ad0ed33fdd6c0beb7087e --- /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/NodeVisitInfo.java b/src/main/java/m1graphs2025/NodeVisitInfo.java deleted file mode 100644 index ba21d1a3f05cdbac173f16f21066fb79988982de..0000000000000000000000000000000000000000 --- a/src/main/java/m1graphs2025/NodeVisitInfo.java +++ /dev/null @@ -1,104 +0,0 @@ -package m1graphs2025; - -/** - * Représente les informations de visite d’un nœud lors d’un parcours en profondeur (DFS). - * - * Chaque nœud du graphe peut être : - * - WHITE : non visité - * - GRAY : en cours de visite - * - BLACK : complètement exploré - */ -public class NodeVisitInfo { - - /** Couleur du nœud (état dans le parcours DFS) */ - public enum NodeColour { WHITE, GRAY, BLACK } - - private NodeColour colour; // Couleur du nœud - private Node predecessor; // Nœud parent dans le parcours - private Integer discoveryTime; // Temps de découverte (d) - private Integer finishTime; // Temps de fin (f) - - // ============================ - // Constructeurs - // ============================ - - /** - * Constructeur par défaut : - * couleur initiale = WHITE, aucun prédécesseur, temps nuls. - */ - public NodeVisitInfo() { - this.colour = NodeColour.WHITE; - this.predecessor = null; - this.discoveryTime = null; - this.finishTime = null; - } - - /** - * Constructeur complet. - * - * @param colour couleur du nœud - * @param predecessor prédécesseur dans le parcours - * @param discoveryTime temps de découverte - * @param finishTime temps de fin - */ - public NodeVisitInfo(NodeColour colour, Node predecessor, Integer discoveryTime, Integer finishTime) { - this.colour = colour; - this.predecessor = predecessor; - this.discoveryTime = discoveryTime; - this.finishTime = finishTime; - } - - // ============================ - // Getters - // ============================ - - public NodeColour getColour() { - return colour; - } - - public Node getPredecessor() { - return predecessor; - } - - public Integer getDiscoveryTime() { - return discoveryTime; - } - - public Integer getFinishTime() { - return finishTime; - } - - // ============================ - // Setters - // ============================ - - public void setColour(NodeColour colour) { - this.colour = colour; - } - - public void setPredecessor(Node predecessor) { - this.predecessor = predecessor; - } - - public void setDiscoveryTime(Integer discoveryTime) { - this.discoveryTime = discoveryTime; - } - - public void setFinishTime(Integer finishTime) { - this.finishTime = finishTime; - } - - // ============================ - // Méthodes utilitaires - // ============================ - - /** - * Réinitialise les informations de visite (utile avant un nouveau parcours). - */ - public void reset() { - this.colour = NodeColour.WHITE; - this.predecessor = null; - this.discoveryTime = null; - this.finishTime = null; - } -} diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/UndirectedGraph.java deleted file mode 100644 index 595e22fa7ec17da23e0307defd65b686f7e1d7d5..0000000000000000000000000000000000000000 --- a/src/main/java/m1graphs2025/UndirectedGraph.java +++ /dev/null @@ -1,176 +0,0 @@ -package m1graphs2025; - -import java.util.*; - -/** - * Représente un graphe non orienté en héritant de Graph. - * Chaque arête (u, v) est automatiquement dupliquée en (v, u). - */ -public class UndirectedGraph extends Graph { - - // ====================== - // CONSTRUCTEURS - // ====================== - public UndirectedGraph(Map> adjEdList) { - super(adjEdList); - } - - public UndirectedGraph() { - super(new HashMap<>()); // graphe vide - } - - public UndirectedGraph(int... successorArray) { - super(successorArray); - // Ici, successorArray est interprété comme orienté, - // donc on doit symétriser après construction - symmetrize(); - } - - @Override - public boolean addNode(Node n) { - return super.addNode(n); - } - - /** - * Symétrise toutes les arêtes existantes dans le graphe - * (utile après construction par tableau de successeurs). - */ - private void symmetrize() { - List edges = getAllEdges(); - for (Edge e : edges) { - Node u = e.getNodeFrom(); - Node v = e.getNodeTo(); - if (!existsEdge(v, u)) { - if (e.isWeighted()) - addEdge(v, u, e.getWeight()); - else - addEdge(v, u); - } - } - } - - // ====================== - // API DES ARÊTES (Override) - // ====================== - - @Override - public void addEdge(Node from, Node to) { - super.addEdge(from, to); - // 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); - } - - @Override - public void addEdge(Node from, Node to, int weight) { - super.addEdge(from, to, weight); - if (!existsEdge(to, from)) - 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); - } - - @Override - public void addEdge(Edge e) { - super.addEdge(e); - Edge sym = e.getSymmetric(); - if (sym != null && existsEdge(sym)) - super.addEdge(sym); - } - - @Override - public boolean removeEdge(Node from, Node to) { - boolean r1 = super.removeEdge(from, to); - boolean r2 = super.removeEdge(to, from); - return r1 || r2; - } - - @Override - public boolean removeEdge(int fromId, int toId) { - boolean r1 = super.removeEdge(fromId, toId); - boolean r2 = super.removeEdge(toId, fromId); - return r1 || r2; - } - - @Override - public boolean removeEdge(Edge e) { - boolean r1 = super.removeEdge(e); - Edge sym = e.getSymmetric(); - boolean r2 = (sym != null) && super.removeEdge(sym); - return r1 || r2; - } - - @Override - public boolean existsEdge(Node u, Node v) { - // 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); - } - - @Override - public boolean isMultiEdge(Node u, Node v) { - // Vérifie dans les deux directions - return super.isMultiEdge(u, v) || super.isMultiEdge(v, u); - } - - @Override - public UndirectedGraph copy() { - Map> copy = new HashMap<>(); - for (Node u : adjEdList.keySet()) { - List list = new ArrayList<>(); - for (Edge e : adjEdList.get(u)) - list.add(new Edge(e.getNodeFrom(), e.getNodeTo(), e.getWeight())); - copy.put(u, list); - } - return new UndirectedGraph(copy); - } - - @Override - public UndirectedGraph getTransitiveClosure() { - UndirectedGraph closure = this.copy(); - List nodes = closure.getAllNodes(); - int n = nodes.size(); - for (Node k : nodes) { - for (Node i : nodes) { - for (Node j : nodes) { - if (closure.existsEdge(i, k) && closure.existsEdge(k, j)) { - if (!closure.existsEdge(i, j)) - closure.addEdge(i, j); - } - } - } - } - return closure; - } - - public static UndirectedGraph fromDotFile(String filename) { - return fromDotFile(filename, ".gv"); - } - - // ====================== - // Utilitaires - // ====================== - - /** - * Indique que ce graphe est non orienté (utile pour certaines fonctions). - */ - public boolean isDirected() { - return false; - } -} diff --git a/src/main/java/m1graphs2025/pw2/Edge.java b/src/main/java/m1graphs2025/pw2/Edge.java new file mode 100644 index 0000000000000000000000000000000000000000..b5c5d180cf6c43225651420f579e27aee18764ab --- /dev/null +++ b/src/main/java/m1graphs2025/pw2/Edge.java @@ -0,0 +1,455 @@ +package m1graphs2025.pw2; + +import java.util.*; + +public class Edge implements Comparable { + + // ================================================================================================================= + // ====================================================Attributs==================================================== + // ================================================================================================================= + + /** Nœud source de l’arête */ + private Node from; + + /** Nœud destination de l’arête */ + private Node to; + + /** Poids de l’arête (null si non pondérée) */ + private Integer weight; + + // ================================================================================================================= + // ==============================================Constructeurs====================================================== + // ================================================================================================================= + + /** + * Crée une arête non pondérée 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; + } + + /** + * 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; + } + + /** + * 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."); + } + Node from = g.getNode(fromId); + Node to = g.getNode(toId); + + if (from == null) { + from = new Node(fromId, g); + g.addNode(from); + } + if (to == null) { + to = new Node(toId, g); + g.addNode(to); + } + + this.from = from; + this.to = to; + this.weight = 0; + } + + 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. + * + * @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."); + } + Node from = g.getNode(fromId); + Node to = g.getNode(toId); + + if (from == null) { + from = new Node(fromId, g); + g.addNode(from); + } + if (to == null) { + to = new Node(toId, g); + g.addNode(to); + } + + this.from = from; + this.to = to; + this.weight = weight; + } + + 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========================================================= + // ================================================================================================================= + + /** + * 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===================================================== + // ================================================================================================================= + + /** + * Retourne le nœud source de l’arête. + * + * @return le nœud source + */ + public Node from() { + return from; + } + + /** + * Retourne le nœud destination de l’arête. + * + * @return le nœud destination + */ + public Node to() { + return to; + } + + /** + * Retourne une arête symétrique (inversée) avec la même pondération. + * + * @return une nouvelle arête inversée, ou null si les nœuds n’appartiennent pas au même graphe + */ + public Edge getSymmetric() { + Graph g1 = (from != null) ? from.getGraph() : null; + Graph g2 = (to != null) ? to.getGraph() : null; + + if (Objects.equals(g1, g2)) { + Node symFrom = new Node(to.getId(), g1, to.getName()); + Node symTo = new Node(from.getId(), g1, from.getName()); + return new Edge(symFrom, symTo, this.weight); + } + return null; + } + + /** + * Vérifie si l’arête est une boucle (relie un nœud à lui-même). + * + * @return true si l’arête est une boucle, false sinon + */ + public boolean isSelfLoop() { + return from != null && to != null && from.getId() == to.getId(); + } + + /** + * Vérifie s’il existe une autre arête reliant les mêmes nœuds mais avec un poids différent (arête multiple). + * + * @return true si l’arête est multiple, false sinon + */ + public boolean isMultiEdge() { + if (from == null || from.getGraph() == null) + return false; + + Graph graph = from.getGraph(); + List edges = graph.adjEdList.get(from); + + if (edges == null) + return false; + + for (Edge edge : edges) { + boolean sameNodes = edge.from.getId() == this.from.getId() + && edge.to.getId() == this.to.getId(); + boolean differentWeight = !Objects.equals(edge.getWeight(), this.weight); + + if (sameNodes && differentWeight) { + return true; + } + } + return false; + } + + /** + * Indique si l’arête est pondérée. + * + * @return true si l’arête a un poids, false sinon + */ + public boolean isWeighted() { + return this.weight != null; + } + + // ================================================================================================================= + // =============================================Méthodes redéfinies================================================= + // ================================================================================================================= + + /** + * Compare deux arêtes selon leur poids, puis les identifiants des nœuds. + * 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) { + int w1 = (this.weight == null) ? 0 : this.weight; + int w2 = (other.weight == null) ? 0 : other.weight; + + int cmp = Integer.compare(w1, w2); + if (cmp != 0) + return cmp; + + cmp = Integer.compare(this.from.getId(), other.from.getId()); + if (cmp != 0) + return cmp; + + return Integer.compare(this.to.getId(), other.to.getId()); + } + + /** + * Vérifie l’égalité entre deux arêtes. + * 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) { + if (this == o) + return true; + if (!(o instanceof Edge edge)) + return false; + return Objects.equals(from, edge.from) + && Objects.equals(to, edge.to) + && Objects.equals(weight, edge.weight); + } + + /** + * 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 from.getId() + " -> " + to.getId(); + } + +} \ No newline at end of file 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 7e86722c9897386f66752333623191da24b3179b..eb4a60103d90d3237850a1b7449242e56baa9249 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 50% rename from src/main/java/m1graphs2025/Graph.java rename to src/main/java/m1graphs2025/pw2/Graph.java index f565262a07584ec06b0d3675f8976c0cf14baa74..4d580036d21040c6480666d8de3329a2917ef694 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,28 +8,40 @@ 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; -/** - * 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; } + public Graph() { + this.adjEdList = new HashMap<>(); + } + /** - * 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 +68,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 +95,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 +109,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 +123,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,10 +142,41 @@ 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 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. + * + * @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); + } + + /** + * 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."); @@ -128,6 +200,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 + ")."); @@ -148,6 +226,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."); @@ -173,10 +257,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 + ")."); @@ -197,10 +292,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."); @@ -216,6 +321,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."); @@ -231,6 +341,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()."); @@ -245,6 +361,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) { @@ -254,6 +376,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()."); @@ -268,6 +396,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) { @@ -277,6 +411,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()."); @@ -291,6 +432,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); @@ -301,6 +449,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()."); @@ -314,6 +468,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) { @@ -323,6 +483,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()."); @@ -336,6 +502,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) { @@ -345,6 +517,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()."); @@ -358,6 +536,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) { @@ -367,10 +551,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()) @@ -378,6 +571,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) { @@ -394,6 +599,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) { @@ -411,6 +628,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) { @@ -423,6 +651,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) { @@ -436,6 +675,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) { @@ -453,6 +704,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) { @@ -468,6 +727,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) { @@ -485,6 +752,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) { @@ -500,6 +773,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) { @@ -518,6 +800,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) { @@ -538,6 +829,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) { @@ -558,6 +858,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) { @@ -571,6 +879,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) { @@ -588,6 +905,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) { @@ -600,6 +924,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) { @@ -612,6 +944,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) { @@ -628,6 +968,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) { @@ -647,6 +995,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) { @@ -663,6 +1019,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) { @@ -677,6 +1040,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) { @@ -693,6 +1064,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) { @@ -710,6 +1091,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<>(); @@ -722,10 +1109,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()); @@ -739,17 +1132,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()) @@ -764,6 +1168,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(); @@ -781,6 +1191,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); @@ -795,6 +1211,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)) { @@ -805,10 +1227,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()) @@ -826,6 +1260,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()) { @@ -837,51 +1277,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(); @@ -891,6 +1375,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(); @@ -898,11 +1388,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); @@ -923,12 +1426,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); @@ -939,9 +1450,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); @@ -949,13 +1469,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(); @@ -973,71 +1504,148 @@ 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=========================================================== + // ================================================================================================================= + /** + * 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 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) { - 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<>(); + + try (BufferedReader reader = new BufferedReader(new FileReader(filename))) { + String line; + + while ((line = reader.readLine()) != null) { + line = line.trim(); + if (line.isEmpty()) + continue; // skip empty lines + + if (line.matches("^\\d+$")) { + int id = Integer.parseInt(line); + Node node = g.getOrCreateNode(id); + adjEdList.putIfAbsent(node, new ArrayList<>()); + continue; } - 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])); + + String[] tokens = line.replaceAll("[^0-9]+", " ").trim().split("\\s+"); + if (tokens.length < 2) + continue; // invalid line → skip + + int fromId = Integer.parseInt(tokens[0]); + int toId = Integer.parseInt(tokens[1]); + + Node from = g.getOrCreateNode(fromId); + Node to = g.getOrCreateNode(toId); + + Edge edge; + if (tokens.length >= 3) { + int weight = Integer.parseInt(tokens[2]); + edge = new Edge(from, to, weight); + } else { + edge = new Edge(from, to); } + // Add to adjacency list + adjEdList.computeIfAbsent(from, k -> new ArrayList<>()).add(edge); + adjEdList.putIfAbsent(to, new ArrayList<>()); // ensure target node exists } + } catch (IOException e) { + throw new RuntimeException("Error reading file: " + filename, e); } + + return g; } + /** + * Retourne la représentation du graphe au format DOT sous forme de chaîne. + * + * @return chaîne contenant la description DOT du graphe + */ public String toDotString() { StringBuilder sb = new StringBuilder(); - sb.append("digraph G {\n"); - List edges = getAllEdges(); - edges.sort(Comparator.comparingInt((Edge e) -> e.getNodeFrom().getId()) - .thenComparingInt(e -> e.getNodeTo().getId())); - for (Edge e : edges) { - sb.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"); + + // Trier les nœuds par ID pour affichage ascendant + List sortedNodes = new ArrayList<>(adjEdList.keySet()); + sortedNodes.sort(Comparator.comparingInt(Node::getId)); + + 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) { + 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()); + + if (e.getWeight() != null) { + sb.append(" [label=").append(e.getWeight()).append("]"); + } + sb.append("\n"); } - sb.append("\n"); } - sb.append("}\n"); + + sb.append("}"); 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"; + ext = ".dot"; if (!ext.startsWith(".")) ext = "." + ext; try { @@ -1047,10 +1655,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()) @@ -1058,14 +1671,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/pw2/Node.java similarity index 81% rename from src/main/java/m1graphs2025/Node.java rename to src/main/java/m1graphs2025/pw2/Node.java index 40fa05e248bc4068e50919dd5fe17f9ec462ab90..237d53ae91deb8e0d64a52e724f1396ece054396 100644 --- a/src/main/java/m1graphs2025/Node.java +++ b/src/main/java/m1graphs2025/pw2/Node.java @@ -1,20 +1,12 @@ -package m1graphs2025; +package m1graphs2025.pw2; 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. @@ -38,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; } /** @@ -65,11 +64,12 @@ public class Node implements Comparable { public Node(Graph graph) { this.id = 0; this.graph = graph; + this.name = null; } - // ====================== - // Getters - // ====================== + // ================================================================================================================= + // =============================================Getters============================================================= + // ================================================================================================================= /** * Retourne l’identifiant du nœud. @@ -98,9 +98,9 @@ public class Node implements Comparable { return this.name; } - // ====================== - // Setters - // ====================== + // ================================================================================================================= + // ================================================Setters========================================================== + // ================================================================================================================= /** * Définit un nouvel identifiant pour ce nœud. @@ -120,9 +120,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 +359,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/pw2/NodeVisitInfo.java b/src/main/java/m1graphs2025/pw2/NodeVisitInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..98dd7ca731edb3fb9cd6fd24bac3fd61c12ca8ac --- /dev/null +++ b/src/main/java/m1graphs2025/pw2/NodeVisitInfo.java @@ -0,0 +1,173 @@ +package m1graphs2025.pw2; + +/** + * Représente les informations de visite d’un nœud lors d’un parcours en profondeur (DFS). + * + * Chaque nœud du graphe peut être : + * - WHITE : non visité + * - GRAY : en cours de visite + * - BLACK : complètement exploré + */ +public class NodeVisitInfo { + + /** Couleur du nœud (état dans le parcours DFS) */ + public enum NodeColour { WHITE, GRAY, BLACK } + + private NodeColour colour; // Couleur du nœud + private Node predecessor; // Nœud parent dans le parcours + private Integer discoveryTime; // Temps de découverte (d) + private Integer finishTime; // Temps de fin (f) + + // ================================================================================================================= + // ===========================================Constructeurs========================================================= + // ================================================================================================================= + + /** + * Constructeur par défaut : + * couleur initiale = WHITE, aucun prédécesseur, temps nuls. + */ + public NodeVisitInfo() { + this.colour = NodeColour.WHITE; + this.predecessor = null; + this.discoveryTime = null; + this.finishTime = null; + } + + /** + * Constructeur complet. + * + * @param colour couleur du nœud + * @param predecessor prédécesseur dans le parcours + * @param discoveryTime temps de découverte + * @param finishTime temps de fin + */ + public NodeVisitInfo(NodeColour colour, Node predecessor, Integer discoveryTime, Integer finishTime) { + this.colour = colour; + this.predecessor = predecessor; + this.discoveryTime = discoveryTime; + this.finishTime = finishTime; + } + + // ================================================================================================================= + // ===============================================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============================================================ + // ================================================================================================================= + + /** + * 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================================================== + // ================================================================================================================= + + /** + * 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; + this.predecessor = null; + 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/pw2/UndirectedGraph.java b/src/main/java/m1graphs2025/pw2/UndirectedGraph.java new file mode 100644 index 0000000000000000000000000000000000000000..585dc06f15499be66971e07f89f6821abcac166e --- /dev/null +++ b/src/main/java/m1graphs2025/pw2/UndirectedGraph.java @@ -0,0 +1,478 @@ +package m1graphs2025.pw2; + +import java.io.BufferedReader; +import java.io.FileReader; +import java.io.IOException; +import java.util.*; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class UndirectedGraph extends Graph { + + // ================================================================================================================= + // ===========================================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; + + while (i < taille) { + 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 succId = successorArray[i]; + Node nodeTo = nodes.computeIfAbsent(succId, key -> new Node(key, this)); + + list.add(new Edge(node, nodeTo)); + + if (node.getId() != nodeTo.getId()) { + adjEdList + .computeIfAbsent(nodeTo, key -> new ArrayList<>()) + .add(new Edge(nodeTo, node)); + } + + i++; + } + + 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); + } + + /** + * 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 + */ + @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; + } + } + + /** + * 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); + Node to = getNode(toId); + if (from == null) from = new Node(fromId, this); + if (to == null) to = new Node(toId, this); + 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); + Node to = getNode(toId); + if (from == null) from = new Node(fromId, this); + if (to == null) to = new Node(toId, this); + 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); + boolean r2 = super.removeEdge(to, from); + 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); + boolean r2 = super.removeEdge(toId, fromId); + 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); + Edge sym = e.getSymmetric(); + boolean r2 = (sym != null) && super.removeEdge(sym); + 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) { + 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<>(); + for (Node u : adjEdList.keySet()) { + List list = new ArrayList<>(); + for (Edge e : adjEdList.get(u)) + list.add(new Edge(e.getNodeFrom(), e.getNodeTo(), e.getWeight())); + copy.put(u, list); + } + return new UndirectedGraph(copy); + } + + // ================================================================================================================= + // ========================================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(); + List nodes = closure.getAllNodes(); + int n = nodes.size(); + for (Node k : nodes) { + for (Node i : nodes) { + for (Node j : nodes) { + if (closure.existsEdge(i, k) && closure.existsEdge(k, j)) { + if (!closure.existsEdge(i, j)) + closure.addEdge(i, j); + } + } + } + } + return closure; + } + + + // ================================================================================================================= + // ===============================================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))) { + String line; + + while ((line = dot.readLine()) != null) { + line = line.trim(); + if (line.isEmpty() || line.startsWith("graph") || line.startsWith("digraph") + || line.equals("{") || line.equals("}")) { + continue; // Ignore les lignes de structure DOT + } + + 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; + } + + if (line.contains("--")) { + String[] parts = line.split("--"); + if (parts.length < 2) continue; + + int fromId = Integer.parseInt(parts[0].replaceAll("[^0-9]", "")); + String toPart = parts[1].trim(); + int toId = Integer.parseInt(toPart.replaceAll("[^0-9].*$", "")); + + 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)); + } + + Node from = nodes.computeIfAbsent(fromId, key -> new Node(key, g)); + Node to = nodes.computeIfAbsent(toId, key -> new Node(key, g)); + + 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); + } + + 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 {\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()) { + /*sb.append(" ").append(from.getId()).append("\n");*/ + continue; + } + + for (Edge e : edges) { + Node u = e.from(); + Node v = e.to(); + + 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("}"); + 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========================================================= + // ================================================================================================================= + + /** + * Indique que ce graphe est non orienté (utile pour certaines fonctions). + */ + public boolean isDirected() { + return false; + } +} diff --git a/src/main/resources/isolatedNodes.gv b/src/main/resources/isolatedNodes.gv new file mode 100644 index 0000000000000000000000000000000000000000..221faf25bea089817d5b223621867353409a5cfa --- /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/lectureDFS.gv b/src/main/resources/lectureDFS.gv new file mode 100644 index 0000000000000000000000000000000000000000..d4a05a3413169fad98002261036004701c9e1913 --- /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/main/resources/multiGraph.gv b/src/main/resources/multiGraph.gv new file mode 100644 index 0000000000000000000000000000000000000000..d4bec97fe596a00d30501f8d12e9d59141a8f547 --- /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 0000000000000000000000000000000000000000..9658c6d2483d233a2fc8f48b361e5dc8180eab69 --- /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.dot similarity index 100% rename from src/main/java/m1graphs2025/undirWeightedMultiGraph.gv rename to src/main/resources/undirWeightedMultiGraph.dot diff --git a/src/main/resources/weightedMultiGraph.gv b/src/main/resources/weightedMultiGraph.gv new file mode 100644 index 0000000000000000000000000000000000000000..5ef48fd7155b8dcabc27417ce43c9d50c83b85ee --- /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.dot b/src/main/resources/weightedSimpleGraph.dot new file mode 100644 index 0000000000000000000000000000000000000000..c30550655950e595caef48e94d9f468b548b1797 --- /dev/null +++ b/src/main/resources/weightedSimpleGraph.dot @@ -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 0000000000000000000000000000000000000000..f4e3152e119a88592b30faa51c8a719832fba9f0 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart1.java @@ -0,0 +1,187 @@ +package m1graphs2025; + +import m1graphs2025.pw2.Edge; +import m1graphs2025.pw2.Graph; +import m1graphs2025.pw2.Node; + +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 0000000000000000000000000000000000000000..a22e57f4fbe336c5adcc77936605c89904f69658 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart2.java @@ -0,0 +1,60 @@ +package m1graphs2025; + +import m1graphs2025.pw2.Graph; +import m1graphs2025.pw2.Node; + +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 0000000000000000000000000000000000000000..35ca94f8d25e28b0bcd3f253b0b80c4031235bea --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart3.java @@ -0,0 +1,49 @@ +package m1graphs2025; + +import m1graphs2025.pw2.Edge; +import m1graphs2025.pw2.Graph; + +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.dot'"); + Graph wsg = Graph.fromDotFile("src/main/resources/weightedSimpleGraph.dot"); + if (wsg == null) + 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("---------------------"); + 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 0000000000000000000000000000000000000000..ae5b315e2c5830427dc5f923a8d3b3f57e22ea9f --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart4.java @@ -0,0 +1,114 @@ +package m1graphs2025; + +import m1graphs2025.pw2.Node; +import m1graphs2025.pw2.UndirectedGraph; + +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 0000000000000000000000000000000000000000..be9986f232759b3e549a410ae5e59d0bfd48b308 --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart5.java @@ -0,0 +1,74 @@ +package m1graphs2025; + +import m1graphs2025.pw2.Edge; +import m1graphs2025.pw2.Node; +import m1graphs2025.pw2.UndirectedGraph; + +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.dot'"); + + UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("src/main/resources/undirWeightedMultiGraph.dot"); + if (uwmg == null) + 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("---------------------"); + 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()); + */ + + } +} diff --git a/src/test/java/m1graphs2025/TestGraphPart6.java b/src/test/java/m1graphs2025/TestGraphPart6.java new file mode 100644 index 0000000000000000000000000000000000000000..a19378f7e1c5c62ae1ca5f94a32d9c0285b8123d --- /dev/null +++ b/src/test/java/m1graphs2025/TestGraphPart6.java @@ -0,0 +1,35 @@ +package m1graphs2025; + +import m1graphs2025.pw2.*; + +import java.util.HashMap; +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 6f9fb9b84331dd56967c7253361da598d2dcfd0c..da1d180a94700c387304115185e6521e80b919aa 100644 --- a/src/main/java/m1graphs2025/TestGraphsPW2.java +++ b/src/test/java/m1graphs2025/TestsGraphPW2.java @@ -1,10 +1,16 @@ 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; -public class TestGraphsPW2 { + +public class TestsGraphPW2 { public static void main(String[] args) { System.out.println("*--------------------------------------------------------------------*"); @@ -14,30 +20,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 +52,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 +90,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 +102,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 +112,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 +165,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 +187,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 +198,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 +209,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 +220,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 ***********************"); @@ -233,25 +238,25 @@ public class TestGraphsPW2 { 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"); - if (wsg == null) - System.out.println("Null graph was created from '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.dot'"); 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()) + 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,121 +265,124 @@ 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 ***********************"); 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"); - if (uwmg == null) - System.out.println("Null graph was created from 'undirWeightedMultiGraph.gv'"); + UndirectedGraph uwmg = (UndirectedGraph) UndirectedGraph.fromDotFile("undirWeightedMultiGraph.dot"); + if (uwmg==null) + 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("---------------------"); @@ -382,47 +390,43 @@ 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()); + */ }