Added Dinic’s Max Flow algorithm with tests [Graphs] (#6762)

* Add Dinics max flow algorithm with tests and index update

* Docs: add Dinic reference link and apply clang-format

* Fix: Checkstyle violations in Dinic and tests

* style: apply clang-format to Dinic and tests

---------

Co-authored-by: a <alexanderklmn@gmail.com>
This commit is contained in:
Sameer Prajapati
2025-10-13 03:15:46 +05:30
committed by GitHub
parent 387ecef310
commit 883a050162
3 changed files with 191 additions and 0 deletions

View File

@@ -353,6 +353,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)

View File

@@ -0,0 +1,119 @@
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.
*
* <p>Time complexity: O(E * V^2) in the worst case, but typically faster in practice
* and near O(E * sqrt(V)) for unit networks.</p>
*
* <p>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.</p>
*
* <p>This implementation mirrors the API and validation style of
* {@link EdmondsKarp#maxFlow(int[][], int, int)} for consistency.</p>
*
* @see <a href="https://en.wikipedia.org/wiki/Dinic%27s_algorithm">Wikipedia: Dinic's algorithm</a>
*/
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<Integer> 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;
}
}

View File

@@ -0,0 +1,71 @@
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;
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);
}
return b;
}
}