Push–relabel maximum flow algorithm: Difference between revisions

Content deleted Content added
m References: clean up, removed: | page = <!-- can't have both -->, <!-- --> using AWB (10903)
{{MOS|article|date=July 2025| MOS:FORMULA - avoid mixing {{tag|math}} and {{tl|math}} in the same expression}}
 
(79 intermediate revisions by 48 users not shown)
Line 1:
{{Short description|Algorithm in mathematical optimization}}
In [[mathematical optimization]], the '''push–relabel algorithm''' (alternatively, '''preflow–push algorithm''') is an algorithm for computing [[maximum flow]]s. The name "push–relabel" comes from the two basic operations used in the algorithm. Throughout its execution, the algorithm maintains a "preflow" and gradually converts it into a maximum flow by moving flow locally between neighboring vertices using ''push'' operations under the guidance of an admissible network maintained by ''relabel'' operations. In comparison, the [[Ford–Fulkerson algorithm]] performs global augmentations that send flow following paths from the source all the way to the sink.<ref name="clrs26"/>
{{MOS|article|date=July 2025| [[MOS:FORMULA]] - avoid mixing {{tag|math}} and {{tl|math}} in the same expression}}
In [[mathematical optimization]], the '''push–relabel algorithm''' (alternatively, '''preflow–push algorithm''') is an algorithm for computing [[maximum flow]]s in a [[flow network]]. The name "push–relabel" comes from the two basic operations used in the algorithm. Throughout its execution, the algorithm maintains a "preflow" and gradually converts it into a maximum flow by moving flow locally between neighboring verticesnodes using ''push'' operations under the guidance of an admissible network maintained by ''relabel'' operations. In comparison, the [[Ford–Fulkerson algorithm]] performs global augmentations that send flow following paths from the source all the way to the sink.<ref name="clrs26"/>
 
