From b1074ba29bcacd99a1c34cbc850ba4126519d9d7 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Sun, 12 Oct 2025 22:55:19 +0530 Subject: [PATCH 1/4] Add Dinics max flow algorithm with tests and index update --- DIRECTORY.md | 1 + .../java/com/thealgorithms/graph/Dinic.java | 112 ++++++++++++++++++ .../com/thealgorithms/graph/DinicTest.java | 88 ++++++++++++++ 3 files changed, 201 insertions(+) create mode 100644 src/main/java/com/thealgorithms/graph/Dinic.java create mode 100644 src/test/java/com/thealgorithms/graph/DinicTest.java diff --git a/DIRECTORY.md b/DIRECTORY.md index b311b10fa177..b3ccb80a9bd0 100644 --- a/DIRECTORY.md +++ b/DIRECTORY.md @@ -352,6 +352,7 @@ - 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java) - 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java) - 📄 [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java) + - 📄 [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java) - 📁 **greedyalgorithms** - 📄 [ActivitySelection](src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java) - 📄 [BandwidthAllocation](src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java) diff --git a/src/main/java/com/thealgorithms/graph/Dinic.java b/src/main/java/com/thealgorithms/graph/Dinic.java new file mode 100644 index 000000000000..084420223555 --- /dev/null +++ b/src/main/java/com/thealgorithms/graph/Dinic.java @@ -0,0 +1,112 @@ +package com.thealgorithms.graph; + +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.Queue; + +/** + * Dinic's algorithm for computing maximum flow in a directed graph. + * + *

Time complexity: O(E * V^2) in the worst case, but typically faster in practice + * and near O(E * sqrt(V)) for unit networks.

+ * + *

The graph is represented using a capacity matrix where capacity[u][v] is the + * capacity of the directed edge u -> v. Capacities must be non-negative. + * The algorithm builds level graphs using BFS and finds blocking flows using DFS + * with current-edge optimization.

+ * + *

This implementation mirrors the API and validation style of + * {@link EdmondsKarp#maxFlow(int[][], int, int)} for consistency.

+ */ +public final class Dinic { + + private Dinic() {} + + /** + * Computes the maximum flow from source to sink using Dinic's algorithm. + * + * @param capacity square capacity matrix (n x n); entries must be >= 0 + * @param source source vertex index in [0, n) + * @param sink sink vertex index in [0, n) + * @return the maximum flow value + * @throws IllegalArgumentException if the input matrix is null/non-square/has negatives or indices invalid + */ + public static int maxFlow(int[][] capacity, int source, int sink) { + if (capacity == null || capacity.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + final int n = capacity.length; + for (int i = 0; i < n; i++) { + if (capacity[i] == null || capacity[i].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int j = 0; j < n; j++) { + if (capacity[i][j] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); + } + } + } + if (source < 0 || sink < 0 || source >= n || sink >= n) { + throw new IllegalArgumentException("Source and sink must be valid vertex indices"); + } + if (source == sink) { + return 0; + } + + // residual capacities + int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } + + int[] level = new int[n]; + int flow = 0; + while (bfsBuildLevelGraph(residual, source, sink, level)) { + int[] next = new int[n]; // current-edge optimization + int pushed; + do { + pushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE); + flow += pushed; + } while (pushed > 0); + } + return flow; + } + + private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) { + Arrays.fill(level, -1); + level[source] = 0; + Queue q = new ArrayDeque<>(); + q.add(source); + while (!q.isEmpty()) { + int u = q.poll(); + for (int v = 0; v < residual.length; v++) { + if (residual[u][v] > 0 && level[v] == -1) { + level[v] = level[u] + 1; + if (v == sink) { + return true; + } + q.add(v); + } + } + } + return level[sink] != -1; + } + + private static int dfsBlocking(int[][] residual, int[] level, int[] next, int u, int sink, int f) { + if (u == sink) { + return f; + } + final int n = residual.length; + for (int v = next[u]; v < n; v++, next[u] = v) { + if (residual[u][v] <= 0) continue; + if (level[v] != level[u] + 1) continue; + int pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v])); + if (pushed > 0) { + residual[u][v] -= pushed; + residual[v][u] += pushed; + return pushed; + } + } + return 0; + } +} diff --git a/src/test/java/com/thealgorithms/graph/DinicTest.java b/src/test/java/com/thealgorithms/graph/DinicTest.java new file mode 100644 index 000000000000..4ed410199be9 --- /dev/null +++ b/src/test/java/com/thealgorithms/graph/DinicTest.java @@ -0,0 +1,88 @@ +package com.thealgorithms.graph; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DinicTest { + + @Test + @DisplayName("Classic CLRS network yields max flow 23 (Dinic)") + void clrsExample() { + int[][] capacity = { + {0, 16, 13, 0, 0, 0}, + {0, 0, 10, 12, 0, 0}, + {0, 4, 0, 0, 14, 0}, + {0, 0, 9, 0, 0, 20}, + {0, 0, 0, 7, 0, 4}, + {0, 0, 0, 0, 0, 0} + }; + int maxFlow = Dinic.maxFlow(capacity, 0, 5); + assertEquals(23, maxFlow); + } + + @Test + @DisplayName("Disconnected network has zero flow (Dinic)") + void disconnectedGraph() { + int[][] capacity = { + {0, 0, 0}, + {0, 0, 0}, + {0, 0, 0} + }; + int maxFlow = Dinic.maxFlow(capacity, 0, 2); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Source equals sink returns zero (Dinic)") + void sourceEqualsSink() { + int[][] capacity = { + {0, 5}, + {0, 0} + }; + int maxFlow = Dinic.maxFlow(capacity, 0, 0); + assertEquals(0, maxFlow); + } + + @Test + @DisplayName("Invalid matrix throws exception (Dinic)") + void invalidMatrix() { + int[][] capacity = { + {0, 1}, + {1} + }; + assertThrows(IllegalArgumentException.class, () -> Dinic.maxFlow(capacity, 0, 1)); + } + + @Test + @DisplayName("Dinic matches Edmonds-Karp on random small graphs") + void parityWithEdmondsKarp() { + java.util.Random rnd = new java.util.Random(42); + for (int n = 3; n <= 7; n++) { + for (int it = 0; it < 25; it++) { + int[][] cap = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && rnd.nextDouble() < 0.35) { + cap[i][j] = rnd.nextInt(10); // capacities 0..9 + } + } + } + int s = 0, t = n - 1; + int f1 = Dinic.maxFlow(copyMatrix(cap), s, t); + int f2 = EdmondsKarp.maxFlow(cap, s, t); + assertEquals(f2, f1); + } + } + } + + private static int[][] copyMatrix(int[][] a) { + int[][] b = new int[a.length][a.length]; + for (int i = 0; i < a.length; i++) { + b[i] = java.util.Arrays.copyOf(a[i], a[i].length); + } + return b; + } +} From ef75cc45ca50ace48483605e0a5fede121dc975b Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Sun, 12 Oct 2025 23:21:59 +0530 Subject: [PATCH 2/4] Docs: add Dinic reference link and apply clang-format --- .../java/com/thealgorithms/graph/Dinic.java | 165 +++++++++--------- .../com/thealgorithms/graph/DinicTest.java | 121 ++++++------- 2 files changed, 137 insertions(+), 149 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/Dinic.java b/src/main/java/com/thealgorithms/graph/Dinic.java index 084420223555..7f8c343843c7 100644 --- a/src/main/java/com/thealgorithms/graph/Dinic.java +++ b/src/main/java/com/thealgorithms/graph/Dinic.java @@ -17,96 +17,101 @@ * *

This implementation mirrors the API and validation style of * {@link EdmondsKarp#maxFlow(int[][], int, int)} for consistency.

+ * + * @see Wikipedia: Dinic's algorithm */ public final class Dinic { + private Dinic() {} - private Dinic() {} - - /** - * Computes the maximum flow from source to sink using Dinic's algorithm. - * - * @param capacity square capacity matrix (n x n); entries must be >= 0 - * @param source source vertex index in [0, n) - * @param sink sink vertex index in [0, n) - * @return the maximum flow value - * @throws IllegalArgumentException if the input matrix is null/non-square/has negatives or indices invalid - */ - public static int maxFlow(int[][] capacity, int source, int sink) { - if (capacity == null || capacity.length == 0) { - throw new IllegalArgumentException("Capacity matrix must not be null or empty"); - } - final int n = capacity.length; - for (int i = 0; i < n; i++) { - if (capacity[i] == null || capacity[i].length != n) { - throw new IllegalArgumentException("Capacity matrix must be square"); - } - for (int j = 0; j < n; j++) { - if (capacity[i][j] < 0) { - throw new IllegalArgumentException("Capacities must be non-negative"); - } - } - } - if (source < 0 || sink < 0 || source >= n || sink >= n) { - throw new IllegalArgumentException("Source and sink must be valid vertex indices"); - } - if (source == sink) { - return 0; + /** + * Computes the maximum flow from source to sink using Dinic's algorithm. + * + * @param capacity square capacity matrix (n x n); entries must be >= 0 + * @param source source vertex index in [0, n) + * @param sink sink vertex index in [0, n) + * @return the maximum flow value + * @throws IllegalArgumentException if the input matrix is null/non-square/has negatives or + * indices invalid + */ + public static int maxFlow(int[][] capacity, int source, int sink) { + if (capacity == null || capacity.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + final int n = capacity.length; + for (int i = 0; i < n; i++) { + if (capacity[i] == null || capacity[i].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int j = 0; j < n; j++) { + if (capacity[i][j] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); } + } + } + if (source < 0 || sink < 0 || source >= n || sink >= n) { + throw new IllegalArgumentException("Source and sink must be valid vertex indices"); + } + if (source == sink) { + return 0; + } - // residual capacities - int[][] residual = new int[n][n]; - for (int i = 0; i < n; i++) { - residual[i] = Arrays.copyOf(capacity[i], n); - } + // residual capacities + int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } - int[] level = new int[n]; - int flow = 0; - while (bfsBuildLevelGraph(residual, source, sink, level)) { - int[] next = new int[n]; // current-edge optimization - int pushed; - do { - pushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE); - flow += pushed; - } while (pushed > 0); - } - return flow; + int[] level = new int[n]; + int flow = 0; + while (bfsBuildLevelGraph(residual, source, sink, level)) { + int[] next = new int[n]; // current-edge optimization + int pushed; + do { + pushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE); + flow += pushed; + } while (pushed > 0); } + return flow; + } - private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) { - Arrays.fill(level, -1); - level[source] = 0; - Queue q = new ArrayDeque<>(); - q.add(source); - while (!q.isEmpty()) { - int u = q.poll(); - for (int v = 0; v < residual.length; v++) { - if (residual[u][v] > 0 && level[v] == -1) { - level[v] = level[u] + 1; - if (v == sink) { - return true; - } - q.add(v); - } - } + private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) { + Arrays.fill(level, -1); + level[source] = 0; + Queue q = new ArrayDeque<>(); + q.add(source); + while (!q.isEmpty()) { + int u = q.poll(); + for (int v = 0; v < residual.length; v++) { + if (residual[u][v] > 0 && level[v] == -1) { + level[v] = level[u] + 1; + if (v == sink) { + return true; + } + q.add(v); } - return level[sink] != -1; + } } + return level[sink] != -1; + } - private static int dfsBlocking(int[][] residual, int[] level, int[] next, int u, int sink, int f) { - if (u == sink) { - return f; - } - final int n = residual.length; - for (int v = next[u]; v < n; v++, next[u] = v) { - if (residual[u][v] <= 0) continue; - if (level[v] != level[u] + 1) continue; - int pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v])); - if (pushed > 0) { - residual[u][v] -= pushed; - residual[v][u] += pushed; - return pushed; - } - } - return 0; + private static int dfsBlocking( + int[][] residual, int[] level, int[] next, int u, int sink, int f) { + if (u == sink) { + return f; + } + final int n = residual.length; + for (int v = next[u]; v < n; v++, next[u] = v) { + if (residual[u][v] <= 0) + continue; + if (level[v] != level[u] + 1) + continue; + int pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v])); + if (pushed > 0) { + residual[u][v] -= pushed; + residual[v][u] += pushed; + return pushed; + } } + return 0; + } } diff --git a/src/test/java/com/thealgorithms/graph/DinicTest.java b/src/test/java/com/thealgorithms/graph/DinicTest.java index 4ed410199be9..4a5389876c9b 100644 --- a/src/test/java/com/thealgorithms/graph/DinicTest.java +++ b/src/test/java/com/thealgorithms/graph/DinicTest.java @@ -7,82 +7,65 @@ import org.junit.jupiter.api.Test; class DinicTest { + @Test + @DisplayName("Classic CLRS network yields max flow 23 (Dinic)") + void clrsExample() { + int[][] capacity = {{0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, + {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 5); + assertEquals(23, maxFlow); + } - @Test - @DisplayName("Classic CLRS network yields max flow 23 (Dinic)") - void clrsExample() { - int[][] capacity = { - {0, 16, 13, 0, 0, 0}, - {0, 0, 10, 12, 0, 0}, - {0, 4, 0, 0, 14, 0}, - {0, 0, 9, 0, 0, 20}, - {0, 0, 0, 7, 0, 4}, - {0, 0, 0, 0, 0, 0} - }; - int maxFlow = Dinic.maxFlow(capacity, 0, 5); - assertEquals(23, maxFlow); - } - - @Test - @DisplayName("Disconnected network has zero flow (Dinic)") - void disconnectedGraph() { - int[][] capacity = { - {0, 0, 0}, - {0, 0, 0}, - {0, 0, 0} - }; - int maxFlow = Dinic.maxFlow(capacity, 0, 2); - assertEquals(0, maxFlow); - } + @Test + @DisplayName("Disconnected network has zero flow (Dinic)") + void disconnectedGraph() { + int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 2); + assertEquals(0, maxFlow); + } - @Test - @DisplayName("Source equals sink returns zero (Dinic)") - void sourceEqualsSink() { - int[][] capacity = { - {0, 5}, - {0, 0} - }; - int maxFlow = Dinic.maxFlow(capacity, 0, 0); - assertEquals(0, maxFlow); - } + @Test + @DisplayName("Source equals sink returns zero (Dinic)") + void sourceEqualsSink() { + int[][] capacity = {{0, 5}, {0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 0); + assertEquals(0, maxFlow); + } - @Test - @DisplayName("Invalid matrix throws exception (Dinic)") - void invalidMatrix() { - int[][] capacity = { - {0, 1}, - {1} - }; - assertThrows(IllegalArgumentException.class, () -> Dinic.maxFlow(capacity, 0, 1)); - } + @Test + @DisplayName("Invalid matrix throws exception (Dinic)") + void invalidMatrix() { + int[][] capacity = {{0, 1}, {1}}; + assertThrows(IllegalArgumentException.class, () -> Dinic.maxFlow(capacity, 0, 1)); + } - @Test - @DisplayName("Dinic matches Edmonds-Karp on random small graphs") - void parityWithEdmondsKarp() { - java.util.Random rnd = new java.util.Random(42); - for (int n = 3; n <= 7; n++) { - for (int it = 0; it < 25; it++) { - int[][] cap = new int[n][n]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (i != j && rnd.nextDouble() < 0.35) { - cap[i][j] = rnd.nextInt(10); // capacities 0..9 - } - } - } - int s = 0, t = n - 1; - int f1 = Dinic.maxFlow(copyMatrix(cap), s, t); - int f2 = EdmondsKarp.maxFlow(cap, s, t); - assertEquals(f2, f1); + @Test + @DisplayName("Dinic matches Edmonds-Karp on random small graphs") + void parityWithEdmondsKarp() { + java.util.Random rnd = new java.util.Random(42); + for (int n = 3; n <= 7; n++) { + for (int it = 0; it < 25; it++) { + int[][] cap = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && rnd.nextDouble() < 0.35) { + cap[i][j] = rnd.nextInt(10); // capacities 0..9 } + } } + int s = 0, t = n - 1; + int f1 = Dinic.maxFlow(copyMatrix(cap), s, t); + int f2 = EdmondsKarp.maxFlow(cap, s, t); + assertEquals(f2, f1); + } } + } - private static int[][] copyMatrix(int[][] a) { - int[][] b = new int[a.length][a.length]; - for (int i = 0; i < a.length; i++) { - b[i] = java.util.Arrays.copyOf(a[i], a[i].length); - } - return b; + private static int[][] copyMatrix(int[][] a) { + int[][] b = new int[a.length][a.length]; + for (int i = 0; i < a.length; i++) { + b[i] = java.util.Arrays.copyOf(a[i], a[i].length); } + return b; + } } From 839e34bbe51266b33a7baa48bcbaf90a47d7cbf1 Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Sun, 12 Oct 2025 23:40:15 +0530 Subject: [PATCH 3/4] Fix: Checkstyle violations in Dinic and tests --- .../java/com/thealgorithms/graph/Dinic.java | 28 ++++++++++--------- .../com/thealgorithms/graph/DinicTest.java | 3 +- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/Dinic.java b/src/main/java/com/thealgorithms/graph/Dinic.java index 7f8c343843c7..476dd210b888 100644 --- a/src/main/java/com/thealgorithms/graph/Dinic.java +++ b/src/main/java/com/thealgorithms/graph/Dinic.java @@ -21,7 +21,7 @@ * @see Wikipedia: Dinic's algorithm */ public final class Dinic { - private Dinic() {} + private Dinic() { } /** * Computes the maximum flow from source to sink using Dinic's algorithm. @@ -101,17 +101,19 @@ private static int dfsBlocking( } final int n = residual.length; for (int v = next[u]; v < n; v++, next[u] = v) { - if (residual[u][v] <= 0) - continue; - if (level[v] != level[u] + 1) - continue; - int pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v])); - if (pushed > 0) { - residual[u][v] -= pushed; - residual[v][u] += pushed; - return pushed; - } - } - return 0; + if (residual[u][v] <= 0) { + continue; + } + if (level[v] != level[u] + 1) { + continue; + } + int pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v])); + if (pushed > 0) { + residual[u][v] -= pushed; + residual[v][u] += pushed; + return pushed; + } + } +return 0; } } diff --git a/src/test/java/com/thealgorithms/graph/DinicTest.java b/src/test/java/com/thealgorithms/graph/DinicTest.java index 4a5389876c9b..2089c4632109 100644 --- a/src/test/java/com/thealgorithms/graph/DinicTest.java +++ b/src/test/java/com/thealgorithms/graph/DinicTest.java @@ -53,7 +53,8 @@ void parityWithEdmondsKarp() { } } } - int s = 0, t = n - 1; + int s = 0; + int t = n - 1; int f1 = Dinic.maxFlow(copyMatrix(cap), s, t); int f2 = EdmondsKarp.maxFlow(cap, s, t); assertEquals(f2, f1); From ff313800c633ad850e21563f6ccee59b1709127e Mon Sep 17 00:00:00 2001 From: Sameer Prajapati Date: Mon, 13 Oct 2025 00:01:09 +0530 Subject: [PATCH 4/4] style: apply clang-format to Dinic and tests --- .../java/com/thealgorithms/graph/Dinic.java | 146 +++++++++--------- .../com/thealgorithms/graph/DinicTest.java | 105 +++++++------ 2 files changed, 125 insertions(+), 126 deletions(-) diff --git a/src/main/java/com/thealgorithms/graph/Dinic.java b/src/main/java/com/thealgorithms/graph/Dinic.java index 476dd210b888..c45bd62ddfbb 100644 --- a/src/main/java/com/thealgorithms/graph/Dinic.java +++ b/src/main/java/com/thealgorithms/graph/Dinic.java @@ -21,86 +21,86 @@ * @see Wikipedia: Dinic's algorithm */ public final class Dinic { - private Dinic() { } - - /** - * Computes the maximum flow from source to sink using Dinic's algorithm. - * - * @param capacity square capacity matrix (n x n); entries must be >= 0 - * @param source source vertex index in [0, n) - * @param sink sink vertex index in [0, n) - * @return the maximum flow value - * @throws IllegalArgumentException if the input matrix is null/non-square/has negatives or - * indices invalid - */ - public static int maxFlow(int[][] capacity, int source, int sink) { - if (capacity == null || capacity.length == 0) { - throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + private Dinic() { } - final int n = capacity.length; - for (int i = 0; i < n; i++) { - if (capacity[i] == null || capacity[i].length != n) { - throw new IllegalArgumentException("Capacity matrix must be square"); - } - for (int j = 0; j < n; j++) { - if (capacity[i][j] < 0) { - throw new IllegalArgumentException("Capacities must be non-negative"); + + /** + * Computes the maximum flow from source to sink using Dinic's algorithm. + * + * @param capacity square capacity matrix (n x n); entries must be >= 0 + * @param source source vertex index in [0, n) + * @param sink sink vertex index in [0, n) + * @return the maximum flow value + * @throws IllegalArgumentException if the input matrix is null/non-square/has negatives or + * indices invalid + */ + public static int maxFlow(int[][] capacity, int source, int sink) { + if (capacity == null || capacity.length == 0) { + throw new IllegalArgumentException("Capacity matrix must not be null or empty"); + } + final int n = capacity.length; + for (int i = 0; i < n; i++) { + if (capacity[i] == null || capacity[i].length != n) { + throw new IllegalArgumentException("Capacity matrix must be square"); + } + for (int j = 0; j < n; j++) { + if (capacity[i][j] < 0) { + throw new IllegalArgumentException("Capacities must be non-negative"); + } + } + } + if (source < 0 || sink < 0 || source >= n || sink >= n) { + throw new IllegalArgumentException("Source and sink must be valid vertex indices"); + } + if (source == sink) { + return 0; } - } - } - if (source < 0 || sink < 0 || source >= n || sink >= n) { - throw new IllegalArgumentException("Source and sink must be valid vertex indices"); - } - if (source == sink) { - return 0; - } - // residual capacities - int[][] residual = new int[n][n]; - for (int i = 0; i < n; i++) { - residual[i] = Arrays.copyOf(capacity[i], n); - } + // residual capacities + int[][] residual = new int[n][n]; + for (int i = 0; i < n; i++) { + residual[i] = Arrays.copyOf(capacity[i], n); + } - int[] level = new int[n]; - int flow = 0; - while (bfsBuildLevelGraph(residual, source, sink, level)) { - int[] next = new int[n]; // current-edge optimization - int pushed; - do { - pushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE); - flow += pushed; - } while (pushed > 0); + int[] level = new int[n]; + int flow = 0; + while (bfsBuildLevelGraph(residual, source, sink, level)) { + int[] next = new int[n]; // current-edge optimization + int pushed; + do { + pushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE); + flow += pushed; + } while (pushed > 0); + } + return flow; } - return flow; - } - private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) { - Arrays.fill(level, -1); - level[source] = 0; - Queue q = new ArrayDeque<>(); - q.add(source); - while (!q.isEmpty()) { - int u = q.poll(); - for (int v = 0; v < residual.length; v++) { - if (residual[u][v] > 0 && level[v] == -1) { - level[v] = level[u] + 1; - if (v == sink) { - return true; - } - q.add(v); + private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) { + Arrays.fill(level, -1); + level[source] = 0; + Queue q = new ArrayDeque<>(); + q.add(source); + while (!q.isEmpty()) { + int u = q.poll(); + for (int v = 0; v < residual.length; v++) { + if (residual[u][v] > 0 && level[v] == -1) { + level[v] = level[u] + 1; + if (v == sink) { + return true; + } + q.add(v); + } + } } - } + return level[sink] != -1; } - return level[sink] != -1; - } - private static int dfsBlocking( - int[][] residual, int[] level, int[] next, int u, int sink, int f) { - if (u == sink) { - return f; - } - final int n = residual.length; - for (int v = next[u]; v < n; v++, next[u] = v) { + private static int dfsBlocking(int[][] residual, int[] level, int[] next, int u, int sink, int f) { + if (u == sink) { + return f; + } + final int n = residual.length; + for (int v = next[u]; v < n; v++, next[u] = v) { if (residual[u][v] <= 0) { continue; } @@ -114,6 +114,6 @@ private static int dfsBlocking( return pushed; } } -return 0; - } + return 0; + } } diff --git a/src/test/java/com/thealgorithms/graph/DinicTest.java b/src/test/java/com/thealgorithms/graph/DinicTest.java index 2089c4632109..912f787519fa 100644 --- a/src/test/java/com/thealgorithms/graph/DinicTest.java +++ b/src/test/java/com/thealgorithms/graph/DinicTest.java @@ -7,66 +7,65 @@ import org.junit.jupiter.api.Test; class DinicTest { - @Test - @DisplayName("Classic CLRS network yields max flow 23 (Dinic)") - void clrsExample() { - int[][] capacity = {{0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, - {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0}}; - int maxFlow = Dinic.maxFlow(capacity, 0, 5); - assertEquals(23, maxFlow); - } + @Test + @DisplayName("Classic CLRS network yields max flow 23 (Dinic)") + void clrsExample() { + int[][] capacity = {{0, 16, 13, 0, 0, 0}, {0, 0, 10, 12, 0, 0}, {0, 4, 0, 0, 14, 0}, {0, 0, 9, 0, 0, 20}, {0, 0, 0, 7, 0, 4}, {0, 0, 0, 0, 0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 5); + assertEquals(23, maxFlow); + } - @Test - @DisplayName("Disconnected network has zero flow (Dinic)") - void disconnectedGraph() { - int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; - int maxFlow = Dinic.maxFlow(capacity, 0, 2); - assertEquals(0, maxFlow); - } + @Test + @DisplayName("Disconnected network has zero flow (Dinic)") + void disconnectedGraph() { + int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 2); + assertEquals(0, maxFlow); + } - @Test - @DisplayName("Source equals sink returns zero (Dinic)") - void sourceEqualsSink() { - int[][] capacity = {{0, 5}, {0, 0}}; - int maxFlow = Dinic.maxFlow(capacity, 0, 0); - assertEquals(0, maxFlow); - } + @Test + @DisplayName("Source equals sink returns zero (Dinic)") + void sourceEqualsSink() { + int[][] capacity = {{0, 5}, {0, 0}}; + int maxFlow = Dinic.maxFlow(capacity, 0, 0); + assertEquals(0, maxFlow); + } - @Test - @DisplayName("Invalid matrix throws exception (Dinic)") - void invalidMatrix() { - int[][] capacity = {{0, 1}, {1}}; - assertThrows(IllegalArgumentException.class, () -> Dinic.maxFlow(capacity, 0, 1)); - } + @Test + @DisplayName("Invalid matrix throws exception (Dinic)") + void invalidMatrix() { + int[][] capacity = {{0, 1}, {1}}; + assertThrows(IllegalArgumentException.class, () -> Dinic.maxFlow(capacity, 0, 1)); + } - @Test - @DisplayName("Dinic matches Edmonds-Karp on random small graphs") - void parityWithEdmondsKarp() { - java.util.Random rnd = new java.util.Random(42); - for (int n = 3; n <= 7; n++) { - for (int it = 0; it < 25; it++) { - int[][] cap = new int[n][n]; - for (int i = 0; i < n; i++) { - for (int j = 0; j < n; j++) { - if (i != j && rnd.nextDouble() < 0.35) { - cap[i][j] = rnd.nextInt(10); // capacities 0..9 + @Test + @DisplayName("Dinic matches Edmonds-Karp on random small graphs") + void parityWithEdmondsKarp() { + java.util.Random rnd = new java.util.Random(42); + for (int n = 3; n <= 7; n++) { + for (int it = 0; it < 25; it++) { + int[][] cap = new int[n][n]; + for (int i = 0; i < n; i++) { + for (int j = 0; j < n; j++) { + if (i != j && rnd.nextDouble() < 0.35) { + cap[i][j] = rnd.nextInt(10); // capacities 0..9 + } + } + } + int s = 0; + int t = n - 1; + int f1 = Dinic.maxFlow(copyMatrix(cap), s, t); + int f2 = EdmondsKarp.maxFlow(cap, s, t); + assertEquals(f2, f1); } - } } - int s = 0; - int t = n - 1; - int f1 = Dinic.maxFlow(copyMatrix(cap), s, t); - int f2 = EdmondsKarp.maxFlow(cap, s, t); - assertEquals(f2, f1); - } } - } - private static int[][] copyMatrix(int[][] a) { - int[][] b = new int[a.length][a.length]; - for (int i = 0; i < a.length; i++) { - b[i] = java.util.Arrays.copyOf(a[i], a[i].length); + private static int[][] copyMatrix(int[][] a) { + int[][] b = new int[a.length][a.length]; + for (int i = 0; i < a.length; i++) { + b[i] = java.util.Arrays.copyOf(a[i], a[i].length); + } + return b; } - return b; - } }