|
| 1 | +""" |
| 2 | +Dinic's Algorithm for Maximum Flow Problem. |
| 3 | +Refined by Even and Itai for efficient blocking flow computation. |
| 4 | +
|
| 5 | +Description: |
| 6 | + The algorithm computes the maximum flow in a flow network. |
| 7 | + It constructs a level graph using BFS and then finds a blocking flow |
| 8 | + using DFS (incorporating the logic of Advance and Retreat). |
| 9 | +
|
| 10 | + Time Complexity: O(V^2 * E), or O(E * sqrt(V)) for unit networks. |
| 11 | +
|
| 12 | +Reference: |
| 13 | + - E. A. Dinic, "Algorithm for solution of a problem of maximum flow...", 1970. |
| 14 | + - S. Even and A. Itai, "Theoretical improvements in algorithmic efficiency...", |
| 15 | + 1976. |
| 16 | + - https://en.wikipedia.org/wiki/Dinic%27s_algorithm |
| 17 | +""" |
| 18 | + |
| 19 | + |
| 20 | +class Dinic: |
| 21 | + def __init__(self, n: int): |
| 22 | + """ |
| 23 | + Initialize the Dinic algorithm with n nodes. |
| 24 | + Nodes are 0-indexed. |
| 25 | + """ |
| 26 | + self.n = n |
| 27 | + self.graph = [[] for _ in range(n)] |
| 28 | + self.level = [] |
| 29 | + |
| 30 | + def add_edge(self, u: int, v: int, capacity: int) -> None: |
| 31 | + """ |
| 32 | + Adds a directed edge with a capacity. |
| 33 | + Note: Stores indices to handle the residual edges efficiently. |
| 34 | + """ |
| 35 | + # Forward edge: [v, capacity, index_of_reverse_edge] |
| 36 | + self.graph[u].append([v, capacity, len(self.graph[v])]) |
| 37 | + # Backward edge: [u, 0, index_of_forward_edge] |
| 38 | + self.graph[v].append([u, 0, len(self.graph[u]) - 1]) |
| 39 | + |
| 40 | + def bfs(self, source: int, sink: int) -> bool: |
| 41 | + """ |
| 42 | + Builds the Level Graph (L_G) using BFS. |
| 43 | + Corresponds to the INITIALIZE step in the Even-Itai refinement. |
| 44 | + Returns True if the sink is reachable, False otherwise. |
| 45 | + """ |
| 46 | + self.level = [-1] * self.n |
| 47 | + self.level[source] = 0 |
| 48 | + # using list as queue to avoid importing collections.deque |
| 49 | + queue = [source] |
| 50 | + |
| 51 | + while queue: |
| 52 | + u = queue.pop(0) |
| 53 | + for v, cap, _ in self.graph[u]: |
| 54 | + if cap > 0 and self.level[v] < 0: |
| 55 | + self.level[v] = self.level[u] + 1 |
| 56 | + queue.append(v) |
| 57 | + |
| 58 | + return self.level[sink] >= 0 |
| 59 | + |
| 60 | + def dfs(self, u: int, sink: int, flow: int, ptr: list) -> int: |
| 61 | + """ |
| 62 | + Finds a blocking flow in the Level Graph. |
| 63 | + Combines the ADVANCE and RETREAT steps. |
| 64 | +
|
| 65 | + Args: |
| 66 | + u: Current node. |
| 67 | + sink: Target node. |
| 68 | + flow: Current flow bottleneck. |
| 69 | + ptr: Current arc pointers (to implement 'Remove saturated edges'). |
| 70 | +
|
| 71 | + Returns: |
| 72 | + The amount of flow pushed. |
| 73 | + """ |
| 74 | + if u == sink or flow == 0: |
| 75 | + return flow |
| 76 | + |
| 77 | + for i in range(ptr[u], len(self.graph[u])): |
| 78 | + # 'ptr[u] = i' updates the current edge pointer (pruning L_G) |
| 79 | + # This is the "Current Arc Optimization" |
| 80 | + ptr[u] = i |
| 81 | + v, cap, rev_idx = self.graph[u][i] |
| 82 | + |
| 83 | + # Condition: Edge exists in L_G (level[v] == level[u] + 1) and has capacity |
| 84 | + if self.level[v] == self.level[u] + 1 and cap > 0: |
| 85 | + # ADVANCE step: recurse to v |
| 86 | + pushed = self.dfs(v, sink, min(flow, cap), ptr) |
| 87 | + |
| 88 | + if pushed > 0: |
| 89 | + # AUGMENT step: Update residual capacities |
| 90 | + self.graph[u][i][1] -= pushed |
| 91 | + self.graph[v][rev_idx][1] += pushed |
| 92 | + return pushed |
| 93 | + |
| 94 | + # RETREAT step: If no flow can be pushed, this node is dead for this phase. |
| 95 | + return 0 |
| 96 | + |
| 97 | + def max_flow(self, source: int, sink: int) -> int: |
| 98 | + """ |
| 99 | + Computes the maximum flow from source to sink. |
| 100 | + """ |
| 101 | + max_f = 0 |
| 102 | + # While we can build a Level Graph (source can reach sink) |
| 103 | + while self.bfs(source, sink): |
| 104 | + # ptr (pointer) array stores the index of the next edge to explore. |
| 105 | + # This implements the "Delete v" optimization efficiently |
| 106 | + ptr = [0] * self.n |
| 107 | + while True: |
| 108 | + pushed = self.dfs(source, sink, float("inf"), ptr) |
| 109 | + if pushed == 0: |
| 110 | + break |
| 111 | + max_f += pushed |
| 112 | + return max_f |
| 113 | + |
| 114 | + |
| 115 | +if __name__ == "__main__": |
| 116 | + |
| 117 | + dn = Dinic(6) |
| 118 | + edges = [ |
| 119 | + (0, 1, 16), |
| 120 | + (0, 2, 13), |
| 121 | + (1, 2, 10), |
| 122 | + (1, 3, 12), |
| 123 | + (2, 1, 4), |
| 124 | + (2, 4, 14), |
| 125 | + (3, 2, 9), |
| 126 | + (3, 5, 20), |
| 127 | + (4, 3, 7), |
| 128 | + (4, 5, 4), |
| 129 | + ] |
| 130 | + for u, v, c in edges: |
| 131 | + dn.add_edge(u, v, c) |
| 132 | + |
| 133 | + print(f"Maximum Flow: {dn.max_flow(0, 5)}") |
0 commit comments