The push–relabel algorithm is considered one of the most efficient maximum flow algorithms. The generic algorithm has a [[strongly polynomial]] {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>''E'')}} time complexity, which is asymptotically more efficient than the {{nowrapmath|''O''(''VE''<sup>&nbsp;2</sup>)}} [[Edmonds–Karp algorithm]].<ref name="goldberg86"/> Specific variants of the algorithms achieve even lower time complexities. The variant based on the highest label vertexnode selection rule has {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>{{sqrt|''E''}})}} time complexity and is generally regarded as the benchmark for maximum flow algorithms.<ref name="ahuja97"/><ref name="goldberg08"/> Subcubic {{nowrapmath|''O''(''VE''&#x200a;log&#x200a;(''V''<sup>&nbsp;2</sup>/''E''))}} time complexity can be achieved using [[Link-cut tree|dynamic trees]],<ref name="goldberg86"/> although in practice it is less efficient.<ref{{reference nameneeded|date="goldberg86"/>December 2024}}
 
The push–relabel algorithm has been extended to compute [[minimum cost flow]]s.<ref name="goldberg97"/> The idea of distance labels has led to a more efficient augmenting path algorithm, which in turn can be incorporated back into the push–relabel algorithm to create a variant with even higher empirical performance.<ref name="goldberg08"/><ref name="ahuja91"/>
Line 7 ⟶ 9:
==History==
 
The concept of a preflow was originally designed by [[Alexander V. Karzanov]] and was published in 1974 in Soviet Mathematical Dokladi 15. This preflowpre-flow algorithm also used a push operation; however, it used distances in the auxiliary network to determine where to push the flow instead of a labeling system.<ref name="goldberg86"/><ref name="goldberg2014"/>
 
The push-relabel algorithm was designed by [[Andrew V. Goldberg]] and [[Robert Tarjan]]. The algorithm was initially presentpresented in November 1986 in STOC '86: Proceedings of the eighteenth annual ACM symposium on Theory of computing, and then officially in October 1988 as an article in the Journal of the ACM. Both papers detail a generic form of the algorithm terminating in {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>''E'')}} along with a {{nowrapmath|''O''(''V''<sup>&nbsp;3</sup>)}} sequential implementation, a {{nowrapmath|''O''(''VE''&#x200anbsp;log&#x200a;(''V''<sup>&nbsp;2</sup>/''E''))}} implementation using dynamic trees, and parallel/distributed implementation.<ref name="goldberg86"/><ref name="goldberg88"/> As explained in,<ref name="amo93"/> Goldberg-Tarjan introduced distance labels by incorporating them into the parallel maximum flow algorithm of Yossi Shiloach and [[Uzi Vishkin]].<ref name="sv82"/>
 
==Concepts==
Line 16 ⟶ 18:
{{main|Flow network}}
 
Let:
Consider a flow network <math>G = (V,E)</math> with a pair of distinct vertices <math>s</math> and <math>t</math> designated as the source and the sink, respectively. The <math>c(u, v) \ge 0</math> relation denotes the capacity of each edge <math>(u, v) \in E</math>. If <math>(u, v) \notin E</math>, then we assume that <math>c(u, v) = 0</math>. A flow on <math>G</math> is a function [[real number|real]] [[function (mathematics)|function]] <math>\ f:V \times V \rightarrow \mathbb{R}</math> satisfying the following conditions:
*{{math|''G'' {{=}} (''V'', ''E'')}} be a ''network'' with ''capacity function'' {{math|''c'': ''V'' × ''V'' → <math>\mathbb{R}</math><sub>∞</sub>}},
*{{math|''F'' {{=}} (''G'', ''c'', ''s'', ''t'')}} a ''flow network'', where {{math|''s'' ∈ ''V''}} and {{math|''t'' ∈ ''V''}} are chosen ''source'' and ''sink'' vertices respectively,
*{{math|''f'' : ''V'' × ''V'' → <math>\mathbb{R}</math>}} denote a [[Flow network#Flows|''pre-flow'']] in {{mvar|F}},
*{{math|''x''<sub>''f''</sub> : ''V'' → <math>\mathbb{R}</math>}} denote the ''excess'' function with respect to the flow {{mvar|f}}, defined by {{math|''x''<sub>''f''</sub> (''u'') {{=}} Σ<sub>''v'' ∈ ''V''</sub> ''f'' (''v'', ''u'') − Σ<sub>''v'' ∈ ''V''</sub> ''f'' (''u'', ''v'')}},
*{{math|''c''<sub>''f''</sub> : ''V'' × ''V'' → <math>\mathbb{R}</math><sub>∞</sub>}} denote the ''residual capacity function'' with respect to the flow {{mvar|f}}, defined by {{math|''c''<sub>''f''</sub>&nbsp;(''e'') {{=}} ''c''(''e'') − ''f''&nbsp;(''e'')}},
*{{math|''E''<sub>''f''</sub> ⊂ ''E''}} being the edges where {{math|''f'' < ''c''}},
and
*{{math|''G''<sub>''f''</sub> (''V'', ''E''<sub>''f''&nbsp;</sub>)}} denote the [[Flow network#Residuals|''residual network'']] of {{mvar|G}} with respect to the flow {{mvar|f}}.
 
The push–relabel algorithm uses a nonnegative integer ''valid labeling'''labeling function''' which makes use of ''distance labels'', or ''heights'', on verticesnodes to determine which vertex pairarcs should be selected for the push operation. This labeling function is denoted by <{{math>h(v),|𝓁 v \in: ''V'' → <math>\mathbb{N}</math>}}. This function must satisfy the following conditions in order to be considered valid:
:{|
| '''Capacity constraints''': || <math>\ f(u,v) \le c(u,v), \quad \forall u, v \in V</math>
|-
| '''Skew symmetry''': || <math>\ f(u,v) = - f(v,u), \quad \forall u, v \in V</math>
|-
| '''Flow conservation''': || <math>\ \sum_{v \in V} f(u,v) = 0, \quad \forall u \in V - \{s,t\}</math>
|}
 
:'''Valid labeling''':
The push–relabel algorithm introduces the concept of ''preflows''. A preflow is a function with a definition almost identical to that of a flow except that it relaxes the flow conservation condition. Instead of requiring strict flow balance at vertices other than ''s'' and ''t'', it allows them to carry positive excesses. This means that in a preflow the total flow into a vertex can exceed the flow out of the vertex. Put symbolically:
::{{math|𝓁(''u'') ≤ 𝓁(''v'') + 1}} for all {{math|(''u'', ''v'') ∈ ''E''<sub>''f''</sub>}}
| :'''Source condition''': || <math>h(s) = |V|</math>
::{{math|𝓁(''s'') {{=}} {{!}}&nbsp;''V''&nbsp;{{!}}}}
| :'''Sink conservation''': || <math>h(t) = 0</math>
::{{math|𝓁(''t'') {{=}} 0}}
 
In the algorithm, the heightlabel values of ''{{mvar|s''}} and ''{{mvar|t''}} are fixed. <{{math>h|𝓁(''u'')</math>}} is a lower bound of the unweighted distance from ''{{mvar|u''}} to ''{{mvar|t''}} in {{math|''G''<mathsub>G_f''f''</mathsub>}}&nbsp; if ''{{mvar|t''}} is reachable from ''{{mvar|u''}}. If ''{{mvar|u''}} has been disconnected from ''{{mvar|t''}}, then <{{math>h|𝓁(''u'') - |{{!}}&nbsp;''V|</math>''&nbsp;{{!}}}} is a lower bound of the unweighted distance from ''{{mvar|u''}} to ''{{mvar|s''}}. As a result, if a valid heightlabeling function exists, there are no {{math|''s''-''t''}} paths in <{{math|''G''<sub>G_f''f''</mathsub>}}&nbsp; because no such paths can be longer than <{{math>|{{!}}&nbsp;''V|''&nbsp;{{!}} - 1</math>}}.
:{|
| '''Non-Negative constraint''': || <math>\ \sum\limits_{u \in V} f(u, v) \geq 0, \quad \forall v \in V - \{s\} </math>
|-
| '''Flow Excess''': || <math> e(v) =
\begin{cases}
\sum\limits_{u \in V} f(u, v), \quad \forall v \in V - \{s\} \\
\infty, \quad v = s
\end{cases} </math>
|}
 
A vertex <math>v</math> is called ''active'' if <math>e(v) > 0</math> for <math>v \in V - \{s,t\}</math>.
 
For each <math>(u, v) \in V \times V</math>, denote its ''residual capacity'' by <math>c_f(u, v) = c(u, v) - f(u, v)</math>. The residual network of <math>G</math> with respect to a preflow <math>f</math> is defined as <math>G_f(V, E_f)</math> where the residual edges are defined as <math>E_f = \{ (u, v) | u, v \in V \and c_f(u, v) > 0 \}</math>. If there is no path from any active vertex to ''t'' in <math>G_f</math>, then preflow is called ''maximum''. In a maximum preflow, <math>e(t)</math> is equal to the value of a maximum flow; if <math>T</math> is the set of vertices from which ''t'' is reachable in <math>G_f</math>, and <math>S = V \backslash T</math>, then <math>(S, T)</math> is a [[Max-flow min-cut theorem|minimum ''s''-''t'' cut]].
 
The push–relabel algorithm uses a nonnegative integer ''valid labeling'' function which makes use of ''distance labels'', or ''heights'', on vertices to determine which vertex pair should be selected for the push operation. This labeling function is denoted by <math>h(v), v \in V</math>. This function must satisfy the following conditions in order to be considered valid:
 
:{|
| '''Valid labeling''': || <math>h(u) \le h(v) + 1, \quad \forall (u, v) \in E_f</math>
|-
| '''Source condition''': || <math>h(s) = |V|</math>
|-
| '''Sink conservation''': || <math>h(t) = 0</math>
|}
 
An arc {{math|(''u'', ''v'') ∈ ''E''<sub>''f''</sub>}}&nbsp; is called '''admissible''' if {{math|𝓁(''u'') {{=}} 𝓁(''v'') + 1}}. The '''admissible network''' {{math|''G̃''<sub>''f''</sub> (''V'', ''Ẽ''<sub>''f''</sub>&nbsp;)}} is composed of the set of arcs {{math|''e'' ∈ ''E''<sub>''f''</sub>}}&nbsp; that are admissible. The admissible network is acyclic.
In the algorithm, the height values of ''s'' and ''t'' are fixed. <math>h(u)</math> is a lower bound of the unweighted distance from ''u'' to ''t'' in <math>G_f</math> if ''t'' is reachable from ''u''. If ''u'' has been disconnected from ''t'', then <math>h(u) - |V|</math> is a lower bound of the unweighted distance from ''u'' to ''s''. As a result, if a valid height function exists, there are no ''s''-''t'' paths in <math>G_f</math> because no such paths can be longer than <math>|V| - 1</math>.
 
For a fixed flow {{math|''f''}}, a vertex {{math|''v ∉ ''{''s, t''} }} is called '''active''' if it has positive excess with respect to {{math|''f''}}, i.e., {{math|''x''<sub>''f''</sub> (''u'') > 0}}.
An edge <math>(u, v) \in E_f</math> is called ''admissible'' if <math>h(u) = h(v) + 1</math>. The network <math>\tilde{G}_f (V, \tilde{E}_f)</math> when <math>\tilde{E}_f = \{ (u, v) | (u, v) \in E_f \and h(u) = h(v) + 1 \}</math> is called the ''admissible network''. The admissible network is acyclic.
 
===Operations===
Line 60 ⟶ 47:
====Initialization====
 
The algorithm starts by creating a residual graph, initializing the preflow values to zero and performing a set of saturating push operations on residual edges exiting the source,arcs {{nowrapmath|(''s'', ''v'')}} exiting the source, where {{math|''v'' ∈ ''V'' \ {{(}}''s''{{)}}}}. Similarly, the label heightslabels are initialized such that the heightlabel at the source is in the number of verticesnodes in the graph, {{nowrapmath|''h''𝓁(''s'') {{=}} {{!}}&nbsp;''V''&nbsp;{{!}}}}, and all other verticesnodes are given a heightlabel of zero. Once the initialization is complete, the algorithm repeatedly performs either the push or relabel operations against active verticesnodes until no applicable operation can be performed.
 
====Push====
The push operation applies on an admissible out-edgearc {{nowrapmath|(''u'', ''v'')}} of an active vertexnode ''{{mvar|u''}} in {{nowrapmath|''G''<sub>''f''</sub>''}}. It moves <{{math> \|min\{{(}}''x''<sub>''f''</sub> e(''u''), c_f''c''<sub>''f''</sub> (''u'',''v'')\{{)}}}} </math> units of flow from ''{{mvar|u''}} to ''{{mvar|v''}}.
 
push(u, v):
assert ex<sub>f</sub>[u] > 0 and h𝓁[u] == h𝓁[v] + 1
Δ = min(ex<sub>f</sub>[u], c[u][v] - f[u][v])
f[u][v] += Δ
f[v][u] -= Δ
ex<sub>f</sub>[u] -= Δ
ex<sub>f</sub>[v] += Δ
 
A push operation that causes {{nowrapmath|''f''&nbsp;(''u'', ''v'')}} to reach {{nowrapmath|''c''(''u'', ''v'')}} is called a ''saturating''saturating push''' since it uses up all the available capacity of the residual edgearc. Otherwise, all of the excess at the vertexnode is pushed across the residual edgearc. This is called an '''unsaturating''' or '''non-saturating'' push'''.
 
====Relabel====
The relabel operation applies on an active vertexnode ''{{mvar|u''}} which is neither the source nor the sink without any admissible out-edgesarcs in {{nowrapmath|''G<sub>f</sub>''}}. It modifies {{nowrapmath|''h''𝓁(''u'')}} to be the minimum value such that an admissible out-edgearc is created. Note that this always increases {{nowrapmath|''h''𝓁(''u'')}} and never creates a steep edgearc, which is an edgearc {{nowrapmath|(''u'', ''v'')}} such that {{nowrapmath|''c''<sub>''f''</sub>''&nbsp;(''u'', ''v'') > 0}}, and {{nowrapmath|''h''𝓁(''u'') > ''h''𝓁(''v'') + 1}}.
 
relabel(u):
assert ex<sub>f</sub>[u] > 0 and h𝓁[u] <= h𝓁[v] ∀vfor all v such that c<sub>f</sub>[u][v] <> c[u][v]0
h𝓁[u] = 1 + min(h𝓁[v] ∀vfor all v such that c<sub>f</sub>[u][v] <> c[u][v]0) + 1
 
====Effects of push and relabel====
After a push or relabel operation, ''h''{{math|𝓁}} remains a valid heightlabeling function with respect to ''{{mvar|f''}}.
 
For a push operation on an admissible edgearc {{nowrapmath|(''u'', ''v'')}}, it may add an edgearc {{nowrapmath|(''v'', ''u'')}} to {{nowrapmath|''E<sub>f</sub>''}}, where {{nowrapmath|''h''𝓁(''v'') {{=}} ''h''𝓁(''u'') − 1 ≤ ''h''𝓁(''u'') + 1}}; it may also remove the edgearc {{nowrapmath|(''u'', ''v'')}} from {{nowrapmath|''E''<sub>''f''</sub>''}}, where it effectively removes the constraint {{nowrapmath|''h''𝓁(''u'') ≤ ''h''𝓁(''v'') + 1}}.
 
To see that a relabel operation on vertexnode ''{{mvar|u''}} preserves the validity of {{nowrapmath|''h''𝓁(''u'')}}, notice that this is trivially guaranteed by definition for the out-edgesarcs of ''u'' in {{nowrapmath|''G''<sub>''f''</sub>''}}. For the in-edgesarcs of ''{{mvar|u''}} in the {{nowrapmath|''G''<sub>''f''</sub>''}}, the increased {{nowrapmath|''h''𝓁(''u'')}} can only satisfy the constraints less tightly, not violate them.
 
==The generic push–relabel algorithm==
 
The following algorithm is a generic version of the push–relabel algorithm. It is used as a proof of concept only and does not contain implementation details on how to select an active vertexnode for the push and relabel operations. This generic version of the algorithm will terminate in {{nowrapmath|''O''(''V''<sup>2</sup>''E'')}}.
===Description===
The following algorithm is a generic version of the push–relabel algorithm. It is used as a proof of concept and does not contain implementation details on how to select an active vertex for the push and relabel operations. This generic version of the algorithm will terminate in {{nowrap|''O''(''V''<sup>2</sup>''E'')}}.
 
Since {{nowrapmath|''h''𝓁(''s'') {{=}} {{!}}&nbsp;''V''&nbsp;{{!}}}}, {{nowrapmath|''h''𝓁(''t'') {{=}} 0}}, and there are no paths longer than {{nowrapmath|{{!}}&nbsp;''V''&nbsp;{{!}} − 1}} in {{nowrapmath|''G''<sub>''f''</sub>''}}, in order for {{nowrapmath|''h''𝓁(''s'')}} to satisfy the valid labeling condition, ''{{mvar|s''}} must be disconnected from ''{{mvar|t''}}. At initializationinitialisation, the algorithm fulfills this requirement by creating a preflowpre-flow ''{{mvar|f''}} that saturates all out-edgesarcs of ''{{mvar|s''}}, after which {{nowrapmath|''h''𝓁(''v'') {{=}} 0}} is trivially valid for all {{nowrapmath|''v'' ∈ ''V'' \ {{(}}''s'', ''t''{{)}}}}. After initializationinitialisation, the algorithm repeatedly executes an applicable push or relabel operation until no such operations apply, at which point the preflowpre-flow has been converted into a maximum flow.
 
generic-push-relabel(G(V, E)c, s, t):
create a preflowpre-flow f that saturates all out-edgesarcs of s
'''let''' h𝓁[s] = |V|
'''let''' h𝓁[v] = 0 ∀vfor all v ∈ V \ {s}
'''while''' there is an applicable push or relabel operation '''do'''
execute the operation
 
===Correctness===
The algorithm maintains the condition that ''h''{{math|𝓁}} is a valid labeling during its execution. This can be proven true by examining the effects of the push and relabel operations on the label function ''h''{{math|𝓁}}. The relabel operation increases the label value by the associated minimum plus one which will always satisfy the {{nowrapmath|''h''𝓁(''u'') ≤ ''h''𝓁(''v'') + ''1''}} constraint. The push operation can send flow from ''{{mvar|u''}} to ''{{mvar|v''}} if {{nowrapmath|''h''𝓁(''u'') {{=}} ''h''𝓁(''v'') + ''1''}}. This may add {{nowrapmath|(''v'', ''u'')}} to {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}} and may delete {{nowrapmath|(''u'', ''v'')}} from {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}}. The addition of {{nowrapmath|(''v'', ''u'')}} to {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}} will not affect the valid labeling since {{nowrapmath|''h''𝓁(''v'') {{=}} ''h''𝓁(''u'') - ''1''}}. The deletion of {{nowrapmath|(''u'', ''v'')}} from {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}} removes the corresponding constraint since the valid labeling property {{nowrapmath|''h''𝓁(''u'') ≤ ''h''𝓁(''v'') + ''1''}} only applies to residual edgesarcs in {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}}.<ref name="goldberg88"/>
 
If a preflow ''{{mvar|f''}} and a valid labeling ''h''{{math|𝓁}} for ''{{mvar|f''}} exists then there is no augmenting path from ''{{mvar|s''}} to ''{{mvar|t''}} in the residual graph {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}}. This can be proven by contradiction based on inequalities which arise in the labeling function when supposing that an augmenting path does exist. If the algorithm terminates, then all verticesnodes in {{nowrapmath|''V'' -\ {{(}}''s'', ''t''{{)}}}} are not active. This means all {{nowrapmath|''v'' ∈ ''V'' -\ {{(}}''s'', ''t''{{)}}}} have no excess flow, and with no excess the preflow ''{{mvar|f''}} obeys the flow conservation constraint and can be considered a normal flow. This flow is the maximum flow according to the [[max-flow min-cut theorem]] since there is no augmenting path from ''{{mvar|s''}} to ''{{mvar|t''}}.<ref name="goldberg88"/>
 
Therefore, the algorithm will return the maximum flow upon termination.
 
===Time complexity===
In order to bindbound the time complexity of the algorithm, we must analyze the number of push and relabel operations which occur within the main loop. The numbers of relabel, saturating push and nonsaturating push operations are analyzed separately.
 
In the algorithm, the relabel operation can be performed at most {{nowrapmath| (2{{!}}&nbsp;''V''&nbsp;{{!}} - 1)({{!}}&nbsp;''V''&nbsp;{{!}} - 2) < 2{{!}}&nbsp;''V''&nbsp;{{!}}<sup>2</sup>}} times. This is because the labeling {{nowrapmath|''h''𝓁(''u'')}} value for any vertexnode ''u'' can never decrease, and the maximum label value is at most {{nowrapmath| 2{{!}}&nbsp;''V''&nbsp;{{!}} - 1}} for all verticesnodes. This means the relabel operation could potentially be performed {{nowrapmath| 2{{!}}&nbsp;''V''&nbsp;{{!}} - 1 }} times for all verticesnodes {{nowrapmath| ''V'' \ {{(}}''s'', ''t''{{)}}}} (i.e. {{nowrapmath|{{!}}&nbsp;''V''&nbsp;{{!}} - 2}}). This results in a bound of {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>)}} for the relabel operation.
 
Each saturating push on an admissible edgearc {{nowrapmath|(''u'', ''v'')}} removes the edgearc from {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}}. For the edgearc to be reinserted into {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}} for another saturating push, ''{{mvar|v''}} must be first be relabeled, followed by a push on edgethe arc {{nowrapmath|(''v'', ''u'')}}, then ''{{mvar|u''}} must be relabeled. In the process, {{nowrapmath|''h''𝓁(''u'')}} increases by at least two. Therefore, there are {{nowrapmath|''O''(''V'')}} saturating pushes on {{nowrapmath|(''u'', ''v'')}}, and the total number of saturating pushes is at most {{nowrapmath| 2{{!}}&nbsp;''V''&nbsp;{{!}}{{!}}&nbsp;''E''&nbsp;{{!}}}}. This results in a time bound of {{nowrapmath|''O''(''VE'')}} for the saturating push operations.
 
Bounding the number of nonsaturating pushes can be achieved via a [[Potential method|potential argument]]. We use the potential function {{nowrapmath|Φ {{=}} Σ<sub>[''u''&#x200a; &#x200a; ''V''&#x200a; &#x200a; ''ex''<sub>''f''</sub>&nbsp;(''u'')&#x200a; >&#x200a; 0]</sub> ''h''𝓁(''u'')}} (i.e. {{math|Φ}} is the sum of the heightslabels of all active verticesnodes). It is obvious that {{math|Φ}} is {{nowrapmath|{{!}}''V''{{!}}0}} initially and stays nonnegative throughout the execution of the algorithm. Both relabels and saturating pushes can increase {{math|Φ}}. However, the value of {{math|Φ}} must be equal to 0 at termination since there cannot be any remaining active verticesnodes at the end of the algorithm's execution. This means that over the execution of the algorithm, the nonsaturating pushes must make up the difference of the relabel and saturating push operations in order for {{math|Φ}} to terminate with a value of 0.
The relabel operation can increase {{math|Φ}} by at most {{nowrapmath| (2{{!}}&nbsp;''V''&nbsp;{{!}} - 1)({{!}}&nbsp;''V''&nbsp;{{!}} - 2)}}. A saturating push on {{nowrapmath|(''u'', ''v'')}} activates ''{{mvar|v''}} if it was inactive before the push, increasing {{math|Φ}} by at most {{nowrapmath| 2{{!}}&nbsp;''V''&nbsp;{{!}} - 1}}. Hence, the total contribution of all saturating pushes operations to {{math|Φ}} is at most {{nowrapmath| (2{{!}}&nbsp;''V''&nbsp;{{!}} - 1)(2{{!}}&nbsp;''V''&nbsp;{{!}}{{!}}&nbsp;''E''&nbsp;{{!}})}}. A nonsaturating push on {{nowrapmath|(''u'', ''v'')}} always deactivates ''{{mvar|u''}}, but it can also activate ''{{mvar|v''}} as in a saturating push. As a result, it decreases {{math|Φ}} by at least {{nowrapmath|''h''𝓁(''u'') − ''h''𝓁(''v'') {{=}} 1}}. Since relabels and saturating pushes increase {{math|Φ}}, the total number of nonsaturating pushes must make up the difference of {{nowrapmath| (2{{!}}&nbsp;''V''&nbsp;{{!}} - 1)({{!}}&nbsp;''V''&nbsp;{{!}} - 2) + (2{{!}}&nbsp;''V''&nbsp;{{!}} - 1)(2{{!}}&nbsp;''V''&nbsp;{{!}}{{!}}&nbsp;''E''&nbsp;{{!}}) ≤ 4{{!}}&nbsp;''V''&nbsp;{{!}}<sup>2</sup>{{!}}&nbsp;''E''&nbsp;{{!}}}}. This results in a time bound of {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>''E'')}} for the nonsaturating push operations.
 
In sum, the algorithm executes {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>)}} relabels, {{nowrapmath|''O''(''VE'')}} saturating pushes and {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>''E'')}} nonsaturating pushes. Data structures can be designed to pick and execute an applicable operation in {{nowrapmath|''O''(1)}} time. Therefore, the time complexity of the algorithm is {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>''E'')}}.<ref name="clrs26"/><ref name="goldberg88"/>
The relabel operation can increase Φ by at most {{nowrap| (2{{!}}''V''{{!}} - 1)({{!}}''V''{{!}} - 2)}}. A saturating push on {{nowrap|(''u'', ''v'')}} activates ''v'' if it was inactive before the push, increasing Φ by at most {{nowrap| 2{{!}}''V''{{!}} - 1}}. Hence, the total contribution of all saturating pushes operations to Φ is at most {{nowrap| (2{{!}}''V''{{!}} - 1)(2{{!}}''V''{{!}}{{!}}''E''{{!}})}}. A nonsaturating push on {{nowrap|(''u'', ''v'')}} always deactivates ''u'', but it can also activate ''v'' as in a saturating push. As a result, it decreases Φ by at least {{nowrap|''h''(''u'') − ''h''(''v'') {{=}} 1}}. Since relabels and saturating pushes increase Φ, the total number of nonsaturating pushes must make up the difference of {{nowrap| (2{{!}}''V''{{!}} - 1)({{!}}''V''{{!}} - 2) + (2{{!}}''V''{{!}} - 1)(2{{!}}''V''{{!}}{{!}}''E''{{!}}) ≤ 4{{!}}''V''{{!}}<sup>2</sup>{{!}}''E''{{!}}}}. This results in a time bound of {{nowrap|''O''(''V''<sup>2</sup>''E'')}} for the nonsaturating push operations.
 
In sum, the algorithm executes {{nowrap|''O''(''V''<sup>2</sup>)}} relabels, {{nowrap|''O''(''VE'')}} saturating pushes and {{nowrap|''O''(''V''<sup>2</sup>''E'')}} nonsaturating pushes. Data structures can be designed to pick and execute an applicable operation in {{nowrap|''O''(1)}} time. Therefore, the time complexity of the algorithm is {{nowrap|''O''(''V''<sup>2</sup>''E'')}}.<ref name="clrs26"/><ref name="goldberg88"/>
 
===Example===
Line 139 ⟶ 124:
{{clear}}
In the example, the {{nowrap|''h''}} and {{nowrap|''e''}} values denote the heightlabel {{math|𝓁}} and excess {{math|''x''<sub>''f''</sub>&nbsp;}}, respectively, of the vertexnode during the execution of the algorithm. Each residual graph in the example only contains the residual edgesarcs with a capacity larger than zero. Each residual graph may contain multiple iterations of the {{nowrap|''perform operation''}} loop.
{{clear}}
Line 147 ⟶ 132:
! Algorithm Operation(s) !! Residual Graph
|-
| InitializeInitialise the residual graph by setting the preflow to values 0 and initializinginitialising the labeling. || [[File:Push-Relabel Algorithm Example - Step 1.svg|Step 1|350px]]
|-
| Initial saturating push is performed across all preflow edgesarcs out of the source, {{nowrapmvar|''s''}}. || [[File:Push-Relabel Algorithm Example - Step 2.svg|Step 2|350px]]
|-
|rowspan="2"| VertexNode {{nowrapmvar|''a''}} is relabeled in order to push its excess flow towards the sink, {{nowrapmvar|''t''}}.
The excess at {{nowrapmvar|''a''}} is then pushed to {{nowrapmvar|''b''}} then {{nowrapmvar|''d''}} in two subsequent saturating pushes; which still leaves {{nowrapmvar|''a''}} with some excess.
|-
| [[File:Push-Relabel Algorithm Example - Step 3.svg|Step 3|350px]]
|-
|rowspan="2"| Once again, {{nowrapmvar|''a''}} is relabeled in order to push its excess along its last remaining positive residual (i.e. push the excess back to {{nowrapmvar|''s''}}).
The vertexnode {{nowrapmvar|''a''}} is then removed from the set of active verticesnodes.
|-
| [[File:Push-Relabel Algorithm Example - Step 4.svg|Step 4|350px]]
|-
| Relabel {{nowrapmvar|''b''}} and then push its excess to {{nowrapmvar|''t''}} and {{nowrapmvar|''c''}}. || [[File:Push-Relabel Algorithm Example - Step 5.svg|Step 5|350px]]
|-
| Relabel {{nowrapmvar|''c''}} and then push its excess to {{nowrapmvar|''d''}}. || [[File:Push-Relabel Algorithm Example - Step 6.svg|Step 6|350px]]
|-
| Relabel {{nowrapmvar|''d''}} and then push its excess to {{nowrapmvar|''t''}}. || [[File:Push-Relabel Algorithm Example - Step 7.svg|Step 7|350px]]
|-
|rowspan="2"| This leaves the vertexnode {{nowrapmvar|''b''}} as the only remaining active vertexnode, but it cannot push its excess flow towards the sink.
Relabel {{nowrapmvar|''b''}} and then push its excess towards the source, {{nowrapmvar|''s''}}, via the vertexnode {{nowrapmvar|''a''}}.
|-
| [[File:Push-Relabel Algorithm Example - Step 8.svg|Step 8|350px]]
|-
|rowspan="2"| Push the last bit of excess at {{nowrapmvar|''a''}} back to the source, {{nowrapmvar|''s''}}.
There are no remaining active verticesnodes. The algorithm terminates and returns the maximum flow of the graph (as seen above).
|-
| [[File:Push-Relabel Algorithm Example - Step 9.svg|Step 9|350px]]
|}
 
The example (but with initial flow of 0) can be run [http://www.adrian-haarbach.de/idp-graph-algorithms/implementation/maxflow-push-relabel/index_en.html?graph=6 here ] interactively.
 
==Practical implementations==
While the generic push–relabel algorithm has {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>''E'')}} time complexity, efficient implementations achieve {{nowrapmath|''O''(''V''<sup>&nbsp;3</sup>)}} or lower time complexity by enforcing appropriate rules in selecting applicable push and relabel operations. The empirical performance can be further improved by heuristics.
 
==="Current-edgearc" data structure and discharge operation===
The "current-edgearc" data structure is a mechanism for visiting the in- and out-neighbors of a vertexnode in the flow network in a static circular order. If a singly linked list of neighbors is created for a vertexnode, the data structure can be as simple as a pointer into the list that steps through the list and rewinds to the head when it runs off the end.
 
Based on the "current-edgearc" data structure, the discharge operation can be defined. A discharge operation applies on an active node and repeatedly pushes flow from the node until it becomes inactive, relabeling it as necessary to create admissible edgesarcs in the process.
 
discharge(u):
'''while''' ex<sub>f</sub>[u] > 0 '''do'''
'''if''' current-edgearc[u] has run off the end of neighbors[u] '''then'''
relabel(u)
rewind current-edgearc[u]
'''else'''
'''let''' (u, v) = current-edgearc[u]
'''if''' (u, v) is admissible '''then'''
push(u, v)
'''let''' current-edgearc[u] point to the next neighbor of u
else
Finding the next admissible edge to push on has <math>O(1)</math> [[amortized complexity]]. The current-arc pointer only moves to the next neighbor when the edge to the current neighbor is saturated or non-admissible, and neither of these two properties can change until the active node <math>u</math> is relabelled. Therefore, when the pointer runs off, there are no admissible unsaturated edges and we have to relabel the active node <math>u</math>, so having moved the pointer <math>O(V)</math> times is paid for by the <math>O(V)</math> relabel operation.<ref name="goldberg88" />
let current-edge[u] point to the next neighbor of u
 
===Active vertexnode selection rules===
Definition of the discharge operation reduces the push–relabel algorithm to repeatedly selecting an active node to discharge. Depending on the selection rule, the algorithm exhibits different time complexities. For the sake of brevity, we ignore ''{{mvar|s''}} and ''{{mvar|t''}} when referring to the verticesnodes in the following discussion.
 
====FIFO selection rule====
The [[FIFO (computing and electronics)|FIFO]] push–relabel algorithm<ref name="goldberg86"/> organizes the active verticesnodes into a queue. The initial active nodes can be inserted in arbitrary order. The algorithm always removes the vertexnode at the front of the queue for discharging. Whenever an inactive vertexnode becomes active, it is appended to the back of the queue.
 
The algorithm has {{nowrapmath|''O''(''V''<sup>&nbsp;3</sup>)}} time complexity.
 
====Relabel-to-front selection rule====
The relabel-to-front push–relabel algorithm<ref name="clrs26"/> organizes all verticesnodes into a linked list and maintains the invariant that the list is [[Topological sorting|topologically sorted]] with respect to the admissible network. The algorithm scans the list from front to back and performs a discharge operation on the current vertexnode if it is active. If the node is relabeled, it is moved to the front of the list, and the scan is restarted from the front.
 
The algorithm also has {{nowrapmath|''O''(''V''<sup>&nbsp;3</sup>)}} time complexity.
 
====Highest label selection rule====
The highest-label push–relabel algorithm<ref name="cheriyan88"/> organizes all verticesnodes into buckets indexed by their heightslabels. The algorithm always selects an active vertexnode with the largest heightlabel to discharge.
 
The algorithm has {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>{{sqrt|''E''}})}} time complexity. If the lowest-label selection rule is used instead, the time complexity becomes {{nowrapmath|''O''(''V''<sup>&nbsp;2</sup>''E'')}}.<ref name="ahuja97"/>
 
===Implementation techniques===
Although in the description of the generic push–relabel algorithm above, {{nowrapmath|''h''𝓁(''u'')}} is set to zero for each vertexnode ''u'' other than ''{{mvar|s''}} and ''{{mvar|t''}} at the beginning, it is preferable to perform a backward [[breadth-first search]] from ''{{mvar|t''}} to compute the exact heightslabels.<ref name="goldberg86"/>
 
The algorithm is typically separated into two phases. Phase one computes a maximum preflowpre-flow by discharging only active verticesnodes whose heightslabels are below ''{{mvar|n''}}. Phase two converts the maximum preflow into a maximum flow by returning excess flow that cannot reach ''{{mvar|t''}} to ''{{mvar|s''}}. It can be shown that phase two has {{nowrapmath|''O''(''VE'')}} time complexity regardless of the order of push and relabel operations and is therefore dominated by phase one. Alternatively, it can be implemented using flow decomposition.<ref name="amo93"/>
 
Heuristics are crucial to improving the empirical performance of the algorithm.<ref name="cherkassky95"/> Two commonly used heuristics are the gap heuristic and the global relabeling heuristic.<ref name="goldberg86"/><ref name="derigs89"/> The gap heuristic detects gaps in the heightlabeling function. If there is a heightlabel {{nowrap|0 < 𝓁''{{'}}'' < {{!}}&nbsp;''V''&nbsp;{{!}}}} for which there is no vertexnode ''{{mvar|u''}} such that {{nowrapmath|''h''𝓁(''u'') {{=}} 𝓁''{{'}}''}}, then any vertexnode ''{{mvar|u''}} with {{nowrapmath|𝓁''{{'}}'' < ''h''𝓁(''u'') < {{!}}&nbsp;''V''&nbsp;{{!}}}} has been disconnected from ''{{mvar|t''}} and can be relabeled to {{nowrapmath|({{!}}&nbsp;''V''&nbsp;{{!}} + 1)}} immediately. The global relabeling heuristic periodically performs backward breadth-first search from ''{{mvar|t''}} in {{nowrapmath|''G''<sub>''f''</sub>''&nbsp;}} to compute the exact heightslabels of the verticesnodes. Both heuristics skip unhelpful relabel operations, which are a bottleneck of the algorithm and contribute to the ineffectiveness of dynamic trees.<ref name="goldberg08"/>
 
==Sample implementations==
{{hidden begin|border=1px #aaa solid|titlestyle=text-align:center|title=[[C (programming language)|C]] implementation}}
<sourcesyntaxhighlight lang="c">
#include <stdlib.h>
#include <stdio.h>
Line 258 ⟶ 243:
if (seen[u] < NODES) {
int v = seen[u];
if ((C[u][v] - F[u][v] > 0) && (height[u] > height[v])) {
push(C, F, excess, u, v);
} else {
seen[u] += 1;
}
else
seen[u] += 1;
} else {
relabel(C, F, height, u);
Line 273 ⟶ 258:
int temp = A[i];
int n;
for (n = i; n > 0; n--) {
A[n] = A[n-1];
}
Line 289 ⟶ 274:
 
for (i = 0, p = 0; i < NODES; i++){
if ((i != source) && (i != sink)) {
list[p] = i;
p++;
Line 306 ⟶ 291:
discharge(C, F, excess, height, seen, u);
if (height[u] > old_height) {
moveToFront(p, list);
p = 0;
} else {
else
p += 1;
else}
}
int maxflow = 0;
Line 326 ⟶ 311:
 
void printMatrix(const int * const * M) {
int i, j;
for (i = 0; i < NODES; i++) {
for (j = 0; j < NODES; j++)
Line 343 ⟶ 328:
}
 
// Sample graph
capacities[0][1] = 2;
capacities[0][2] = 9;
Line 363 ⟶ 348:
return 0;
}
</syntaxhighlight>
</source>
{{hidden end}}
 
{{hidden begin|border=1px #aaa solid|titlestyle=text-align:center|title=[[Python (programming language)|Python]] implementation}}
<sourcesyntaxhighlight lang="python">
def relabel_to_front(C, source: int, sink: int) -> int:
n = len(C) # C is the capacity matrix
F = [[0] * n for _ in xrangerange(n)]
# residual capacity from u to v is C[u][v] - F[u][v]
 
height = [0] * n # height of node
excess = [0] * n # flow into node minus flow from node
seen = [0] * n # neighbours seen since last relabel
# node "queue"
nodelist = [i for i in xrangerange(n) if i != source and i != sink]
 
def push(u, v):
send = min(excess[u], C[u][v] - F[u][v])
F[u][v] += send
F[v][u] -= send
excess[u] -= send
excess[v] += send
 
def relabel(u):
# findFind smallest new height making a push possible,
# if such a push is possible at all.
min_height = ∞
for v in xrangerange(n):
if C[u][v] - F[u][v] > 0:
min_height = min(min_height, height[v])
height[u] = min_height + 1
 
def discharge(u):
while excess[u] > 0:
if seen[u] < n: # check next neighbour
v = seen[u]
if C[u][v] - F[u][v] > 0 and height[u] > height[v]:
push(u, v)
else:
seen[u] += 1
else: # we have checked all neighbours. must relabel
relabel(u)
seen[u] = 0
 
height[source] = n # longest path from source to sink is less than n long
excess[source] = Inf # send as much flow as possible to neighbours of source
for v in xrangerange(n):
push(source, v)
 
p = 0
while p < len(nodelist):
u = nodelist[p]
old_height = height[u]
discharge(u)
if height[u] > old_height:
nodelist.insert(0, nodelist.pop(p)) # move to front of list
p = 0 # start from front of list
else:
p += 1
 
return sum(F[source])
</syntaxhighlight>
</source>
{{hidden end}}
 
== References ==
{{reflist|2|refs=
<ref name="clrs26">{{Cite book | isbn = 978-0262032933 | title = Introduction to Algorithms | edition = 2nd | last1 = Cormen | first1 = T. H. | author-link1 = Thomas H. Cormen | year = 2001 | publisher = The MIT Press | last2 = Leiserson | first2 = C. E. | author-link2 = Charles E. Leiserson | last3 = Rivest | first3 = R. L. | author-link3 = Ron Rivest | last4 = Stein | first4 = C. | author-link4 = Clifford Stein | chapter = §26 Maximum flow | pages = [https://archive.org/details/introductiontoal00corm_691/page/n665 643]–698| title-link = Introduction to Algorithms }}</ref>
<ref name="clrs26">{{cite isbn/978026203293|chapter=§26 Maximum flow|pages=643–698}}</ref>
<ref name="goldberg86">{{cite book|doi=10.1145/12130.12144|chapter=A new approach to the maximum flow problem|title=Proceedings of the eighteenth annual ACM symposium on Theory of computing - STOC '86|pages=136|year=1986|last1=Goldberg|first1=A V|last2=Tarjan|first2=R E|isbn=0897911938978-0897911931|s2cid=14492800}}</ref>
<ref name="goldberg88">{{cite journal|doi=10.1145/48014.61051|title=A new approach to the maximum-flow problem|journal=Journal of the ACM|volume=35|issue=4|pages=921|year=1988|last1=Goldberg|first1=Andrew V.|last2=Tarjan|first2=Robert E.|s2cid=52152408|doi-access=free}}</ref>
<ref name="sv82">{{cite journal|doi=10.1016/0196-6774(82)90013-X|title=An O(n2log n) parallel max-flow algorithm|journal=Journal of Algorithms|volume=3|issue=2|pages=128–146|year=1982|last1=Shiloach|first1=Yossi|last2=Vishkin|first2=Uzi}}</ref>
<ref name="cheriyan88">{{cite book|doi=10.1007/3-540-50517-2_69|chapter=Analysis of preflow push algorithms for maximum network flow|title=Foundations of Software Technology and Theoretical Computer Science|volume=338|pages=30|series=Lecture Notes in Computer Science|year=1988|last1=Cheriyan|first1=J.|last2=Maheshwari|first2=S. N.|isbn=978-3-540-50517-4}}</ref>
<ref name="derigs89">{{cite journal|doi=10.1007/BF01415937|title=Implementing Goldberg's max-flow-algorithm ? A computational investigation|journal=ZOR Zeitschrift für Operations Research Methods and Models of Operations Research|volume=33|issue=6|pages=383|year=1989|last1=Derigs|first1=U.|last2=Meier|first2=W.|s2cid=39730584}}</ref>
<ref name="ahuja91">{{cite journal|doi=10.1002/1520-6750(199106)38:3<413::AID-NAV3220380310>3.0.CO;2-J|title=Distance-directed augmenting path algorithms for maximum flow and parametric maximum flow problems|journal=Naval Research Logistics|volume=38|issue=3|pages=413|year=1991|last1=Ahuja|first1=Ravindra K.|last2=Orlin|first2=James B.|citeseerx=10.1.1.297.5698}}</ref>
<ref name="amo93">{{Cite book | isbn = 013617549X978-0136175490 | title = Network Flows: Theory, Algorithms, and Applications | edition = 1st | last1 = Ahuja | first1 = R. K. | year = 1993 | publisher = Prentice Hall | last2 = Magnanti | first2 = T. L. | last3 = Orlin | first3 = J. B. | pages = }}</ref>
<ref name="cherkassky95">{{cite book|doi=10.1007/3-540-59408-6_49|chapter=On implementing push-relabel method for the maximum flow problem|title=Integer Programming and Combinatorial Optimization|volume=920|pages=157|series=Lecture Notes in Computer Science|year=1995|last1=Cherkassky|first1=Boris V.|last2=Goldberg|first2=Andrew V.|isbn=978-3-540-59408-6|citeseerx=10.1.1.150.3609}}</ref>
<ref name="ahuja97">{{cite journal|doi=10.1016/S0377-2217(96)00269-X|title=Computational investigations of maximum flow algorithms|journal=European Journal of Operational Research|volume=97|issue=3|pages=509|year=1997|last1=Ahuja|first1=Ravindra K.|last2=Kodialam|first2=Murali|last3=Mishra|first3=Ajay K.|last4=Orlin|first4=James B.|citeseerx=10.1.1.297.2945}}</ref>
<ref name="goldberg97">{{cite journal|doi=10.1006/jagm.1995.0805|title=An Efficient Implementation of a Scaling Minimum-Cost Flow Algorithm|journal=Journal of Algorithms|volume=22|pages=11–29|year=1997|last1=Goldberg|first1=Andrew V}}</ref>
<ref name="goldberg08">{{cite book|doi=10.1007/978-3-540-87744-8_39|chapter=The Partial Augment–Relabel Algorithm for the Maximum Flow Problem|title=Algorithms - ESA 2008|volume=5193|pages=466466–477|series=Lecture Notes in Computer Science|year=2008|last1=Goldberg|first1=Andrew V.|isbn=978-3-540-87743-1|citeseerx=10.1.1.150.5103}}</ref>
<ref name="goldberg2014">{{cite journal|doi=10.1145/2628036|title=Efficient maximum flow algorithms|journal=Communications of the ACM|volume=57|issue=8|pages=82|year=2014|last1=Goldberg|first1=Andrew V.|last2=Tarjan|first2=Robert E.|s2cid=17014879}}</ref>
}}
 
{{optimization algorithms|combinatorial|state=expanded}}
{{DEFAULTSORT:Push-relabel maximum flow algorithm}}
 
[[Category:Network flow problem]]
[[Category:Graph algorithms]]
[[Category:Articles with example C code]]
[[Category:Articles with example Python (programming language) code]]