Skip to content

Commit 1437036

Browse files
SamXop123alxkm
andauthored
feat(graph): add Push–Relabel max flow with tests and index (#6793)
* feat(graph): add Push–Relabel max flow with tests and index * style(checkstyle): reduce discharge parameter count via State holder * chore(pmd): make discharge void and remove empty else; satisfy PMD --------- Co-authored-by: a <[email protected]>
1 parent f8688ba commit 1437036

File tree

3 files changed

+229
-0
lines changed

3 files changed

+229
-0
lines changed

DIRECTORY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,7 @@
351351
- 📄 [ConstrainedShortestPath](src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java)
352352
- 📄 [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
353353
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
354+
- 📄 [PushRelabel](src/main/java/com/thealgorithms/graph/PushRelabel.java)
354355
- 📄 [StronglyConnectedComponentOptimized](src/main/java/com/thealgorithms/graph/StronglyConnectedComponentOptimized.java)
355356
- 📄 [TravelingSalesman](src/main/java/com/thealgorithms/graph/TravelingSalesman.java)
356357
- 📄 [Dinic](src/main/java/com/thealgorithms/graph/Dinic.java)
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
package com.thealgorithms.graph;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.Arrays;
5+
import java.util.Queue;
6+
7+
/**
8+
* Push–Relabel (Relabel-to-Front variant simplified to array scanning) for maximum flow.
9+
*
10+
* <p>Input graph is a capacity matrix where {@code capacity[u][v]} is the capacity of the edge
11+
* {@code u -> v}. Capacities must be non-negative. Vertices are indexed in {@code [0, n)}.
12+
*
13+
* <p>Time complexity: O(V^3) in the worst case for the array-based variant; typically fast in
14+
* practice. This implementation uses a residual network over an adjacency-matrix representation.
15+
*
16+
* <p>The API mirrors {@link EdmondsKarp#maxFlow(int[][], int, int)} and {@link Dinic#maxFlow(int[][], int, int)}.
17+
*
18+
* @see <a href="https://en.wikipedia.org/wiki/Push%E2%80%93relabel_maximum_flow_algorithm">Wikipedia: Push–Relabel maximum flow algorithm</a>
19+
*/
20+
public final class PushRelabel {
21+
22+
private PushRelabel() {
23+
}
24+
25+
/**
26+
* Computes the maximum flow from {@code source} to {@code sink} using Push–Relabel.
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 inputs are invalid
33+
*/
34+
public static int maxFlow(int[][] capacity, int source, int sink) {
35+
validate(capacity, source, sink);
36+
final int n = capacity.length;
37+
if (source == sink) {
38+
return 0;
39+
}
40+
41+
int[][] residual = new int[n][n];
42+
for (int i = 0; i < n; i++) {
43+
residual[i] = Arrays.copyOf(capacity[i], n);
44+
}
45+
46+
int[] height = new int[n];
47+
int[] excess = new int[n];
48+
int[] nextNeighbor = new int[n];
49+
50+
// Preflow initialization
51+
height[source] = n;
52+
for (int v = 0; v < n; v++) {
53+
int cap = residual[source][v];
54+
if (cap > 0) {
55+
residual[source][v] -= cap;
56+
residual[v][source] += cap;
57+
excess[v] += cap;
58+
excess[source] -= cap;
59+
}
60+
}
61+
62+
// Active queue contains vertices (except source/sink) with positive excess
63+
Queue<Integer> active = new ArrayDeque<>();
64+
for (int v = 0; v < n; v++) {
65+
if (v != source && v != sink && excess[v] > 0) {
66+
active.add(v);
67+
}
68+
}
69+
70+
State state = new State(residual, height, excess, nextNeighbor, source, sink, active);
71+
72+
while (!active.isEmpty()) {
73+
int u = active.poll();
74+
discharge(u, state);
75+
if (excess[u] > 0) {
76+
// still active after discharge; push to back
77+
active.add(u);
78+
}
79+
}
80+
81+
// Total flow equals excess at sink
82+
return excess[sink];
83+
}
84+
85+
private static void discharge(int u, State s) {
86+
final int n = s.residual.length;
87+
while (s.excess[u] > 0) {
88+
if (s.nextNeighbor[u] >= n) {
89+
relabel(u, s.residual, s.height);
90+
s.nextNeighbor[u] = 0;
91+
continue;
92+
}
93+
int v = s.nextNeighbor[u];
94+
if (s.residual[u][v] > 0 && s.height[u] == s.height[v] + 1) {
95+
int delta = Math.min(s.excess[u], s.residual[u][v]);
96+
s.residual[u][v] -= delta;
97+
s.residual[v][u] += delta;
98+
s.excess[u] -= delta;
99+
int prevExcessV = s.excess[v];
100+
s.excess[v] += delta;
101+
if (v != s.source && v != s.sink && prevExcessV == 0) {
102+
s.active.add(v);
103+
}
104+
} else {
105+
s.nextNeighbor[u]++;
106+
}
107+
}
108+
}
109+
110+
private static final class State {
111+
final int[][] residual;
112+
final int[] height;
113+
final int[] excess;
114+
final int[] nextNeighbor;
115+
final int source;
116+
final int sink;
117+
final Queue<Integer> active;
118+
119+
State(int[][] residual, int[] height, int[] excess, int[] nextNeighbor, int source, int sink, Queue<Integer> active) {
120+
this.residual = residual;
121+
this.height = height;
122+
this.excess = excess;
123+
this.nextNeighbor = nextNeighbor;
124+
this.source = source;
125+
this.sink = sink;
126+
this.active = active;
127+
}
128+
}
129+
130+
private static void relabel(int u, int[][] residual, int[] height) {
131+
final int n = residual.length;
132+
int minHeight = Integer.MAX_VALUE;
133+
for (int v = 0; v < n; v++) {
134+
if (residual[u][v] > 0) {
135+
minHeight = Math.min(minHeight, height[v]);
136+
}
137+
}
138+
if (minHeight < Integer.MAX_VALUE) {
139+
height[u] = minHeight + 1;
140+
}
141+
}
142+
143+
private static void validate(int[][] capacity, int source, int sink) {
144+
if (capacity == null || capacity.length == 0) {
145+
throw new IllegalArgumentException("Capacity matrix must not be null or empty");
146+
}
147+
int n = capacity.length;
148+
for (int i = 0; i < n; i++) {
149+
if (capacity[i] == null || capacity[i].length != n) {
150+
throw new IllegalArgumentException("Capacity matrix must be square");
151+
}
152+
for (int j = 0; j < n; j++) {
153+
if (capacity[i][j] < 0) {
154+
throw new IllegalArgumentException("Capacities must be non-negative");
155+
}
156+
}
157+
}
158+
if (source < 0 || sink < 0 || source >= n || sink >= n) {
159+
throw new IllegalArgumentException("Source and sink must be valid vertex indices");
160+
}
161+
}
162+
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
package com.thealgorithms.graph;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
5+
import org.junit.jupiter.api.DisplayName;
6+
import org.junit.jupiter.api.Test;
7+
8+
class PushRelabelTest {
9+
10+
@Test
11+
@DisplayName("Classic CLRS network yields max flow 23 (PushRelabel)")
12+
void clrsExample() {
13+
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}};
14+
int maxFlow = PushRelabel.maxFlow(capacity, 0, 5);
15+
assertEquals(23, maxFlow);
16+
}
17+
18+
@Test
19+
@DisplayName("Disconnected network has zero flow (PushRelabel)")
20+
void disconnectedGraph() {
21+
int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
22+
int maxFlow = PushRelabel.maxFlow(capacity, 0, 2);
23+
assertEquals(0, maxFlow);
24+
}
25+
26+
@Test
27+
@DisplayName("Source equals sink returns zero (PushRelabel)")
28+
void sourceEqualsSink() {
29+
int[][] capacity = {{0, 5}, {0, 0}};
30+
int maxFlow = PushRelabel.maxFlow(capacity, 0, 0);
31+
assertEquals(0, maxFlow);
32+
}
33+
34+
@Test
35+
@DisplayName("PushRelabel matches Dinic and EdmondsKarp on random small graphs")
36+
void parityWithOtherMaxFlow() {
37+
java.util.Random rnd = new java.util.Random(42);
38+
for (int n = 3; n <= 7; n++) {
39+
for (int it = 0; it < 25; it++) {
40+
int[][] cap = new int[n][n];
41+
for (int i = 0; i < n; i++) {
42+
for (int j = 0; j < n; j++) {
43+
if (i != j && rnd.nextDouble() < 0.35) {
44+
cap[i][j] = rnd.nextInt(10); // capacities 0..9
45+
}
46+
}
47+
}
48+
int s = 0;
49+
int t = n - 1;
50+
int fPushRelabel = PushRelabel.maxFlow(copyMatrix(cap), s, t);
51+
int fDinic = Dinic.maxFlow(copyMatrix(cap), s, t);
52+
int fEdmondsKarp = EdmondsKarp.maxFlow(cap, s, t);
53+
assertEquals(fDinic, fPushRelabel);
54+
assertEquals(fEdmondsKarp, fPushRelabel);
55+
}
56+
}
57+
}
58+
59+
private static int[][] copyMatrix(int[][] a) {
60+
int[][] b = new int[a.length][a.length];
61+
for (int i = 0; i < a.length; i++) {
62+
b[i] = java.util.Arrays.copyOf(a[i], a[i].length);
63+
}
64+
return b;
65+
}
66+
}

0 commit comments

Comments
 (0)