Instead of augmenting paths, maintain a preflow (vertices may hold excess) and heights with for every residual edge . Repeatedly push excess along admissible edges () or relabel a vertex with excess but no admissible edge. With highest-label selection and the gap heuristic it runs in and is often the fastest max-flow in practice on dense or adversarial graphs. Use it when Dinic times out; otherwise Dinic is simpler (Max Flow).
struct PushRelabel { // highest label + gap heuristic
struct E { int to, rev; ll c; };
vector<vector<E>> g; vector<ll> ex; vector<int> H, cur; vector<vector<int>> hs;
PushRelabel(int n) : g(n), ex(n), H(n), cur(n), hs(2 * n) {}
void addEdge(int s, int t, ll cap, ll rcap = 0) {
if (s == t) return;
g[s].push_back({t, sz(g[t]), cap}); g[t].push_back({s, sz(g[s]) - 1, rcap});
}
void addFlow(E& e, ll f) {
E& b = g[e.to][e.rev];
if (!ex[e.to] && f) hs[H[e.to]].push_back(e.to);
e.c -= f; ex[e.to] += f; b.c += f; ex[b.to] -= f;
}
ll maxflow(int s, int t) {
int v = sz(g); H[s] = v; ex[t] = 1; // ex[t] = 1 keeps t out of the active lists
vector<int> co(2 * v); co[0] = v - 1;
for (auto& e : g[s]) addFlow(e, e.c);
for (int hi = 0;;) {
while (hs[hi].empty()) if (!hi--) return -ex[s];
int u = hs[hi].back(); hs[hi].pop_back();
while (ex[u] > 0) { // discharge u
if (cur[u] == sz(g[u])) { // relabel
H[u] = 1e9;
for (int i = 0; i < sz(g[u]); i++) { auto& e = g[u][i]; if (e.c && H[u] > H[e.to] + 1) H[u] = H[e.to] + 1, cur[u] = i; }
if (++co[H[u]], !--co[hi] && hi < v) // gap: nobody left at height hi
for (int i = 0; i < v; i++) if (hi < H[i] && H[i] < v) --co[H[i]], H[i] = v + 1;
hi = H[u];
} else if (g[u][cur[u]].c && H[u] == H[g[u][cur[u]].to] + 1) addFlow(g[u][cur[u]], min(ex[u], g[u][cur[u]].c));
else ++cur[u];
}
}
}
bool sourceSide(int a) const { return H[a] >= sz(g); } // after maxflow: S side of a minimum cut
};Proof: correctness and the generic bound
Invariant: for every residual edge , . Saturating the source edges at the start respects it because and those edges have no residual left. A push along with can create the residual edge , which satisfies . A relabel sets over residual edges, restoring the invariant at . Termination with a max flow: when no vertex except has excess, the preflow is a flow. If there were an augmenting path in the residual graph, it would have at most edges with heights dropping at most 1 per edge, from to : impossible. So no augmenting path exists and the flow is maximum. Bounds: a vertex with excess has a residual path back to (its excess came from ), so : at most relabels. Each edge is saturated by a push times between relabels of its endpoints, saturating pushes; non-saturating pushes are bounded by a potential argument ( heights of active vertices) by . FIFO selection gives and highest-label by sharper potential arguments.
Gap heuristic: if no vertex has height (with ), every vertex above can no longer reach (heights drop by at most one per edge), so lift them all to at once; their excess will flow back to . Together with highest-label selection this is the practical speedup. The minimum cut is read from heights: vertices with cannot reach .
When it matters: dense graphs with – and , unit-capacity graphs with bad structure for Dinic, and problems with many max-flow calls on the same graph (Gomory–Hu).