Skip to content

Commit b1074ba

Browse files
committed
Add Dinics max flow algorithm with tests and index update
1 parent 14a23b7 commit b1074ba

File tree

3 files changed

+201
-0
lines changed

3 files changed

+201
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,7 @@
352352
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
353353
- 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
354354
- 📄 [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java)
355+
- 📄 [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java)
355356
- 📁 **greedyalgorithms**
356357
- 📄 [ActivitySelection](src/main/java/com/thealgorithms/greedyalgorithms/ActivitySelection.java)
357358
- 📄 [BandwidthAllocation](src/main/java/com/thealgorithms/greedyalgorithms/BandwidthAllocation.java)
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Arrays;
5+
import java.util.Queue;
6+
7+
/**
8+
* Dinic's algorithm for computing maximum flow in a directed graph.
9+
*
10+
* <p>Time complexity: O(E * V^2) in the worst case, but typically faster in practice
11+
* and near O(E * sqrt(V)) for unit networks.</p>
12+
*
13+
* <p>The graph is represented using a capacity matrix where capacity[u][v] is the
14+
* capacity of the directed edge u -> v. Capacities must be non-negative.
15+
* The algorithm builds level graphs using BFS and finds blocking flows using DFS
16+
* with current-edge optimization.</p>
17+
*
18+
* <p>This implementation mirrors the API and validation style of
19+
* {@link EdmondsKarp#maxFlow(int[][], int, int)} for consistency.</p>
20+
*/
21+
public final class Dinic {
22+
23+
private Dinic() {}
24+
25+
/**
26+
* Computes the maximum flow from source to sink using Dinic's algorithm.
27+
*
28+
* @param capacity square capacity matrix (n x n); entries must be >= 0
29+
* @param source source vertex index in [0, n)
30+
* @param sink sink vertex index in [0, n)
31+
* @return the maximum flow value
32+
* @throws IllegalArgumentException if the input matrix is null/non-square/has negatives or indices invalid
33+
*/
34+
public static int maxFlow(int[][] capacity, int source, int sink) {
35+
if (capacity == null || capacity.length == 0) {
36+
throw new IllegalArgumentException("Capacity matrix must not be null or empty");
37+
}
38+
final int n = capacity.length;
39+
for (int i = 0; i < n; i++) {
40+
if (capacity[i] == null || capacity[i].length != n) {
41+
throw new IllegalArgumentException("Capacity matrix must be square");
42+
}
43+
for (int j = 0; j < n; j++) {
44+
if (capacity[i][j] < 0) {
45+
throw new IllegalArgumentException("Capacities must be non-negative");
46+
}
47+
}
48+
}
49+
if (source < 0 || sink < 0 || source >= n || sink >= n) {
50+
throw new IllegalArgumentException("Source and sink must be valid vertex indices");
51+
}
52+
if (source == sink) {
53+
return 0;
54+
}
55+
56+
// residual capacities
57+
int[][] residual = new int[n][n];
58+
for (int i = 0; i < n; i++) {
59+
residual[i] = Arrays.copyOf(capacity[i], n);
60+
}
61+
62+
int[] level = new int[n];
63+
int flow = 0;
64+
while (bfsBuildLevelGraph(residual, source, sink, level)) {
65+
int[] next = new int[n]; // current-edge optimization
66+
int pushed;
67+
do {
68+
pushed = dfsBlocking(residual, level, next, source, sink, Integer.MAX_VALUE);
69+
flow += pushed;
70+
} while (pushed > 0);
71+
}
72+
return flow;
73+
}
74+
75+
private static boolean bfsBuildLevelGraph(int[][] residual, int source, int sink, int[] level) {
76+
Arrays.fill(level, -1);
77+
level[source] = 0;
78+
Queue<Integer> q = new ArrayDeque<>();
79+
q.add(source);
80+
while (!q.isEmpty()) {
81+
int u = q.poll();
82+
for (int v = 0; v < residual.length; v++) {
83+
if (residual[u][v] > 0 && level[v] == -1) {
84+
level[v] = level[u] + 1;
85+
if (v == sink) {
86+
return true;
87+
}
88+
q.add(v);
89+
}
90+
}
91+
}
92+
return level[sink] != -1;
93+
}
94+
95+
private static int dfsBlocking(int[][] residual, int[] level, int[] next, int u, int sink, int f) {
96+
if (u == sink) {
97+
return f;
98+
}
99+
final int n = residual.length;
100+
for (int v = next[u]; v < n; v++, next[u] = v) {
101+
if (residual[u][v] <= 0) continue;
102+
if (level[v] != level[u] + 1) continue;
103+
int pushed = dfsBlocking(residual, level, next, v, sink, Math.min(f, residual[u][v]));
104+
if (pushed > 0) {
105+
residual[u][v] -= pushed;
106+
residual[v][u] += pushed;
107+
return pushed;
108+
}
109+
}
110+
return 0;
111+
}
112+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import org.junit.jupiter.api.DisplayName;
7+
import org.junit.jupiter.api.Test;
8+
9+
class DinicTest {
10+
11+
@Test
12+
@DisplayName("Classic CLRS network yields max flow 23 (Dinic)")
13+
void clrsExample() {
14+
int[][] capacity = {
15+
{0, 16, 13, 0, 0, 0},
16+
{0, 0, 10, 12, 0, 0},
17+
{0, 4, 0, 0, 14, 0},
18+
{0, 0, 9, 0, 0, 20},
19+
{0, 0, 0, 7, 0, 4},
20+
{0, 0, 0, 0, 0, 0}
21+
};
22+
int maxFlow = Dinic.maxFlow(capacity, 0, 5);
23+
assertEquals(23, maxFlow);
24+
}
25+
26+
@Test
27+
@DisplayName("Disconnected network has zero flow (Dinic)")
28+
void disconnectedGraph() {
29+
int[][] capacity = {
30+
{0, 0, 0},
31+
{0, 0, 0},
32+
{0, 0, 0}
33+
};
34+
int maxFlow = Dinic.maxFlow(capacity, 0, 2);
35+
assertEquals(0, maxFlow);
36+
}
37+
38+
@Test
39+
@DisplayName("Source equals sink returns zero (Dinic)")
40+
void sourceEqualsSink() {
41+
int[][] capacity = {
42+
{0, 5},
43+
{0, 0}
44+
};
45+
int maxFlow = Dinic.maxFlow(capacity, 0, 0);
46+
assertEquals(0, maxFlow);
47+
}
48+
49+
@Test
50+
@DisplayName("Invalid matrix throws exception (Dinic)")
51+
void invalidMatrix() {
52+
int[][] capacity = {
53+
{0, 1},
54+
{1}
55+
};
56+
assertThrows(IllegalArgumentException.class, () -> Dinic.maxFlow(capacity, 0, 1));
57+
}
58+
59+
@Test
60+
@DisplayName("Dinic matches Edmonds-Karp on random small graphs")
61+
void parityWithEdmondsKarp() {
62+
java.util.Random rnd = new java.util.Random(42);
63+
for (int n = 3; n <= 7; n++) {
64+
for (int it = 0; it < 25; it++) {
65+
int[][] cap = new int[n][n];
66+
for (int i = 0; i < n; i++) {
67+
for (int j = 0; j < n; j++) {
68+
if (i != j && rnd.nextDouble() < 0.35) {
69+
cap[i][j] = rnd.nextInt(10); // capacities 0..9
70+
}
71+
}
72+
}
73+
int s = 0, t = n - 1;
74+
int f1 = Dinic.maxFlow(copyMatrix(cap), s, t);
75+
int f2 = EdmondsKarp.maxFlow(cap, s, t);
76+
assertEquals(f2, f1);
77+
}
78+
}
79+
}
80+
81+
private static int[][] copyMatrix(int[][] a) {
82+
int[][] b = new int[a.length][a.length];
83+
for (int i = 0; i < a.length; i++) {
84+
b[i] = java.util.Arrays.copyOf(a[i], a[i].length);
85+
}
86+
return b;
87+
}
88+
}

0 commit comments

Comments
 (0)