|
| 1 | +## Maximum Flow |
| 2 | + |
| 3 | +The Maximum Flow problem is about finding the maximum flow through a directed graph, from one node in the graph to another. |
| 4 | + |
| 5 | +The problem can be used to model a wide variety of real-world situations, such as transportation systems, communication networks, and resource allocation. |
| 6 | + |
| 7 | +### Flow network |
| 8 | + |
| 9 | +A directed graph is called a flow network when each edge has a nonnegative **capacity** and each edge receives a flow. Furthermore, two nodes are distinguished, one as the **source** *s* where the flow comes from and the other as the **sink** *t* where the flow ends up. |
| 10 | + |
| 11 | + |
| 12 | + |
| 13 | +The amount of incoming flow on an edge cannot exceed the capacity of the edge. For all vertices except *s* and *t*, there is a **conservation** of flow, what means that the same amount of flow that goes into a vertex must also come out of it. |
| 14 | + |
| 15 | +In Figure *@maxFlow@*, we see a flow network with maximal flow $5$. The edges are labelled with *flow/capacity*. |
| 16 | + |
| 17 | +The **residual** capacity is defined as $capacity - flow$. Flow, capacity and residual capacity are accessed in our class `AINetworkFlowEdge`. |
| 18 | + |
| 19 | +### A naive greedy approach |
| 20 | + |
| 21 | +A naive greedy approach to find the maximum flow consists of the following steps: |
| 22 | + |
| 23 | +``` |
| 24 | +1. Start with zero flow on all edges. |
| 25 | +2. Find an augmenting path where more flow can be sent. |
| 26 | +3. Do a bottleneck calculation to find out how much flow can be maximally sent through that augmented path. |
| 27 | +4. Increase the flow found from the bottleneck calculation for each edge in the augmenting path. |
| 28 | +5. Repeat steps 2-4 until max flow is found. This happens when a new augmenting path can no longer be found. |
| 29 | +``` |
| 30 | + |
| 31 | +An **augmenting path** is a path from source to sink where some edge has not reached its full capacity, i.e. $flow < capacity$ for at least one edge in the path. It takes $O(E)$ time to find an augmenting path using BFS (breadth-first search) or DFS (depth-first search). |
| 32 | + |
| 33 | +The **bottleneck** is the minimum residual capacity in the augmenting path. Note that after step 4, the path is not anymore among the remaining augmenting paths to be found because the flow of every bottleneck edge in the path is augmented to the full capacity. |
| 34 | + |
| 35 | + |
| 36 | + |
| 37 | +As suspected, the naive greedy approach does not always yield the maximum flow. We see in Figure *@greedyCounterexample@* a counterexample. Suppose that the greedy algorithm initially finds the augmenting path $s\rightarrow v\rightarrow w\rightarrow t$. After that, it would not find any more augmenting path and keep the non-optimal flow value $3$ (top graph in Figure *@greedyCounterexample@*), missing the correct maximum flow value $5$ (bottom graph in Figure *@greedyCounterexample@*). |
| 38 | + |
| 39 | +### Ford-Fulkerson method |
| 40 | + |
| 41 | +Fortunately, there is an enhancement of the naive greedy approach that leads to the optimal maximum flow: the Ford-Fulkerson method. The method applies the greedy approach explained before not on the original graph but on the so-called residual graph. |
| 42 | + |
| 43 | +The **residual graph** is formed by adding for each edge in the original directed graph a corresponding **reverse edge** relating the edge nodes in reverse direction to complete a cycle between the two nodes. For each reverse edge, its residual capacity is always the flow of the corresponding original edge. Thanks to the residual graph, that has the double of edges than the original, the greedy algorithm doesn't stuck too early and can go on finding more augmenting paths. |
| 44 | + |
| 45 | +### Edmonds-Karp algorithm |
| 46 | + |
| 47 | +The Ford-Fulkerson procedure is often called method instead of algorithm because it doesn't specify how to find an augmenting path. The Edmonds-Karp algorithm implements the Ford-Fulkerson method explicitly using BFS to find the augmenting paths. BFS is better than DFS in this context and contributes to the time complexity of $O(V*E^2)$ for the Edmonds-Karp algorithm. |
| 48 | + |
| 49 | +In `AIEdmondsKarp`, the `run` method formulates the greedy steps like this: |
| 50 | + |
| 51 | +``` |
| 52 | +AIEdmondsKarp >> run |
| 53 | + "Execute the Edmonds-Karp algorithm and return the maximum flow value" |
| 54 | +
|
| 55 | + | maxFlow pathFlow | |
| 56 | + maxFlow := 0. |
| 57 | +
|
| 58 | + [ self findAugmentingPathBFS ] whileTrue: [ |
| 59 | + pathFlow := self findBottleneckCapacity. |
| 60 | + self updateFlowAlongPath: pathFlow. |
| 61 | + maxFlow := maxFlow + pathFlow ]. |
| 62 | +
|
| 63 | + ^ maxFlow |
| 64 | +``` |
| 65 | + |
| 66 | +In the following method `updateFlowAlongPath:`, we see how it updates the current augmenting path by |
| 67 | + |
| 68 | +- increasing for each of its edges the flow by the `flowValue` found in `findBottleneckCapacity` and |
| 69 | + |
| 70 | +- adjusting the corresponding reverse edge flow to maintain the dependence between original (forward) edge and reverse edge. |
| 71 | + |
| 72 | +``` |
| 73 | +AIEdmondsKarp >> updateFlowAlongPath: flowValue |
| 74 | + "Update the flow along the augmenting path found by BFS" |
| 75 | +
|
| 76 | + | current | |
| 77 | + current := sink. |
| 78 | +
|
| 79 | + [ current ~= source ] whileTrue: [ |
| 80 | + | edge reverseEdge | |
| 81 | + edge := parent at: current. |
| 82 | +
|
| 83 | + "Update forward edge flow" |
| 84 | + edge flow: edge flow + flowValue. |
| 85 | +
|
| 86 | + "Update reverse edge flow" |
| 87 | + reverseEdge := self findReverseEdge: edge. |
| 88 | + reverseEdge flow: reverseEdge flow - flowValue. |
| 89 | +
|
| 90 | + current := edge from ] |
| 91 | +``` |
| 92 | + |
| 93 | +### Dinic's algorithm |
| 94 | + |
| 95 | +Dinic's (Dinitz's) algorithm is a refinement of the Ford-Fulkerson method, designed to improve its efficiency by using a combination of BFS and DFS to speed up the process of finding augmenting paths. |
| 96 | + |
| 97 | +Dinic's algorithm also operates on a residual graph, that is generated, in the same way as for Edmonds-Karp algorithm, at the time of entering the original graph edges: |
| 98 | + |
| 99 | +``` |
| 100 | +AIDinic >> edges: aCollection from: source to: target capacity: capacityFunction |
| 101 | +
|
| 102 | + | edge edgeRev | |
| 103 | + aCollection do: [ :eModel | |
| 104 | + edge := self addEdge: eModel from: source to: target. |
| 105 | + edge ifNotNil: [ edge capacity: (capacityFunction value: eModel) ]. |
| 106 | + edgeRev := self addEdge: eModel from: target to: source. |
| 107 | + edgeRev ifNotNil: [ edgeRev capacity: 0 ] ] |
| 108 | +``` |
| 109 | + |
| 110 | +#### Main steps |
| 111 | + |
| 112 | +The main steps of Dinic's algorithm involve constructing a level graph, performing blocking flow augmentations, and repeating these phases until no more augmenting paths can be found: |
| 113 | + |
| 114 | +``` |
| 115 | +1. Level graph construction (uses BFS) |
| 116 | +2. Blocking flow augmentation (uses DFS) |
| 117 | +3. Repeat: The process is repeated until no more augmenting paths can be found in the level graph. |
| 118 | +``` |
| 119 | + |
| 120 | +The graph nodes class for Dinic's algorithm is `AIDinicNode`, where the values `level` (useful for level graph construction and for finding a blocking flow) and `currentIndex` (useful for finding a blocking flow) can be accessed. |
| 121 | + |
| 122 | +Now, we outline to some extent both main steps: the level graph construction and the blocking flow augmentation. |
| 123 | + |
| 124 | +#### Level graph construction |
| 125 | + |
| 126 | +While the Ford-Fulkerson method uses a sequence of augmenting paths to update the flow in a network, Dinic's algorithm works in phases, using level graphs to find augmenting paths in a more structured manner, significantly reducing the number of iterations. |
| 127 | + |
| 128 | +In Edmond's Karp algorithm, we use BFS to find an augmenting path and send flow across this path, whereas in Dinic's algorithm, we use BFS to check if more flow is possible and to construct a level graph. |
| 129 | + |
| 130 | +The **level graph** is a subgraph of the original flow network, where edges only exist from vertices of a higher level to those of a lower level. This is used to speed up the process of finding augmenting paths. The level graph is basically mantained at each iteration by the following method `bfs`. |
| 131 | + |
| 132 | +``` |
| 133 | +AIDinic >> bfs |
| 134 | + "This method uses bfs on the residual graph to construct a level graph. |
| 135 | + The level graph assigns levels or distances to each node, indicating the shortest path from the source. |
| 136 | + The returnBool boolean indicates if there exists and augmenting path (path from source to sink) in the residual level graph." |
| 137 | +
|
| 138 | + | node ind returnBool | |
| 139 | + [ queue isNotEmpty ] whileTrue: [ |
| 140 | + node := queue removeFirst. |
| 141 | + ind := adjList at: node. |
| 142 | + ind do: [ :i | |
| 143 | + | e n | |
| 144 | + e := edges at: i. |
| 145 | + n := e to. |
| 146 | + e residualCapacity >= 1 & (n level = -1) ifTrue: [ |
| 147 | + n level: node level + 1. |
| 148 | + queue addLast: n ] ] ]. |
| 149 | + returnBool := sink level == -1. |
| 150 | + ^ returnBool |
| 151 | +``` |
| 152 | + |
| 153 | +#### Blocking flow augmentation |
| 154 | + |
| 155 | +After having reconstructed the level graph, the next step uses DFS |
| 156 | + |
| 157 | +- to find a new **blocking flow**, a flow configuration where no more flow can be pushed from the source to the sink in the current level graph without violating capacity constraints and |
| 158 | + |
| 159 | +- to push the found maximum possible flow through the network, augmenting the flow along the paths in the level graph. |
| 160 | + |
| 161 | +#### Complexity |
| 162 | + |
| 163 | +Dinic's algorithm's ($O(V^2*E)$) complexity is a theoretical upper bound. In practice, it often performs much better, sometimes approaching O($V*E* log V$) or even better for certain graph classes or with the use of advanced data structures like dynamic trees. |
| 164 | + |
| 165 | +### Conclusion |
| 166 | + |
| 167 | +For most problems, greedy algorithms do not generally produce the best-possible solution. But it is still worth trying them, because the ways in which greedy algorithms break often yields insights that lead to better algorithms. |
| 168 | + |
| 169 | +On the one hand, Edmond-Karps algorithm is easier to understand and implement than Dinic's algorithm. On the other hand, Dinic's algorithm ($O(V^2*E)$) is generally faster than Edmonds-Karp algorithm ($O(V*E^2)$), especially on larger dense graphs. |
0 commit comments