Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions DIRECTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
119 changes: 119 additions & 0 deletions src/main/java/com/thealgorithms/graph/Dinic.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
71 changes: 71 additions & 0 deletions src/test/java/com/thealgorithms/graph/DinicTest.java
Original file line number Diff line number Diff line change
@@ -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;
}
}