diff --git a/DIRECTORY.md b/DIRECTORY.md
index 3bfedac64b89..55a7c7f0a511 100644
--- a/DIRECTORY.md
+++ b/DIRECTORY.md
@@ -351,6 +351,7 @@
- 📄 [ConstrainedShortestPath](src/main/java/com/thealgorithms/graph/ConstrainedShortestPath.java)
- 📄 [HopcroftKarp](src/main/java/com/thealgorithms/graph/HopcroftKarp.java)
- 📄 [PredecessorConstrainedDfs](src/main/java/com/thealgorithms/graph/PredecessorConstrainedDfs.java)
+ - 📄 [PushRelabel](src/main/java/com/thealgorithms/graph/PushRelabel.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)
diff --git a/src/main/java/com/thealgorithms/graph/PushRelabel.java b/src/main/java/com/thealgorithms/graph/PushRelabel.java
new file mode 100644
index 000000000000..1bfb5ceacce0
--- /dev/null
+++ b/src/main/java/com/thealgorithms/graph/PushRelabel.java
@@ -0,0 +1,162 @@
+package com.thealgorithms.graph;
+
+import java.util.ArrayDeque;
+import java.util.Arrays;
+import java.util.Queue;
+
+/**
+ * Push–Relabel (Relabel-to-Front variant simplified to array scanning) for maximum flow.
+ *
+ *
Input graph is a capacity matrix where {@code capacity[u][v]} is the capacity of the edge
+ * {@code u -> v}. Capacities must be non-negative. Vertices are indexed in {@code [0, n)}.
+ *
+ *
Time complexity: O(V^3) in the worst case for the array-based variant; typically fast in
+ * practice. This implementation uses a residual network over an adjacency-matrix representation.
+ *
+ *
The API mirrors {@link EdmondsKarp#maxFlow(int[][], int, int)} and {@link Dinic#maxFlow(int[][], int, int)}.
+ *
+ * @see Wikipedia: Push–Relabel maximum flow algorithm
+ */
+public final class PushRelabel {
+
+ private PushRelabel() {
+ }
+
+ /**
+ * Computes the maximum flow from {@code source} to {@code sink} using Push–Relabel.
+ *
+ * @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 inputs are invalid
+ */
+ public static int maxFlow(int[][] capacity, int source, int sink) {
+ validate(capacity, source, sink);
+ final int n = capacity.length;
+ if (source == sink) {
+ return 0;
+ }
+
+ int[][] residual = new int[n][n];
+ for (int i = 0; i < n; i++) {
+ residual[i] = Arrays.copyOf(capacity[i], n);
+ }
+
+ int[] height = new int[n];
+ int[] excess = new int[n];
+ int[] nextNeighbor = new int[n];
+
+ // Preflow initialization
+ height[source] = n;
+ for (int v = 0; v < n; v++) {
+ int cap = residual[source][v];
+ if (cap > 0) {
+ residual[source][v] -= cap;
+ residual[v][source] += cap;
+ excess[v] += cap;
+ excess[source] -= cap;
+ }
+ }
+
+ // Active queue contains vertices (except source/sink) with positive excess
+ Queue active = new ArrayDeque<>();
+ for (int v = 0; v < n; v++) {
+ if (v != source && v != sink && excess[v] > 0) {
+ active.add(v);
+ }
+ }
+
+ State state = new State(residual, height, excess, nextNeighbor, source, sink, active);
+
+ while (!active.isEmpty()) {
+ int u = active.poll();
+ discharge(u, state);
+ if (excess[u] > 0) {
+ // still active after discharge; push to back
+ active.add(u);
+ }
+ }
+
+ // Total flow equals excess at sink
+ return excess[sink];
+ }
+
+ private static void discharge(int u, State s) {
+ final int n = s.residual.length;
+ while (s.excess[u] > 0) {
+ if (s.nextNeighbor[u] >= n) {
+ relabel(u, s.residual, s.height);
+ s.nextNeighbor[u] = 0;
+ continue;
+ }
+ int v = s.nextNeighbor[u];
+ if (s.residual[u][v] > 0 && s.height[u] == s.height[v] + 1) {
+ int delta = Math.min(s.excess[u], s.residual[u][v]);
+ s.residual[u][v] -= delta;
+ s.residual[v][u] += delta;
+ s.excess[u] -= delta;
+ int prevExcessV = s.excess[v];
+ s.excess[v] += delta;
+ if (v != s.source && v != s.sink && prevExcessV == 0) {
+ s.active.add(v);
+ }
+ } else {
+ s.nextNeighbor[u]++;
+ }
+ }
+ }
+
+ private static final class State {
+ final int[][] residual;
+ final int[] height;
+ final int[] excess;
+ final int[] nextNeighbor;
+ final int source;
+ final int sink;
+ final Queue active;
+
+ State(int[][] residual, int[] height, int[] excess, int[] nextNeighbor, int source, int sink, Queue active) {
+ this.residual = residual;
+ this.height = height;
+ this.excess = excess;
+ this.nextNeighbor = nextNeighbor;
+ this.source = source;
+ this.sink = sink;
+ this.active = active;
+ }
+ }
+
+ private static void relabel(int u, int[][] residual, int[] height) {
+ final int n = residual.length;
+ int minHeight = Integer.MAX_VALUE;
+ for (int v = 0; v < n; v++) {
+ if (residual[u][v] > 0) {
+ minHeight = Math.min(minHeight, height[v]);
+ }
+ }
+ if (minHeight < Integer.MAX_VALUE) {
+ height[u] = minHeight + 1;
+ }
+ }
+
+ private static void validate(int[][] capacity, int source, int sink) {
+ if (capacity == null || capacity.length == 0) {
+ throw new IllegalArgumentException("Capacity matrix must not be null or empty");
+ }
+ 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");
+ }
+ }
+}
diff --git a/src/test/java/com/thealgorithms/graph/PushRelabelTest.java b/src/test/java/com/thealgorithms/graph/PushRelabelTest.java
new file mode 100644
index 000000000000..b0021ec805b8
--- /dev/null
+++ b/src/test/java/com/thealgorithms/graph/PushRelabelTest.java
@@ -0,0 +1,66 @@
+package com.thealgorithms.graph;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+class PushRelabelTest {
+
+ @Test
+ @DisplayName("Classic CLRS network yields max flow 23 (PushRelabel)")
+ 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 = PushRelabel.maxFlow(capacity, 0, 5);
+ assertEquals(23, maxFlow);
+ }
+
+ @Test
+ @DisplayName("Disconnected network has zero flow (PushRelabel)")
+ void disconnectedGraph() {
+ int[][] capacity = {{0, 0, 0}, {0, 0, 0}, {0, 0, 0}};
+ int maxFlow = PushRelabel.maxFlow(capacity, 0, 2);
+ assertEquals(0, maxFlow);
+ }
+
+ @Test
+ @DisplayName("Source equals sink returns zero (PushRelabel)")
+ void sourceEqualsSink() {
+ int[][] capacity = {{0, 5}, {0, 0}};
+ int maxFlow = PushRelabel.maxFlow(capacity, 0, 0);
+ assertEquals(0, maxFlow);
+ }
+
+ @Test
+ @DisplayName("PushRelabel matches Dinic and EdmondsKarp on random small graphs")
+ void parityWithOtherMaxFlow() {
+ 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 fPushRelabel = PushRelabel.maxFlow(copyMatrix(cap), s, t);
+ int fDinic = Dinic.maxFlow(copyMatrix(cap), s, t);
+ int fEdmondsKarp = EdmondsKarp.maxFlow(cap, s, t);
+ assertEquals(fDinic, fPushRelabel);
+ assertEquals(fEdmondsKarp, fPushRelabel);
+ }
+ }
+ }
+
+ 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;
+ }
+}