diff --git a/.gitignore b/.gitignore index 480bdf524dd80e25ac77d9cdffdb815b807bdd51..efbcfbbffed41ae9f000618cbebe69cb3f7c36b8 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ target/ !**/src/main/**/target/ !**/src/test/**/target/ .kotlin +output/* ### IntelliJ IDEA ### .idea/modules.xml diff --git a/.idea/encodings.xml b/.idea/encodings.xml index 226ed61e93876cdbb6395e8ee6bd087bd486810a..e2d3ca17c6be707c14648f17d476b1ee19e133a5 100644 --- a/.idea/encodings.xml +++ b/.idea/encodings.xml @@ -1,6 +1,7 @@ + diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000000000000000000000000000000000000..f4d0a6b222b1997d868bb7036926a8ad6d34a118 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/input/example_flow.dot b/input/example_flow.dot new file mode 100644 index 0000000000000000000000000000000000000000..9f9e0d602a03608e194cf8649b66a381e745cadd --- /dev/null +++ b/input/example_flow.dot @@ -0,0 +1,32 @@ +digraph FlowTest { + rankdir="LR"; + 1 -> 2 [label="15"]; + 1 -> 3 [label="8"]; + 1 -> 4 [label="20"]; + + 2 -> 5 [label="12"]; + 2 -> 6 [label="6"]; + + 3 -> 2 [label="5"]; + 3 -> 7 [label="10"]; + + 4 -> 7 [label="5"]; + 4 -> 8 [label="15"]; + + 5 -> 9 [label="10"]; + 5 -> 6 [label="4"]; + + 6 -> 9 [label="7"]; + 6 -> 10 [label="10"]; + + 7 -> 5 [label="2"]; + 7 -> 10 [label="8"]; + 7 -> 11 [label="5"]; + + 8 -> 7 [label="4"]; + 8 -> 11 [label="12"]; + + 9 -> 12 [label="15"]; + 10 -> 12 [label="10"]; + 11 -> 12 [label="8"]; +} diff --git a/output/flow1.gv b/output/flow1.gv new file mode 100644 index 0000000000000000000000000000000000000000..f4418c44dffe2817e5612d436b13c8d95bf11809 --- /dev/null +++ b/output/flow1.gv @@ -0,0 +1,25 @@ +digraph flow1 { + rankdir="LR"; + label="(1) Flow. Value: 0"; + 9 -> 12 [label="0/15", len=15]; + 10 -> 12 [label="0/10", len=10]; + 11 -> 12 [label="0/8", len=8]; + 5 -> 9 [label="0/10", len=10]; + 5 -> 6 [label="0/4", len=4]; + 6 -> 9 [label="0/7", len=7]; + 6 -> 10 [label="0/10", len=10]; + 7 -> 5 [label="0/2", len=2]; + 7 -> 10 [label="0/8", len=8]; + 7 -> 11 [label="0/5", len=5]; + 8 -> 7 [label="0/4", len=4]; + 8 -> 11 [label="0/12", len=12]; + 1 -> 2 [label="0/15", len=15]; + 1 -> 3 [label="0/8", len=8]; + 1 -> 4 [label="0/20", len=20]; + 2 -> 5 [label="0/12", len=12]; + 2 -> 6 [label="0/6", len=6]; + 3 -> 2 [label="0/5", len=5]; + 3 -> 7 [label="0/10", len=10]; + 4 -> 7 [label="0/5", len=5]; + 4 -> 8 [label="0/15", len=15]; +} diff --git a/output/flow2.gv b/output/flow2.gv new file mode 100644 index 0000000000000000000000000000000000000000..80b940f3ef3b9c8e31c785f028e8dcd759f64ff3 --- /dev/null +++ b/output/flow2.gv @@ -0,0 +1,25 @@ +digraph flow2 { + rankdir="LR"; + label="(2) Flow. Value: 10"; + 9 -> 12 [label="10/15", len=15]; + 10 -> 12 [label="0/10", len=10]; + 11 -> 12 [label="0/8", len=8]; + 5 -> 9 [label="10/10", len=10]; + 5 -> 6 [label="0/4", len=4]; + 6 -> 9 [label="0/7", len=7]; + 6 -> 10 [label="0/10", len=10]; + 7 -> 5 [label="0/2", len=2]; + 7 -> 10 [label="0/8", len=8]; + 7 -> 11 [label="0/5", len=5]; + 8 -> 7 [label="0/4", len=4]; + 8 -> 11 [label="0/12", len=12]; + 1 -> 2 [label="10/15", len=15]; + 1 -> 3 [label="0/8", len=8]; + 1 -> 4 [label="0/20", len=20]; + 2 -> 5 [label="10/12", len=12]; + 2 -> 6 [label="0/6", len=6]; + 3 -> 2 [label="0/5", len=5]; + 3 -> 7 [label="0/10", len=10]; + 4 -> 7 [label="0/5", len=5]; + 4 -> 8 [label="0/15", len=15]; +} diff --git a/output/flow3.gv b/output/flow3.gv new file mode 100644 index 0000000000000000000000000000000000000000..561cae4c8a0d65df077b9711e25f634a456c3e16 --- /dev/null +++ b/output/flow3.gv @@ -0,0 +1,25 @@ +digraph flow3 { + rankdir="LR"; + label="(3) Flow. Value: 18"; + 9 -> 12 [label="10/15", len=15]; + 10 -> 12 [label="0/10", len=10]; + 11 -> 12 [label="8/8", len=8]; + 5 -> 9 [label="10/10", len=10]; + 5 -> 6 [label="0/4", len=4]; + 6 -> 9 [label="0/7", len=7]; + 6 -> 10 [label="0/10", len=10]; + 7 -> 5 [label="0/2", len=2]; + 7 -> 10 [label="0/8", len=8]; + 7 -> 11 [label="0/5", len=5]; + 8 -> 7 [label="0/4", len=4]; + 8 -> 11 [label="8/12", len=12]; + 1 -> 2 [label="10/15", len=15]; + 1 -> 3 [label="0/8", len=8]; + 1 -> 4 [label="8/20", len=20]; + 2 -> 5 [label="10/12", len=12]; + 2 -> 6 [label="0/6", len=6]; + 3 -> 2 [label="0/5", len=5]; + 3 -> 7 [label="0/10", len=10]; + 4 -> 7 [label="0/5", len=5]; + 4 -> 8 [label="8/15", len=15]; +} diff --git a/output/flow4.gv b/output/flow4.gv new file mode 100644 index 0000000000000000000000000000000000000000..edb242db56bf37afeeaaa65d50dd99327cf02a31 --- /dev/null +++ b/output/flow4.gv @@ -0,0 +1,25 @@ +digraph flow4 { + rankdir="LR"; + label="(4) Flow. Value: 26"; + 9 -> 12 [label="10/15", len=15]; + 10 -> 12 [label="8/10", len=10]; + 11 -> 12 [label="8/8", len=8]; + 5 -> 9 [label="10/10", len=10]; + 5 -> 6 [label="0/4", len=4]; + 6 -> 9 [label="0/7", len=7]; + 6 -> 10 [label="0/10", len=10]; + 7 -> 5 [label="0/2", len=2]; + 7 -> 10 [label="8/8", len=8]; + 7 -> 11 [label="0/5", len=5]; + 8 -> 7 [label="0/4", len=4]; + 8 -> 11 [label="8/12", len=12]; + 1 -> 2 [label="10/15", len=15]; + 1 -> 3 [label="8/8", len=8]; + 1 -> 4 [label="8/20", len=20]; + 2 -> 5 [label="10/12", len=12]; + 2 -> 6 [label="0/6", len=6]; + 3 -> 2 [label="0/5", len=5]; + 3 -> 7 [label="8/10", len=10]; + 4 -> 7 [label="0/5", len=5]; + 4 -> 8 [label="8/15", len=15]; +} diff --git a/output/flow5.gv b/output/flow5.gv new file mode 100644 index 0000000000000000000000000000000000000000..e8a5766d7f74ee90dd2e7725211978b2b007ce3a --- /dev/null +++ b/output/flow5.gv @@ -0,0 +1,25 @@ +digraph flow5 { + rankdir="LR"; + label="(5) Flow. Value: 31"; + 9 -> 12 [label="15/15", len=15]; + 10 -> 12 [label="8/10", len=10]; + 11 -> 12 [label="8/8", len=8]; + 5 -> 9 [label="10/10", len=10]; + 5 -> 6 [label="0/4", len=4]; + 6 -> 9 [label="5/7", len=7]; + 6 -> 10 [label="0/10", len=10]; + 7 -> 5 [label="0/2", len=2]; + 7 -> 10 [label="8/8", len=8]; + 7 -> 11 [label="0/5", len=5]; + 8 -> 7 [label="0/4", len=4]; + 8 -> 11 [label="8/12", len=12]; + 1 -> 2 [label="15/15", len=15]; + 1 -> 3 [label="8/8", len=8]; + 1 -> 4 [label="8/20", len=20]; + 2 -> 5 [label="10/12", len=12]; + 2 -> 6 [label="5/6", len=6]; + 3 -> 2 [label="0/5", len=5]; + 3 -> 7 [label="8/10", len=10]; + 4 -> 7 [label="0/5", len=5]; + 4 -> 8 [label="8/15", len=15]; +} diff --git a/output/residualGraph1.gv b/output/residualGraph1.gv new file mode 100644 index 0000000000000000000000000000000000000000..6a24e2123f256d5872893b64bd6f0a5d68ff2596 --- /dev/null +++ b/output/residualGraph1.gv @@ -0,0 +1,27 @@ +digraph residualGraph1 { + rankdir="LR"; + label="(1) residual graph. + Augmenting path: [1, 2, 5, 9, 12]. + Residual capacity: 10"; + 5 -> 9 [label="10", len=10, penwidth=3, color="blue", fontcolor="red"]; + 5 -> 6 [label="4", len=4]; + 4 -> 7 [label="5", len=5]; + 4 -> 8 [label="15", len=15]; + 3 -> 2 [label="5", len=5]; + 3 -> 7 [label="10", len=10]; + 2 -> 5 [label="12", len=12, penwidth=3, color="blue"]; + 2 -> 6 [label="6", len=6]; + 1 -> 2 [label="15", len=15, penwidth=3, color="blue"]; + 1 -> 3 [label="8", len=8]; + 1 -> 4 [label="20", len=20]; + 11 -> 12 [label="8", len=8]; + 10 -> 12 [label="10", len=10]; + 9 -> 12 [label="15", len=15, penwidth=3, color="blue"]; + 8 -> 7 [label="4", len=4]; + 8 -> 11 [label="12", len=12]; + 7 -> 5 [label="2", len=2]; + 7 -> 10 [label="8", len=8]; + 7 -> 11 [label="5", len=5]; + 6 -> 9 [label="7", len=7]; + 6 -> 10 [label="10", len=10]; +} diff --git a/output/residualGraph2.gv b/output/residualGraph2.gv new file mode 100644 index 0000000000000000000000000000000000000000..647b18f5ac39f4a1e3e2100f53d19f8f9e3fafac --- /dev/null +++ b/output/residualGraph2.gv @@ -0,0 +1,30 @@ +digraph residualGraph2 { + rankdir="LR"; + label="(2) residual graph. + Augmenting path: [1, 4, 8, 11, 12]. + Residual capacity: 8"; + 3 -> 2 [label="5", len=5]; + 3 -> 7 [label="10", len=10]; + 2 -> 1 [label="10", len=10]; + 2 -> 5 [label="2", len=2]; + 2 -> 6 [label="6", len=6]; + 1 -> 2 [label="5", len=5]; + 1 -> 3 [label="8", len=8]; + 1 -> 4 [label="20", len=20, penwidth=3, color="blue"]; + 7 -> 5 [label="2", len=2]; + 7 -> 10 [label="8", len=8]; + 7 -> 11 [label="5", len=5]; + 6 -> 9 [label="7", len=7]; + 6 -> 10 [label="10", len=10]; + 5 -> 6 [label="4", len=4]; + 5 -> 2 [label="10", len=10]; + 4 -> 7 [label="5", len=5]; + 4 -> 8 [label="15", len=15, penwidth=3, color="blue"]; + 11 -> 12 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"]; + 10 -> 12 [label="10", len=10]; + 9 -> 12 [label="5", len=5]; + 9 -> 5 [label="10", len=10]; + 8 -> 7 [label="4", len=4]; + 8 -> 11 [label="12", len=12, penwidth=3, color="blue"]; + 12 -> 9 [label="10", len=10]; +} diff --git a/output/residualGraph3.gv b/output/residualGraph3.gv new file mode 100644 index 0000000000000000000000000000000000000000..f692262a5e1afac515f1cbb77ca8a13342f73042 --- /dev/null +++ b/output/residualGraph3.gv @@ -0,0 +1,33 @@ +digraph residualGraph3 { + rankdir="LR"; + label="(3) residual graph. + Augmenting path: [1, 3, 7, 10, 12]. + Residual capacity: 8"; + 6 -> 9 [label="7", len=7]; + 6 -> 10 [label="10", len=10]; + 7 -> 5 [label="2", len=2]; + 7 -> 10 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"]; + 7 -> 11 [label="5", len=5]; + 8 -> 7 [label="4", len=4]; + 8 -> 11 [label="4", len=4]; + 8 -> 4 [label="8", len=8]; + 9 -> 12 [label="5", len=5]; + 9 -> 5 [label="10", len=10]; + 2 -> 1 [label="10", len=10]; + 2 -> 5 [label="2", len=2]; + 2 -> 6 [label="6", len=6]; + 3 -> 2 [label="5", len=5]; + 3 -> 7 [label="10", len=10, penwidth=3, color="blue"]; + 4 -> 1 [label="8", len=8]; + 4 -> 7 [label="5", len=5]; + 4 -> 8 [label="7", len=7]; + 5 -> 6 [label="4", len=4]; + 5 -> 2 [label="10", len=10]; + 1 -> 2 [label="5", len=5]; + 1 -> 3 [label="8", len=8, penwidth=3, color="blue", fontcolor="red"]; + 1 -> 4 [label="12", len=12]; + 10 -> 12 [label="10", len=10, penwidth=3, color="blue"]; + 11 -> 8 [label="8", len=8]; + 12 -> 9 [label="10", len=10]; + 12 -> 11 [label="8", len=8]; +} diff --git a/output/residualGraph4.gv b/output/residualGraph4.gv new file mode 100644 index 0000000000000000000000000000000000000000..3c5dfbfefc6dc9eeb01150609d15ca716f9a86f2 --- /dev/null +++ b/output/residualGraph4.gv @@ -0,0 +1,35 @@ +digraph residualGraph4 { + rankdir="LR"; + label="(4) residual graph. + Augmenting path: [1, 2, 6, 9, 12]. + Residual capacity: 5"; + 9 -> 12 [label="5", len=5, penwidth=3, color="blue", fontcolor="red"]; + 9 -> 5 [label="10", len=10]; + 10 -> 12 [label="2", len=2]; + 10 -> 7 [label="8", len=8]; + 7 -> 5 [label="2", len=2]; + 7 -> 11 [label="5", len=5]; + 7 -> 3 [label="8", len=8]; + 8 -> 7 [label="4", len=4]; + 8 -> 11 [label="4", len=4]; + 8 -> 4 [label="8", len=8]; + 11 -> 8 [label="8", len=8]; + 12 -> 9 [label="10", len=10]; + 12 -> 10 [label="8", len=8]; + 12 -> 11 [label="8", len=8]; + 1 -> 2 [label="5", len=5, penwidth=3, color="blue", fontcolor="red"]; + 1 -> 4 [label="12", len=12]; + 2 -> 1 [label="10", len=10]; + 2 -> 5 [label="2", len=2]; + 2 -> 6 [label="6", len=6, penwidth=3, color="blue"]; + 5 -> 6 [label="4", len=4]; + 5 -> 2 [label="10", len=10]; + 6 -> 9 [label="7", len=7, penwidth=3, color="blue"]; + 6 -> 10 [label="10", len=10]; + 3 -> 1 [label="8", len=8]; + 3 -> 2 [label="5", len=5]; + 3 -> 7 [label="2", len=2]; + 4 -> 1 [label="8", len=8]; + 4 -> 7 [label="5", len=5]; + 4 -> 8 [label="7", len=7]; +} diff --git a/output/residualGraph5.gv b/output/residualGraph5.gv new file mode 100644 index 0000000000000000000000000000000000000000..deba65b8121bb046cb88f31d262cc07ce7109118 --- /dev/null +++ b/output/residualGraph5.gv @@ -0,0 +1,35 @@ +digraph residualGraph5 { + rankdir="LR"; + label="(5) residual graph. + Augmenting path: [1, 4, 7, 5, 6, 10, 12]. + Residual capacity: 2"; + 7 -> 5 [label="2", len=2, penwidth=3, color="blue", fontcolor="red"]; + 7 -> 11 [label="5", len=5]; + 7 -> 3 [label="8", len=8]; + 8 -> 7 [label="4", len=4]; + 8 -> 11 [label="4", len=4]; + 8 -> 4 [label="8", len=8]; + 5 -> 6 [label="4", len=4, penwidth=3, color="blue"]; + 5 -> 2 [label="10", len=10]; + 6 -> 9 [label="2", len=2]; + 6 -> 10 [label="10", len=10, penwidth=3, color="blue"]; + 6 -> 2 [label="5", len=5]; + 3 -> 1 [label="8", len=8]; + 3 -> 2 [label="5", len=5]; + 3 -> 7 [label="2", len=2]; + 4 -> 1 [label="8", len=8]; + 4 -> 7 [label="5", len=5, penwidth=3, color="blue"]; + 4 -> 8 [label="7", len=7]; + 1 -> 4 [label="12", len=12, penwidth=3, color="blue"]; + 2 -> 1 [label="15", len=15]; + 2 -> 5 [label="2", len=2]; + 2 -> 6 [label="1", len=1]; + 11 -> 8 [label="8", len=8]; + 12 -> 9 [label="15", len=15]; + 12 -> 10 [label="8", len=8]; + 12 -> 11 [label="8", len=8]; + 9 -> 5 [label="10", len=10]; + 9 -> 6 [label="5", len=5]; + 10 -> 12 [label="2", len=2, penwidth=3, color="blue", fontcolor="red"]; + 10 -> 7 [label="8", len=8]; +} diff --git a/output/step_1_flow.dot b/output/step_1_flow.dot deleted file mode 100644 index 5a13e73d3c959e0d13f8e6f952daf531ec08b6ac..0000000000000000000000000000000000000000 --- a/output/step_1_flow.dot +++ /dev/null @@ -1,18 +0,0 @@ -graph { - rankdir=LR; - 1 -> 2 [label=4] - 1 -> 3 [label=3] - 1 -> 3 [label=6] - 1 -> 4 [label=0] - 2 -> 3 [label=1] - 2 -> 4 [label=2] - 2 -> 4 [label=50] - 2 -> 5 [label=3] - 3 -> 5 [label=2] - 4 -> 4 [label=14] - 4 -> 5 [label=2] - 5 -> 6 [label=7] - 5 -> 6 [label=10] - 6 -> 6 [label=5] - 6 -> 6 [label=8] -} \ No newline at end of file diff --git a/output/step_1_residual.dot b/output/step_1_residual.dot deleted file mode 100644 index d750d65ff98b52c6c7c92717115c0b7f0b46cd3a..0000000000000000000000000000000000000000 --- a/output/step_1_residual.dot +++ /dev/null @@ -1,15 +0,0 @@ -graph { - rankdir=LR; - 1 -> 2 [label=1] - 1 -> 3 [label=4] - 2 -> 1 [label=3] - 2 -> 3 [label=1] - 2 -> 4 [label=50] - 3 -> 1 [label=2] - 4 -> 4 [label=14] - 4 -> 5 [label=2] - 5 -> 2 [label=3] - 5 -> 3 [label=2] - 5 -> 6 [label=10] - 6 -> 6 [label=8] -} \ No newline at end of file diff --git a/output/step_2_flow.dot b/output/step_2_flow.dot deleted file mode 100644 index 5a13e73d3c959e0d13f8e6f952daf531ec08b6ac..0000000000000000000000000000000000000000 --- a/output/step_2_flow.dot +++ /dev/null @@ -1,18 +0,0 @@ -graph { - rankdir=LR; - 1 -> 2 [label=4] - 1 -> 3 [label=3] - 1 -> 3 [label=6] - 1 -> 4 [label=0] - 2 -> 3 [label=1] - 2 -> 4 [label=2] - 2 -> 4 [label=50] - 2 -> 5 [label=3] - 3 -> 5 [label=2] - 4 -> 4 [label=14] - 4 -> 5 [label=2] - 5 -> 6 [label=7] - 5 -> 6 [label=10] - 6 -> 6 [label=5] - 6 -> 6 [label=8] -} \ No newline at end of file diff --git a/output/step_2_residual.dot b/output/step_2_residual.dot deleted file mode 100644 index 5646477b238ac9075a38611a2de42fc83c1211c0..0000000000000000000000000000000000000000 --- a/output/step_2_residual.dot +++ /dev/null @@ -1,14 +0,0 @@ -graph { - rankdir=LR; - 1 -> 2 [label=1] - 1 -> 3 [label=6] - 2 -> 1 [label=3] - 2 -> 3 [label=1] - 2 -> 4 [label=50] - 3 -> 5 [label=2] - 4 -> 4 [label=14] - 4 -> 5 [label=2] - 5 -> 2 [label=3] - 5 -> 6 [label=10] - 6 -> 6 [label=8] -} \ No newline at end of file diff --git a/output/step_3_flow.dot b/output/step_3_flow.dot deleted file mode 100644 index 5a13e73d3c959e0d13f8e6f952daf531ec08b6ac..0000000000000000000000000000000000000000 --- a/output/step_3_flow.dot +++ /dev/null @@ -1,18 +0,0 @@ -graph { - rankdir=LR; - 1 -> 2 [label=4] - 1 -> 3 [label=3] - 1 -> 3 [label=6] - 1 -> 4 [label=0] - 2 -> 3 [label=1] - 2 -> 4 [label=2] - 2 -> 4 [label=50] - 2 -> 5 [label=3] - 3 -> 5 [label=2] - 4 -> 4 [label=14] - 4 -> 5 [label=2] - 5 -> 6 [label=7] - 5 -> 6 [label=10] - 6 -> 6 [label=5] - 6 -> 6 [label=8] -} \ No newline at end of file diff --git a/output/step_3_residual.dot b/output/step_3_residual.dot deleted file mode 100644 index 14f08a2b2fe4224bce88b9237bad24edf623bd37..0000000000000000000000000000000000000000 --- a/output/step_3_residual.dot +++ /dev/null @@ -1,13 +0,0 @@ -graph { - rankdir=LR; - 1 -> 2 [label=4] - 1 -> 3 [label=6] - 2 -> 3 [label=1] - 2 -> 4 [label=50] - 2 -> 5 [label=3] - 3 -> 5 [label=2] - 4 -> 4 [label=14] - 4 -> 5 [label=2] - 5 -> 6 [label=10] - 6 -> 6 [label=8] -} \ No newline at end of file diff --git a/src/main/java/MaximumFlow/DotIO.java b/src/main/java/MaximumFlow/DotIO.java deleted file mode 100644 index 919a18423f8fdb9b50c901fda0ea3999e0c1b345..0000000000000000000000000000000000000000 --- a/src/main/java/MaximumFlow/DotIO.java +++ /dev/null @@ -1,69 +0,0 @@ -package MaximumFlow; - -import m1graphs2025.Graph; -import java.io.File; - -/** - * Classe responsable de la lecture et de l’écriture des graphes DOT - * pour le projet Maximum Flow (PW3). - */ -public class DotIO { - - /** - * Charge un graphe depuis un fichier DOT et crée un FlowNetwork. - * - * @param filename Le fichier DOT à lire - * @param source Indice du sommet source - * @param sink Indice du sommet puits - * @return FlowNetwork correspondant - */ - public static FlowNetwork loadFromDot(String filename, int source, int sink) { - Graph g = Graph.fromDotFile(filename); - return new FlowNetwork(g, source, sink); - } - - /** - * Sauvegarde les fichiers DOT pour le flux et le graphe résiduel à une étape donnée. - * - * @param flow FlowNetwork actuel - * @param residual ResidualGraph actuel - * @param step Numéro de l'étape - * @param outputDir Dossier de sortie (par ex. "output") - */ - public static void saveStepFiles(FlowNetwork flow, ResidualGraph residual, int step, String outputDir) { - File dir = new File(outputDir); - if (!dir.exists() && !dir.mkdirs()) { - System.err.println("[Erreur] Impossible de créer le dossier '" + outputDir + "'."); - return; - } - - String flowPath = stepFilePath(outputDir, step, "flow"); - String residualPath = stepFilePath(outputDir, step, "residual"); - - try { - if (flow.getBaseGraph() != null) { - flow.getBaseGraph().toDotFile(flowPath, "dot"); - } - if (residual.getGraph() != null) { - residual.getGraph().toDotFile(residualPath, "dot"); - } - System.out.println("→ Étape " + step + " sauvegardée :"); - System.out.println(" " + flowPath); - System.out.println(" " + residualPath); - } catch (Exception e) { - System.err.println("[Erreur] Échec de la sauvegarde : " + e.getMessage()); - } - } - - /** - * Génère le chemin du fichier pour une étape et un type donné. - * - * @param outputDir Dossier de sortie - * @param step Numéro de l'étape - * @param type "flow" ou "residual" - * @return Chemin complet du fichier DOT - */ - private static String stepFilePath(String outputDir, int step, String type) { - return outputDir + "/step_" + step + "_" + type; - } -} diff --git a/src/main/java/MaximumFlow/FlowMap.java b/src/main/java/MaximumFlow/FlowMap.java deleted file mode 100644 index 238fa688e5ab3cb639e471df14253e22a156786f..0000000000000000000000000000000000000000 --- a/src/main/java/MaximumFlow/FlowMap.java +++ /dev/null @@ -1,86 +0,0 @@ -package MaximumFlow; - -import m1graphs2025.*; -import java.util.*; - -public class FlowMap { - - private Map flowmap = new HashMap<>(); - - // ------------------------------------------------------------- - // Encodage / décodage de clé "u:v" - // ------------------------------------------------------------- - - private String key(Node u, Node v) { - return u.getId() + ":" + v.getId(); - } - - private String key(int u, int v) { - return u + ":" + v; - } - - private int[] decodeKey(String key) { - String[] parts = key.split(":"); - return new int[]{ - Integer.parseInt(parts[0]), - Integer.parseInt(parts[1]) - }; - } - - // ------------------------------------------------------------- - // Accès au flux - // ------------------------------------------------------------- - - public int get(Node u, Node v) { - return flowmap.getOrDefault(key(u, v), 0); - } - - public int get(int u, int v) { - return flowmap.getOrDefault(key(u, v), 0); - } - - public void set(Node u, Node v, int val) { - if (val < 0) - throw new IllegalArgumentException("Flux négatif interdit : " + val); - flowmap.put(key(u, v), val); - } - - public void set(int u, int v, int val) { - if (val < 0) - throw new IllegalArgumentException("Flux négatif interdit : " + val); - flowmap.put(key(u, v), val); - } - - public void add(Node u, Node v, int val) { - int newVal = get(u, v) + val; - if (newVal < 0) - throw new IllegalArgumentException("Flux négatif interdit : " + newVal); - flowmap.put(key(u, v), newVal); - } - - public void add(int u, int v, int val) { - int newVal = get(u, v) + val; - if (newVal < 0) - throw new IllegalArgumentException("Flux négatif interdit : " + newVal); - flowmap.put(key(u, v), newVal); - } - - // ------------------------------------------------------------- - // Outils utiles - // ------------------------------------------------------------- - - /** Retourne toutes les clés "u:v" */ - public Set keys() { - return flowmap.keySet(); - } - - /** Retourne directement les couples ("u:v", valeur) */ - public Set> entries() { - return flowmap.entrySet(); - } - - /** Vide complètement tous les flux */ - public void reset() { - flowmap.clear(); - } -} diff --git a/src/main/java/MaximumFlow/FlowNetwork.java b/src/main/java/MaximumFlow/FlowNetwork.java deleted file mode 100644 index f658b00cadf3a8d4fe01513d9e355ed1f542081d..0000000000000000000000000000000000000000 --- a/src/main/java/MaximumFlow/FlowNetwork.java +++ /dev/null @@ -1,132 +0,0 @@ -package MaximumFlow; - -import m1graphs2025.*; -import java.util.*; -import java.io.FileWriter; -import java.io.IOException; - -public class FlowNetwork { - - private Graph baseGraph; // Graphe de capacités - private FlowMap flow = new FlowMap(); - private Map capacity = new HashMap<>(); - - private int sourceId; - private int sinkId; - - // ----------------------------------------------------- - // CONSTRUCTEUR - // ----------------------------------------------------- - public FlowNetwork(Graph baseGraph, int sourceId, int sinkId) { - this.baseGraph = baseGraph; - this.sourceId = sourceId; - this.sinkId = sinkId; - - // Charger les capacités depuis baseGraph - for (Edge e : baseGraph.getAllEdges()) { - Node u = e.getNodeFrom(); - Node v = e.getNodeTo(); - capacity.put(key(u, v), e.getWeight()); - } - } - - // ----------------------------------------------------- - // GETTERS - // ----------------------------------------------------- - public Graph getBaseGraph() { return baseGraph; } - public int getSourceId() { return sourceId; } - public int getSinkId() { return sinkId; } - - // ----------------------------------------------------- - // CAPACITÉS - // ----------------------------------------------------- - private String key(Node u, Node v) { - return u.getId() + ":" + v.getId(); - } - - public int getCapacity(Node u, Node v) { - return capacity.getOrDefault(key(u, v), 0); - } - - public void setCapacity(Node u, Node v, int value) { - capacity.put(key(u, v), value); - } - - // ----------------------------------------------------- - // FLUX - // ----------------------------------------------------- - public int getFlow(Node u, Node v) { - return flow.get(u, v); - } - - public int getResidualCapacity(Node u, Node v) { - return getCapacity(u, v) - getFlow(u, v); - } - - // ----------------------------------------------------- - // CRÉATION DU GRAPHE RÉSIDUEL - // ----------------------------------------------------- - public ResidualGraph toResidualGraph() { - return new ResidualGraph(this); - } - - // ----------------------------------------------------- - // MISE À JOUR DU FLUX (chemin augmentant) - // ----------------------------------------------------- - public void updateFlow(List path, int delta) { - - for (int i = 0; i < path.size() - 1; i++) { - - Node u = path.get(i); - Node v = path.get(i + 1); - - // Cas 1 : arc direct (u -> v) - if (getCapacity(u, v) > 0) { - flow.add(u, v, delta); - } - // Cas 2 : arc inverse (v -> u) - else { - flow.add(v, u, -delta); - } - } - } - - public int getSource() { return sourceId; } - public int getSink() { return sinkId; } - - // ----------------------------------------------------- - // EXPORTER LE GRAPHE DE FLUX EN DOT - // ----------------------------------------------------- - public void toDotFileFlow(String filename, int step) { - int totalFlow = 0; - - // Calculer le flot total en sortie de la source - Node source = baseGraph.getNode(sourceId); - for (Edge e : baseGraph.getOutEdges(source)) { - totalFlow += getFlow(e.getNodeFrom(), e.getNodeTo()); - } - - try (FileWriter writer = new FileWriter(filename)) { - writer.write("digraph flow" + step + " {\n"); - writer.write(" rankdir=\"LR\";\n"); - writer.write(" label=\"(" + step + ") Flow induced from residual graph " - + (step-1) + ". Value: " + totalFlow + "\";\n"); - - // Arêtes avec label f/c - for (Edge e : baseGraph.getAllEdges()) { - Node u = e.getNodeFrom(); - Node v = e.getNodeTo(); - int f = getFlow(u, v); - int c = getCapacity(u, v); - - writer.write(" " + u.getId() + " -> " + v.getId() + - " [label=\"" + f + "/" + c + "\", len=" + c + "];\n"); - } - - writer.write("}\n"); - System.out.println("Flow DOT file saved: " + filename); - } catch (IOException e) { - System.err.println("[Erreur] Impossible d'écrire le fichier DOT : " + e.getMessage()); - } - } -} diff --git a/src/main/java/MaximumFlow/FordFulkerson.java b/src/main/java/MaximumFlow/FordFulkerson.java deleted file mode 100644 index 0d92ed77b38e67dc9ce3a467bb75caad12dd6248..0000000000000000000000000000000000000000 --- a/src/main/java/MaximumFlow/FordFulkerson.java +++ /dev/null @@ -1,27 +0,0 @@ -package MaximumFlow; - -// --- FordFulkerson.java --- -public class FordFulkerson { - public int computeMaxFlow(FlowNetwork network) { - FlowMap flow = new FlowMap(); - int maxFlow = 0; - - - while (true) { - ResidualGraph residual = network.toResidualGraph(); - var path = PathFinder.findPathBFS(residual, network.getSource(), network.getSink()); - if (path == null) break; - - - int delta = residual.minCapacity(path); - network.updateFlow(path, delta); - try { - DotIO.saveStepFiles(network, residual, delta, "output"); - } catch (Exception e) { - throw new RuntimeException(e); - } - maxFlow += delta; - } - return maxFlow; - } -} \ No newline at end of file diff --git a/src/main/java/MaximumFlow/Main.java b/src/main/java/MaximumFlow/Main.java deleted file mode 100644 index c80ef900ace64a7b7f1c1aff035e342fc5bb9756..0000000000000000000000000000000000000000 --- a/src/main/java/MaximumFlow/Main.java +++ /dev/null @@ -1,37 +0,0 @@ -package MaximumFlow; - -import m1graphs2025.*; - -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -public class Main { - public static void main(String[] args) { - if (args.length > 0) { - String path = args[0]; - System.out.println("Lecture du graphe depuis : " + path); - Graph g = Graph.fromDotFile("src/main/resources/undirWeightedMultiGraph.dot"); - // --- Construction du réseau --- - FlowNetwork network = new FlowNetwork(g, 1, 5); - - // --- Calcul du flot maximum --- - FordFulkerson solver = new FordFulkerson(); - int maxFlow = solver.computeMaxFlow(network); - - System.out.println("Maximum flow = " + maxFlow); - } else { - System.out.println("Aucun fichier spécifié. Création manuelle du graphe..."); - Graph g = Graph.fromDotFile("src/main/resources/undirWeightedMultiGraph.dot"); - - // --- Construction du réseau --- - FlowNetwork network = new FlowNetwork(g, 1, 5); - - // --- Calcul du flot maximum --- - FordFulkerson solver = new FordFulkerson(); - int maxFlow = solver.computeMaxFlow(network); - - System.out.println("Maximum flow = " + maxFlow); - } - } -} diff --git a/src/main/java/MaximumFlow/PathFinder.java b/src/main/java/MaximumFlow/PathFinder.java deleted file mode 100644 index 77cde8631422c8409bd5a2ca0cad65f6865d8e69..0000000000000000000000000000000000000000 --- a/src/main/java/MaximumFlow/PathFinder.java +++ /dev/null @@ -1,62 +0,0 @@ -package MaximumFlow; - -import m1graphs2025.*; -import java.util.*; - -public class PathFinder { - - /** - * BFS standard dans le graphe résiduel. - * Retourne un chemin s -> t en liste de Node. - */ - public static List findPathBFS(ResidualGraph residual, int sourceId, int sinkId) { - - Graph g = residual.getGraph(); - - Node s = g.getNode(sourceId); - Node t = g.getNode(sinkId); - - if (s == null || t == null) - return null; - - // BFS - Map parent = new HashMap<>(); - Queue queue = new LinkedList<>(); - - parent.put(s, null); - queue.add(s); - - while (!queue.isEmpty()) { - - Node u = queue.poll(); - - if (u.equals(t)) - break; - - for (Edge e : g.getOutEdges(u)) { - - if (e.getWeight() <= 0) - continue; - - Node v = e.getNodeTo(); - - if (!parent.containsKey(v)) { - parent.put(v, u); - queue.add(v); - } - } - } - - // Pas de chemin - if (!parent.containsKey(t)) - return null; - - // Reconstruction du chemin - List path = new ArrayList<>(); - for (Node v = t; v != null; v = parent.get(v)) { - path.add(0, v); - } - - return path; - } -} diff --git a/src/main/java/MaximumFlow/ResidualGraph.java b/src/main/java/MaximumFlow/ResidualGraph.java deleted file mode 100644 index 23cf5fb81ba2b43c21e570ad22bbcd8d3547b367..0000000000000000000000000000000000000000 --- a/src/main/java/MaximumFlow/ResidualGraph.java +++ /dev/null @@ -1,156 +0,0 @@ -package MaximumFlow; - -import m1graphs2025.*; -import java.util.*; -import java.io.FileWriter; -import java.io.IOException; - -public class ResidualGraph { - - private Graph graph; // graphe résiduel complet - - // --------------------------------------------------------- - // CONSTRUCTEUR : construit le graphe résiduel à partir du FlowNetwork - // --------------------------------------------------------- - public ResidualGraph(FlowNetwork network) { - - graph = new Graph(new HashMap<>()); - - // 1) Ajouter tous les nœuds du graphe de base - for (Node n : network.getBaseGraph().getAllNodes()) { - graph.addNode(n.getId()); - } - - // 2) Ajouter les arcs résiduels directs + inverses - for (Edge e : network.getBaseGraph().getAllEdges()) { - - Node u = e.getNodeFrom(); - Node v = e.getNodeTo(); - - int resCap = network.getResidualCapacity(u, v); // cap(u,v) - flow(u,v) - int backCap = network.getFlow(u, v); // flux(u,v) - - // (u -> v) direct si capacité résiduelle > 0 - if (resCap > 0) { - addOrReplaceEdge(u.getId(), v.getId(), resCap); - } - - // (v -> u) inverse si flux > 0 - if (backCap > 0) { - addOrReplaceEdge(v.getId(), u.getId(), backCap); - } - } - } - - // --------------------------------------------------------- - // GETTER : récupérer le graphe résiduel - // --------------------------------------------------------- - public Graph getGraph() { - return graph; - } - - // --------------------------------------------------------- - // OBTENIR LA CAPACITÉ RÉSIDUELLE D'UN ARC - // --------------------------------------------------------- - public int getResidualCapacity(Node u, Node v) { - List edges = graph.getEdges(u, v); - if (edges == null || edges.isEmpty()) return 0; - return edges.get(0).getWeight(); - } - - // --------------------------------------------------------- - // TROUVER LA CAPACITÉ MINIMALE SUR UN CHEMIN - // --------------------------------------------------------- - public int minCapacity(List path) { - int min = Integer.MAX_VALUE; - for (int i = 0; i < path.size() - 1; i++) { - Node u = path.get(i); - Node v = path.get(i + 1); - List edges = graph.getEdges(u, v); - if (edges == null || edges.isEmpty()) { - throw new IllegalStateException("Arc manquant : " + u.getId() + " -> " + v.getId()); - } - int cap = edges.get(0).getWeight(); - if (cap < min) min = cap; - } - return min; - } - - // --------------------------------------------------------- - // MÉTHODE UTILITAIRE : remplace toute arête existante (u → v) - // --------------------------------------------------------- - private void addOrReplaceEdge(int from, int to, int capacity) { - Node u = graph.getNode(from); - Node v = graph.getNode(to); - - List current = new ArrayList<>(graph.getEdges(u, v)); - for (Edge e : current) { - graph.removeEdge(e); - } - - graph.addEdge(from, to, capacity); - } - - // --------------------------------------------------------- - // EXPORTER EN DOT AVEC CHEMIN AUGMENTANT - // --------------------------------------------------------- - public void toDotFile(String filename, List augmentingPath, int step) { - int bottleneck = minCapacity(augmentingPath); - - try (FileWriter writer = new FileWriter(filename)) { - writer.write("digraph residualGraph" + step + " {\n"); - writer.write(" rankdir=\"LR\";\n"); - writer.write(" label=\"(" + step + ") residual graph.\\nAugmenting path: " + - nodeListToString(augmentingPath) + - ".\\nResidual capacity: " + bottleneck + "\";\n"); - - for (Node u : graph.getAllNodes()) { - for (Edge e : graph.getOutEdges(u)) { - Node v = e.getNodeTo(); - int cap = e.getWeight(); - - String color = "black"; - String penwidth = "1"; - String fontcolor = "black"; - - // Vérifie si l'arête est dans le chemin augmentant - for (int i = 0; i < augmentingPath.size() - 1; i++) { - if (u.equals(augmentingPath.get(i)) && v.equals(augmentingPath.get(i + 1))) { - color = "blue"; - penwidth = "3"; - if (cap == bottleneck) fontcolor = "red"; - break; - } - } - - writer.write(" " + u.getId() + " -> " + v.getId() + - " [label=" + cap + - ", len=" + cap + - ", penwidth=" + penwidth + - ", color=\"" + color + "\"" + - ", fontcolor=\"" + fontcolor + "\"];\n"); - } - } - - writer.write("}\n"); - System.out.println("DOT file saved: " + filename); - } catch (IOException e) { - System.err.println("[Erreur] Impossible d'écrire le fichier DOT : " + e.getMessage()); - } - } - - private String nodeListToString(List path) { - StringBuilder sb = new StringBuilder("["); - for (int i = 0; i < path.size(); i++) { - sb.append(path.get(i).getId()); - if (i < path.size() - 1) sb.append(", "); - } - sb.append("]"); - return sb.toString(); - } - - @Override - public String toString() { - return graph.toString(); - } -} diff --git a/src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/AugmentingPathFinder.java new file mode 100644 index 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/BFLongestAugmentingPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..44a61d6dfd10683596763c7b0202f4b32fc67e03 --- /dev/null +++ b/src/main/java/m1graphs2025/MaximumFlow/BFLongestAugmentingPathFinder.java @@ -0,0 +1,121 @@ +package m1graphs2025.MaximumFlow; + +import m1graphs2025.pw2.*; +import java.util.*; + +/** + * Trouve un chemin augmentant en transformant le problème du plus long chemin + * en un plus court chemin en utilisant des poids négatifs (coût = -1 par arête) + * et l'algorithme de Bellman-Ford (limité à n-1 relaxations, donc simple path). + * + * Attention: si le graphe contient des cycles, Bellman-Ford peut détecter des + * cycles négatifs — nous ignorons la détection et utilisons les distances après + * n-1 itérations (correspondant aux plus courts chemins simples). + */ +public class BFLongestAugmentingPathFinder implements AugmentingPathFinder { + + @Override + public List findAugmentingPath(ResidualGraph a3ResidualGraph, int sourceId, int sinkId) { + + + // Approach: + // 1) Run Bellman-Ford with cost = -1 per (positive-capacity) residual edge for exactly n-1 relaxations. + // This yields distances dist[] where dist[v] equals -L for some simple path length L (if reachable). + // 2) Build the equality graph H containing edges (u->v) that satisfy dist[v] == dist[u] - 1. + // Any path in H from source to sink corresponds to a path achieving the target distance. + // 3) Find a simple path in H from source to sink using BFS over partial paths (iterative, + // avoids recursion and prefers shorter expansions). This guarantees no infinite loops. + + + + if (a3ResidualGraph == null) return null; + Graph g = a3ResidualGraph.getGraph(); + if (g == null) return null; + + // Topological DP approach (works only if residual is a DAG) + List nodes = g.getAllNodes(); + int n = nodes.size(); + if (n == 0) return null; + + Map idToIndex = new HashMap<>(); + for (int i = 0; i < nodes.size(); i++) idToIndex.put(nodes.get(i).getId(), i); + if (!idToIndex.containsKey(sourceId) || !idToIndex.containsKey(sinkId)) return null; + int src = idToIndex.get(sourceId); + int tgt = idToIndex.get(sinkId); + + // Build in-degree considering only positive residual edges + int[] indeg = new int[n]; + for (int i = 0; i < n; i++) indeg[i] = 0; + List edges = new ArrayList<>(); + for (Node u : nodes) { + for (Edge e : u.getOutEdges()) { + Integer w = e.getWeight(); + if (w != null && w > 0) { + int vi = idToIndex.get(e.getNodeTo().getId()); + indeg[vi]++; + edges.add(e); + } + } + } + + Deque q = new ArrayDeque<>(); + for (int i = 0; i < n; i++) if (indeg[i] == 0) q.addLast(i); + List topo = new ArrayList<>(); + while (!q.isEmpty()) { + int u = q.removeFirst(); + topo.add(u); + Node nu = nodes.get(u); + for (Edge e : nu.getOutEdges()) { + Integer w = e.getWeight(); + if (w == null || w <= 0) continue; + int vi = idToIndex.get(e.getNodeTo().getId()); + indeg[vi]--; + if (indeg[vi] == 0) q.addLast(vi); + } + } + + if (topo.size() != n) { + // residual graph contains cycles — cannot apply topological DP safely + // fallback to BFS to guarantee a simple augmenting path + AugmentingPathFinder bfs = new BFSPathFinder(); + return bfs.findAugmentingPath(a3ResidualGraph, sourceId, sinkId); + } + + // DP: longest path length from source to each node (in number of edges) + final int NEG = Integer.MIN_VALUE / 4; + int[] dp = new int[n]; + int[] pred = new int[n]; + Arrays.fill(dp, NEG); + Arrays.fill(pred, -1); + dp[src] = 0; + + for (int u : topo) { + if (dp[u] == NEG) continue; // unreachable from source + Node nu = nodes.get(u); + for (Edge e : nu.getOutEdges()) { + Integer w = e.getWeight(); + if (w == null || w <= 0) continue; + int v = idToIndex.get(e.getNodeTo().getId()); + if (dp[u] + 1 > dp[v]) { + dp[v] = dp[u] + 1; + pred[v] = u; + } + } + } + + if (dp[tgt] == NEG) return null; + + // reconstruct path from pred[] + LinkedList path = new LinkedList<>(); + int cur = tgt; + int steps = 0; + while (cur != -1 && steps <= n) { + path.addFirst(nodes.get(cur)); + if (cur == src) break; + cur = pred[cur]; + steps++; + } + if (path.isEmpty() || path.getFirst().getId() != sourceId) return null; + return path; + } +} diff --git a/src/main/java/m1graphs2025/MaximumFlow/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..5da378d50ceb2743d4d2f5f4297fda3bbe96a3b2 --- /dev/null +++ b/src/main/java/m1graphs2025/MaximumFlow/DotExporter.java @@ -0,0 +1,128 @@ +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("\t{rank = min s}\n"); + writer.write("\t{rank = max t}\n"); + writer.write("\tlabel=\"(" + step + ") Flow. Value: " + totalFlow + "\";\n"); + + for (Edge e : g.getAllEdges()) { + Node u = e.getNodeFrom(); + Node v = e.getNodeTo(); + int f = network.getFlow(u, v); + int c = network.getCapacity(u, v); + writer.write("\t" + nodeIdLabel(g, u) + " -> " + nodeIdLabel(g, v) + + " [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"); + writer.write("\t{rank = min s}\n"); + writer.write("\t{rank = max t}\n"); + + // Label : si chemin fourni, on l'affiche sinon on dit "none" + if (path != null && !path.isEmpty()) { + 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" + nodeIdLabel(g, u) + " -> " + nodeIdLabel(g, v) + + " [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(); + } + // Remplace l'id du nœud par 's' ou 't' si nécessaire + private String nodeIdLabel(Graph g, Node n) { + if (n.getId() == g.smallestNodeId()) return "s"; + if (n.getId() == g.largestNodeId()) return "t"; + return String.valueOf(n.getId()); + } + +} diff --git a/src/main/java/m1graphs2025/MaximumFlow/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..b0954e01dcc99d44ae1f8af35c1de94b3e11c69b --- /dev/null +++ b/src/main/java/m1graphs2025/MaximumFlow/FlowNetwork.java @@ -0,0 +1,121 @@ +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; + } + + + + // 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..ae345332fc819655f5eeef996350e46ba7fe7125 --- /dev/null +++ b/src/main/java/m1graphs2025/MaximumFlow/Main.java @@ -0,0 +1,113 @@ +package m1graphs2025.MaximumFlow; + +import m1graphs2025.pw2.*; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Scanner; + +/** + * Main : point d'entrée du programme sans arguments. + * + * 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 += "/"; + clearDirectory(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(); + AugmentingPathFinder finder; + + System.out.println("Selectionner un algorithme de rechereche : \n1- BFS\n2- DFS\n3- Bellman-Ford (recommended)"); + Scanner sc = new Scanner(System.in); + int x = sc.nextInt(); + while (true){ + if (x == 1) { + finder = new BFSPathFinder(); // Edmonds-Karp (recommandé) + break; + } + if (x == 2) { + finder = new DFSPathFinder(); // Edmonds-Karp (recommandé) + break; + } + + if (x == 3) { + + finder = new MaxBottleneckPathFinder(); // plus long chemin (Bellman-Ford) + break; + } + } + + + // 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(); + } + } + + public static void clearDirectory(String folderPath) { + try { + Path dir = Path.of(folderPath); + if (!Files.exists(dir)) { + Files.createDirectories(dir); + return; + } + + // Supprime récursivement tout le contenu + Files.walk(dir) + .sorted((a, b) -> b.compareTo(a)) // supprime d'abord les fichiers, puis les dossiers + .forEach(path -> { + if (!path.equals(dir)) { + try { Files.delete(path); } + catch (Exception e) { + System.err.println("Impossible de supprimer : " + path); + } + } + }); + + } catch (Exception e) { + System.err.println("Erreur lors du nettoyage du dossier : " + folderPath); + e.printStackTrace(); + } + } + +} \ No newline at end of file diff --git a/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java b/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..2d74ec7abab3069ca6ebfab9c05a322cf7363140 --- /dev/null +++ b/src/main/java/m1graphs2025/MaximumFlow/MaxBottleneckPathFinder.java @@ -0,0 +1,78 @@ +package m1graphs2025.MaximumFlow; + +import m1graphs2025.pw2.*; +import java.util.*; + +/** + * MaxBottleneckPathFinder : + * Trouve un chemin augmentant en maximisant la capacité résiduelle minimale + * sur le chemin (Maximum Bottleneck Path). + * + * Cet algo est le plus efficace pour réduire le nombre d'étapes + * lors du calcul du flot maximum. + */ +public class MaxBottleneckPathFinder implements AugmentingPathFinder { + + @Override + public List findAugmentingPath(ResidualGraph residual, int sourceId, int sinkId) { + Graph g = residual.getGraph(); + + Node source = g.getNode(sourceId); + Node sink = g.getNode(sinkId); + if (source == null || sink == null) return null; + + // capacité max rencontrée pour chaque nœud + Map best = new HashMap<>(); + // parent pour reconstruire le chemin + Map parent = new HashMap<>(); + + for (Node n : g.getAllNodes()) { + best.put(n.getId(), 0); + } + best.put(sourceId, Integer.MAX_VALUE); + + // max-heap : on explore toujours le sommet ayant la meilleure capacité + PriorityQueue pq = new PriorityQueue<>( + (a, b) -> Integer.compare(best.get(b.getId()), best.get(a.getId())) + ); + pq.add(source); + + while (!pq.isEmpty()) { + Node u = pq.poll(); + int ucap = best.get(u.getId()); + + if (u.getId() == sinkId) break; + + for (Edge e : g.getOutEdges(u)) { + if (e.getResidualCapacity() > 0) { + Node v = e.getNodeTo(); + int bottleneck = Math.min(ucap, e.getResidualCapacity()); + + if (bottleneck > best.get(v.getId())) { + best.put(v.getId(), bottleneck); + parent.put(v.getId(), u.getId()); + pq.add(v); + } + } + } + } + + if (!parent.containsKey(sinkId)) return null; + + return reconstructPath(g, parent, sourceId, sinkId); + } + + private List reconstructPath(Graph g, Map parent, int s, int t) { + List path = new ArrayList<>(); + int cur = t; + + while (cur != s) { + path.add(g.getNode(cur)); + cur = parent.get(cur); + } + path.add(g.getNode(s)); + + Collections.reverse(path); + return path; + } +} diff --git a/src/main/java/m1graphs2025/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/README.md b/src/main/java/m1graphs2025/MaximumFlow/README.md new file mode 100644 index 0000000000000000000000000000000000000000..0cf1dd48b4f04efe343e3f127c820794b201d221 --- /dev/null +++ b/src/main/java/m1graphs2025/MaximumFlow/README.md @@ -0,0 +1,160 @@ +1. Project Overview + +This project implements several augmenting-path search algorithms to solve the **maximum flow problem** using the **Ford–Fulkerson method**. + +It relies on the graph API developed in PW2, including: + +- DOT file parsing, +- `Graph`, `Node`, `Edge` structures, +- Residual graph construction, +- DOT export for each step. + +The program visualizes each step of the algorithm and allows testing multiple search strategies. + +--- + +## 2. Repository Structure + +/src/m1graphs2025/ +pw2/ → Graph API +MaximumFlow/ +Main.java → Program entry point +FordFulkerson.java → Ford–Fulkerson implementation +ResidualGraph.java → Residual graph structure +FlowNetwork.java → Flow network structure +FlowNetworkValidator.java +AugmentingPathFinder.java +BFSPathFinder.java → Edmonds–Karp +DFSPathFinder.java → Depth-first search +MaxBottleneckPathFinder.java → Longest-path heuristic +DotExporter.java → DOT output generator + +/input/ +example_flow.dot → Example flow network +(additional test graphs) + +/output/ +(generated DOT files) + +/report.pdf → Required report +/README.md → This file + +Toujours afficher les détails + + +--- + +## 3. Compilation + +From the project root: + +### Using `javac` +```bash +javac -d out $(find src -name "*.java") + +Run the program: + +Toujours afficher les détails + +java -cp out m1graphs2025.MaximumFlow.Main + +4. Execution + +Run: + +Toujours afficher les détails + +java -cp out m1graphs2025.MaximumFlow.Main + +You will be asked to select the augmenting path algorithm: + +Toujours afficher les détails + +Select a search algorithm: +1 – BFS +2 – DFS +3 – Bellman-Ford (recommended) + +Then: + + The input is loaded from /input/example_flow.dot + + Each step produces DOT files in /output + + Output files include: + + flowX.gv → current flow with labels f/c + + residualGraphX.gv → residual graph with highlighted path + +5. Visualization + +DOT files are compatible with GraphViz: + +Toujours afficher les détails + +dot -Tpng output/flow1.gv -o flow1.png + +The generated graph shows: + + Source as s and sink as t + + Edges labeled as f/c + + Augmenting paths highlighted in blue + +6. Implemented Algorithms +✔ 1. BFSPathFinder (Edmonds–Karp) + + Complexity: O(V E²) + + Very stable and recommended. + +✔ 2. DFSPathFinder + + May converge quickly or get stuck. + + Behavior depends on edge ordering. + +✔ 3. MaxBottleneckPathFinder (Heuristic) + + Finds an augmenting path maximizing the minimum residual capacity. + + Inspired by "longest path in DAG" dynamic programming. + + Often reduces the number of iterations drastically. + +Algorithm Complexity Notes +BFS (EK) O(VE²) stable, recommended +DFS unbounded risky but fast on some graphs +MaxBottleneck O(VE) per iteration very efficient on structured networks +7. Test Graphs + +You should include several .dot graphs in /input, such as: + + simple flow networks + + networks with cycles + + networks with multiple bottlenecks + + large networks (10+ nodes) + + one-path-only networks + +8. Notes + + /output is automatically cleared before each execution. + + Source and sink are printed as s and t. + + DOT outputs follow the formatting expected by the assignment. + +9. Author + +Project developed by Djemaoui Ahmed, +Master 1 Computer Science — Université de Franche-Comté. +""" + +path = Path("/mnt/data/README.md") +path.write_text(content, encoding="utf-8") 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/Edge.java b/src/main/java/m1graphs2025/pw2/Edge.java similarity index 96% rename from src/main/java/m1graphs2025/Edge.java rename to src/main/java/m1graphs2025/pw2/Edge.java index 378dcdf65882dc23b911a45662ae49d184ad7510..df9f2acb3ebce89339045920212bc7b4744d05db 100644 --- a/src/main/java/m1graphs2025/Edge.java +++ b/src/main/java/m1graphs2025/pw2/Edge.java @@ -1,4 +1,4 @@ -package m1graphs2025; +package m1graphs2025.pw2; import java.util.*; @@ -452,4 +452,13 @@ public class Edge implements Comparable { return from.getId() + " -> " + to.getId(); } + // ================================================================================================================= + // ============================================Flow Max METH==================================================== + + public int getResidualCapacity() { + if (this.weight == null) { + return Integer.MAX_VALUE; // Capacité infinie pour les arêtes non pondérées + } + return this.weight; + } } \ No newline at end of file diff --git a/src/main/java/m1graphs2025/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 99% rename from src/main/java/m1graphs2025/Graph.java rename to src/main/java/m1graphs2025/pw2/Graph.java index e1917aab6172b46ed4b5eacb003bab80bebc22d4..5bec8280667e8f5f24255859c8b44b32a4bea077 100644 --- a/src/main/java/m1graphs2025/Graph.java +++ b/src/main/java/m1graphs2025/pw2/Graph.java @@ -1,4 +1,4 @@ -package m1graphs2025; +package m1graphs2025.pw2; import java.io.BufferedReader; import java.io.FileReader; @@ -8,10 +8,8 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import m1graphs2025.NodeVisitInfo.NodeColour; +import m1graphs2025.pw2.NodeVisitInfo.NodeColour; public class Graph { @@ -1689,7 +1687,7 @@ public class Graph { * * @return liste des nœuds triés par ID */ - private List sortNodes() { + public List sortNodes() { return adjEdList.keySet().stream() .sorted(Comparator.comparingInt(Node::getId)) .toList(); diff --git a/src/main/java/m1graphs2025/pw2/LongestPathFinder.java b/src/main/java/m1graphs2025/pw2/LongestPathFinder.java new file mode 100644 index 0000000000000000000000000000000000000000..6a25590847e0939d30d6c396935a9996e487f2d6 --- /dev/null +++ b/src/main/java/m1graphs2025/pw2/LongestPathFinder.java @@ -0,0 +1,74 @@ +package m1graphs2025.pw2; + +import java.util.*; + +/** + * Utility pour rechercher un chemin le plus long dans un graphe orienté acyclique (DAG). + * - Si le graphe contient un cycle, la méthode retourne null. + * - Les arêtes pondérées utilisent `Edge.getWeight()` si non-null, sinon on considère un poids = 1. + */ +public class LongestPathFinder { + + public static List findLongestPath(Graph g, int sourceId, int sinkId) { + if (g == null) return null; + Node source = g.getNode(sourceId); + Node sink = g.getNode(sinkId); + if (source == null || sink == null) return null; + + // Kahn pour ordre topologique + Map inDeg = new HashMap<>(); + List all = g.getAllNodes(); + for (Node n : all) inDeg.put(n, g.inDegree(n)); + + Deque q = new ArrayDeque<>(); + for (Map.Entry e : inDeg.entrySet()) if (e.getValue() == 0) q.add(e.getKey()); + + List topo = new ArrayList<>(); + while (!q.isEmpty()) { + Node u = q.removeFirst(); + topo.add(u); + for (Edge out : u.getOutEdges()) { + Node v = out.getNodeTo(); + inDeg.put(v, inDeg.get(v) - 1); + if (inDeg.get(v) == 0) q.addLast(v); + } + } + + if (topo.size() != all.size()) { + // graphe non-DAG + return null; + } + + final int NEG = Integer.MIN_VALUE / 4; + Map dist = new HashMap<>(); + Map pred = new HashMap<>(); + for (Node n : all) dist.put(n, NEG); + dist.put(source, 0); + + for (Node u : topo) { + int du = dist.getOrDefault(u, NEG); + if (du == NEG) continue; + for (Edge e : u.getOutEdges()) { + Node v = e.getNodeTo(); + int w = (e.getWeight() != null) ? e.getWeight() : 1; + if (du + w > dist.getOrDefault(v, NEG)) { + dist.put(v, du + w); + pred.put(v, u); + } + } + } + + if (dist.getOrDefault(sink, NEG) == NEG) return null; + + LinkedList path = new LinkedList<>(); + Node cur = sink; + while (cur != null) { + path.addFirst(cur); + if (cur.equals(source)) break; + cur = pred.get(cur); + } + + if (path.isEmpty() || !path.getFirst().equals(source)) return null; + return path; + } +} diff --git a/src/main/java/m1graphs2025/Node.java b/src/main/java/m1graphs2025/pw2/Node.java similarity index 99% rename from src/main/java/m1graphs2025/Node.java rename to src/main/java/m1graphs2025/pw2/Node.java index 1f7e600efcf3be94a0651c3e3ba958642cd6df0f..237d53ae91deb8e0d64a52e724f1396ece054396 100644 --- a/src/main/java/m1graphs2025/Node.java +++ b/src/main/java/m1graphs2025/pw2/Node.java @@ -1,4 +1,4 @@ -package m1graphs2025; +package m1graphs2025.pw2; import java.util.*; diff --git a/src/main/java/m1graphs2025/NodeVisitInfo.java b/src/main/java/m1graphs2025/pw2/NodeVisitInfo.java similarity index 99% rename from src/main/java/m1graphs2025/NodeVisitInfo.java rename to src/main/java/m1graphs2025/pw2/NodeVisitInfo.java index e8d446975db84409cabda89b51be61d1e1fa2147..98dd7ca731edb3fb9cd6fd24bac3fd61c12ca8ac 100644 --- a/src/main/java/m1graphs2025/NodeVisitInfo.java +++ b/src/main/java/m1graphs2025/pw2/NodeVisitInfo.java @@ -1,4 +1,4 @@ -package m1graphs2025; +package m1graphs2025.pw2; /** * Représente les informations de visite d’un nœud lors d’un parcours en profondeur (DFS). diff --git a/src/main/java/m1graphs2025/UndirectedGraph.java b/src/main/java/m1graphs2025/pw2/UndirectedGraph.java similarity index 99% rename from src/main/java/m1graphs2025/UndirectedGraph.java rename to src/main/java/m1graphs2025/pw2/UndirectedGraph.java index 493f310d742a1ca4a8538b2869e544e2053ea15e..585dc06f15499be66971e07f89f6821abcac166e 100644 --- a/src/main/java/m1graphs2025/UndirectedGraph.java +++ b/src/main/java/m1graphs2025/pw2/UndirectedGraph.java @@ -1,11 +1,8 @@ -package m1graphs2025; +package m1graphs2025.pw2; import java.io.BufferedReader; import java.io.FileReader; import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; diff --git a/src/test/java/m1graphs2025/TestGraphPart1.java b/src/test/java/m1graphs2025/TestGraphPart1.java index b2d8bb0389e0a9f7ea5a00e3d6869b8266079867..f4e3152e119a88592b30faa51c8a719832fba9f0 100644 --- a/src/test/java/m1graphs2025/TestGraphPart1.java +++ b/src/test/java/m1graphs2025/TestGraphPart1.java @@ -1,5 +1,9 @@ package m1graphs2025; +import m1graphs2025.pw2.Edge; +import m1graphs2025.pw2.Graph; +import m1graphs2025.pw2.Node; + import java.util.Collections; import java.util.List; diff --git a/src/test/java/m1graphs2025/TestGraphPart2.java b/src/test/java/m1graphs2025/TestGraphPart2.java index d764977b1bc67be80b6b4271724548d99b5f5647..a22e57f4fbe336c5adcc77936605c89904f69658 100644 --- a/src/test/java/m1graphs2025/TestGraphPart2.java +++ b/src/test/java/m1graphs2025/TestGraphPart2.java @@ -1,6 +1,8 @@ package m1graphs2025; -import java.util.Arrays; +import m1graphs2025.pw2.Graph; +import m1graphs2025.pw2.Node; + import java.util.Collections; import java.util.List; diff --git a/src/test/java/m1graphs2025/TestGraphPart3.java b/src/test/java/m1graphs2025/TestGraphPart3.java index 953be9b0d34fa16be25490cd1d566ee6389595a4..35ca94f8d25e28b0bcd3f253b0b80c4031235bea 100644 --- a/src/test/java/m1graphs2025/TestGraphPart3.java +++ b/src/test/java/m1graphs2025/TestGraphPart3.java @@ -1,8 +1,7 @@ package m1graphs2025; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; +import m1graphs2025.pw2.Edge; +import m1graphs2025.pw2.Graph; public class TestGraphPart3 { diff --git a/src/test/java/m1graphs2025/TestGraphPart4.java b/src/test/java/m1graphs2025/TestGraphPart4.java index 720043517d45763537352e59d2b66a205a9fe79c..ae5b315e2c5830427dc5f923a8d3b3f57e22ea9f 100644 --- a/src/test/java/m1graphs2025/TestGraphPart4.java +++ b/src/test/java/m1graphs2025/TestGraphPart4.java @@ -1,5 +1,8 @@ package m1graphs2025; +import m1graphs2025.pw2.Node; +import m1graphs2025.pw2.UndirectedGraph; + import java.util.Arrays; import java.util.Collections; import java.util.List; diff --git a/src/test/java/m1graphs2025/TestGraphPart5.java b/src/test/java/m1graphs2025/TestGraphPart5.java index d0fa911a59c1aa7a82ef234ee4d89aefaf60580f..be9986f232759b3e549a410ae5e59d0bfd48b308 100644 --- a/src/test/java/m1graphs2025/TestGraphPart5.java +++ b/src/test/java/m1graphs2025/TestGraphPart5.java @@ -1,6 +1,9 @@ package m1graphs2025; -import java.util.Arrays; +import m1graphs2025.pw2.Edge; +import m1graphs2025.pw2.Node; +import m1graphs2025.pw2.UndirectedGraph; + import java.util.Collections; import java.util.List; diff --git a/src/test/java/m1graphs2025/TestGraphPart6.java b/src/test/java/m1graphs2025/TestGraphPart6.java index 0cbe7d236e84555fae7799b19a241f72723ccc02..a19378f7e1c5c62ae1ca5f94a32d9c0285b8123d 100644 --- a/src/test/java/m1graphs2025/TestGraphPart6.java +++ b/src/test/java/m1graphs2025/TestGraphPart6.java @@ -1,7 +1,8 @@ package m1graphs2025; +import m1graphs2025.pw2.*; + import java.util.HashMap; -import java.util.List; import java.util.Map; public class TestGraphPart6 { diff --git a/src/test/java/m1graphs2025/TestsGraphPW2.java b/src/test/java/m1graphs2025/TestsGraphPW2.java index 84ced11fc404ee0503ce0434c927c5b5288f19da..da1d180a94700c387304115185e6521e80b919aa 100644 --- a/src/test/java/m1graphs2025/TestsGraphPW2.java +++ b/src/test/java/m1graphs2025/TestsGraphPW2.java @@ -1,5 +1,10 @@ package m1graphs2025; +import m1graphs2025.pw2.Edge; +import m1graphs2025.pw2.Graph; +import m1graphs2025.pw2.Node; +import m1graphs2025.pw2.UndirectedGraph; + import java.util.Arrays; import java.util.Collections; import java.util.List;