-
-
Notifications
You must be signed in to change notification settings - Fork 51.1k
Expand file tree
/
Copy pathpush_relabel.py
More file actions
162 lines (135 loc) · 5.85 KB
/
Copy pathpush_relabel.py
File metadata and controls
162 lines (135 loc) · 5.85 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
"""
Push-relabel (Goldberg-Tarjan) algorithm for the maximum-flow problem.
The push-relabel method takes a very different approach from the augmenting-path
algorithms in this directory (``ford_fulkerson.py`` builds up a valid flow one
path at a time). Instead it works with a *preflow*, in which a vertex may
temporarily receive more flow than it sends out. Each active vertex either
*pushes* its excess towards a neighbour that is one level lower, or is *relabeled*
to a higher level so that a push becomes possible. When no vertex other than the
source and sink has excess, the preflow has become a maximum flow.
Using the highest-label selection rule (always discharge an active vertex whose
label is largest) this implementation runs in O(V^2 * sqrt(E)) time, which beats
the augmenting-path methods on dense graphs.
Reference: https://en.wikipedia.org/wiki/Push%E2%80%93relabel_maximum_flow_algorithm
"""
from __future__ import annotations
class PushRelabel:
"""
Maximum flow in a directed graph with non-negative integer capacities.
Add edges with :meth:`add_edge`, then call :meth:`max_flow`.
>>> g = PushRelabel(6)
>>> capacities = {
... (0, 1): 16, (0, 2): 13, (1, 2): 10, (1, 3): 12,
... (2, 1): 4, (2, 4): 14, (3, 2): 9, (3, 5): 20,
... (4, 3): 7, (4, 5): 4,
... }
>>> for (u, v), cap in capacities.items():
... g.add_edge(u, v, cap)
>>> g.max_flow(0, 5)
23
It agrees with the classic four-vertex example:
>>> h = PushRelabel(4)
>>> for (u, v), cap in {(0, 1): 3, (0, 2): 2, (1, 2): 5,
... (1, 3): 2, (2, 3): 3}.items():
... h.add_edge(u, v, cap)
>>> h.max_flow(0, 3)
5
Parallel edges add up, and a disconnected sink gives zero flow:
>>> p = PushRelabel(2)
>>> p.add_edge(0, 1, 3)
>>> p.add_edge(0, 1, 5)
>>> p.max_flow(0, 1)
8
>>> PushRelabel(3).max_flow(0, 2)
0
"""
def __init__(self, vertices: int) -> None:
if vertices <= 0:
raise ValueError("number of vertices must be positive")
self.size = vertices
self.graph: list[list[int]] = [[] for _ in range(vertices)]
# Each edge is stored as [destination, residual_capacity].
self.edges: list[list[int]] = []
def add_edge(self, source: int, destination: int, capacity: int) -> None:
"""
Add a directed edge ``source -> destination`` with the given capacity.
>>> g = PushRelabel(2)
>>> g.add_edge(0, 1, -1)
Traceback (most recent call last):
...
ValueError: capacity must be non-negative
>>> g.add_edge(2, 0, 1)
Traceback (most recent call last):
...
ValueError: vertex out of range
"""
if capacity < 0:
raise ValueError("capacity must be non-negative")
if not (0 <= source < self.size and 0 <= destination < self.size):
raise ValueError("vertex out of range")
self.graph[source].append(len(self.edges))
self.edges.append([destination, capacity])
self.graph[destination].append(len(self.edges))
self.edges.append([source, 0]) # reverse edge starts saturated
def max_flow(self, source: int, sink: int) -> int:
"""
Return the maximum flow from ``source`` to ``sink``.
>>> PushRelabel(2).max_flow(0, 0)
Traceback (most recent call last):
...
ValueError: source and sink must be different
"""
if not (0 <= source < self.size and 0 <= sink < self.size):
raise ValueError("vertex out of range")
if source == sink:
raise ValueError("source and sink must be different")
height = [0] * self.size
excess = [0] * self.size
height[source] = self.size
# Saturate every edge leaving the source to create the initial preflow.
for edge_index in self.graph[source]:
destination, residual = self.edges[edge_index]
if residual > 0:
self.edges[edge_index][1] -= residual
self.edges[edge_index ^ 1][1] += residual
excess[destination] += residual
excess[source] -= residual
active = [
v for v in range(self.size) if v not in (source, sink) and excess[v] > 0
]
while active:
u = max(active, key=lambda v: height[v])
if not self._discharge(u, height):
# Relabel: lift u just above its lowest usable neighbour.
min_height = min(
height[self.edges[i][0]]
for i in self.graph[u]
if self.edges[i][1] > 0
)
height[u] = min_height + 1
self._apply_pushes(u, height, excess)
active = [
v for v in range(self.size) if v not in (source, sink) and excess[v] > 0
]
return excess[sink]
def _discharge(self, u: int, height: list[int]) -> bool:
"""Return ``True`` if ``u`` has at least one admissible outgoing edge."""
return any(
self.edges[i][1] > 0 and height[self.edges[i][0]] == height[u] - 1
for i in self.graph[u]
)
def _apply_pushes(self, u: int, height: list[int], excess: list[int]) -> None:
"""Push as much excess as possible from ``u`` along admissible edges."""
for edge_index in self.graph[u]:
if excess[u] == 0:
break
destination, residual = self.edges[edge_index]
if residual > 0 and height[u] == height[destination] + 1:
delta = min(excess[u], residual)
self.edges[edge_index][1] -= delta
self.edges[edge_index ^ 1][1] += delta
excess[u] -= delta
excess[destination] += delta
if __name__ == "__main__":
from doctest import testmod
testmod()