Algorithms for Max-Flow Consider a network with given upper bounds for the capacities of the arcs, and one entry and one exit node. The max-flow problem consists in finding a maximal flow through the network from entry node to exit node, that satisfies all capacity bounds. Example:
A simple way to find a large flow from entry to exit is to select paths from entry to exit and to saturate these paths with a maximal flow. For example: The path s → 1 → 2 → t can accomodate a maximum of 2 units. Through s → 1 → t there is room for 3 units, and finally the path s → 2 → t can take 2 units.
Now all possible paths are saturated, there is no extra flow possible. The maximal flow seems to be 7 units, but this is not the case, because another solution is to let 5 units flow over s → 1 → t, and 4 units over s → 2 → t, which yields a flow of 9 units.
Max-flow min-cut theorem In a max-flow network we can draw a “wall” through arcs such that the entry and the exit node are on opposite sides. Now any flow from entry to exit node should cross this wall (=cut). If we add all capacity bounds of arcs that cross this cut in the right direction (from entry to exit) we obtain am upper bound for the total flow
There are 2n-2 possible cuts in a network with n nodes, because for every node we can decide on which side of the cut it lies. The smallest capacity for all these cuts is a sharp upper bound for the maximum flow. In fact, it gives the value of the maximum flow: Max-flow min-cut theorem: In every network there exists a flow (the max-flow) for which the amount which flows is equal to the total capacity of the smallest cut in the network (min-cut).
Example:
In this example there are 4 possible cuts which provide the following upper bounds: 12, 16, 9, 11. The smallest upper bound is 9 (min-cut). There is a flow with this value: 5 units over s → 1 → t, and 4 units over s → 2 → t (Max-flow).
Algoritme van Ford en Fulkerson The Ford-Fulkerson algorithm is based on augmenting flows over possible paths from entry to exit node. We saw already that this may not work:
All paths in this network are now saturated, so it seems that no increase is possible. However, arc 1 → 2 carries a flow of 2 units, which can be decreased! Formulated differently: Arc 2 → 1 is also possible and has capacity 2. This means that a flow of 2 units over s → 2 → 1 → t is still possible, which increased the flow from 7 to 9, which is maximal. To formalizethis approach we define a residual network for a network with a flow in the following way: every arc is replaced by two arcs in both directions, For each arc a capacity bound indicates how much flow is still possible.
For the example this gives the following picture:
The path s → 1 → 2 → t can accommodate a maximum of 2 units. Then s → 1 → t allows 3 units to flow, and finally the path s → 2 → t can take 2 units. Now it is immediately clear that s → 2 → 1 → t still has room for 2 units
The solution is obtained by taking the capacities in the residual network in the arcs that are oposite to arcs in the original network. This is the algorithm of Ford and Fulkerson (1956): Start with a feasible flow (for instance zero flow 0). while there is a path that can accommodate flow do Choose a path that can hold flow Let a maximal flow o over this path Adapt the residual graph od Se also the applet at: http://www-b2.is.tokushimau.ac.jp/~ikeda/suuri/maxflow/Maxflow.shtml
0.1 Convergentieproblemen in Ford-Fulkerson Het algoritme van Ford-Fulkerson is zeer geschikt om met de hand kleine voorbeeldjes op te lossen. Uiteindelijk is het natuurlijk bedoeld om grotere problemen op een computer aan te pakken. Dat betekent dat de stap “zoek een pad van begin- naar eindpunt” ook geautomatiseerd moet worden. In principe levert dat een zoektocht op via knopen over takken, waarbij met backtracking een geschikt pad moet worden gevonden. Hoewel het algoritme in de praktijk best goed voldoet zijn er toch een aantal problemen. De eindigheid van het algoritme is duidelijk als alle capaciteiten geheeltallig zijn. Bij elke stap in het proces treedt namelijk een verbetering op en wordt de totale stroom een geheel aantal eenheden groter. Omdat de kleinste snede en dus de maximale stroom ook geheeltallig is, bereikt het algoritme deze waarde in een eindig aantal stappen. Dit argument werkt niet als de capaciteiten geen rationale verhoudingen hebben. Het algoritme hoeft dan ook niet eindig te zijn. Wel convergeert het proces natuurlijk, omdat de stroom na elke iteratie groter wordt en er een bovengrens is. Helaas hoeft deze limietstroom niet de maximale stroom in het netwerk te zijn. Een ander probleem is, dat er bij veel strategieën om tot een mogelijke stroom van beginnaar eindpunt te komen voorbeelden te verzinnen zijn waarvoor het Ford-Fulkerson algoritme erg veel stappen doet voor het vinden van de beste oplossing. Als voorbeeld bekijken we het onderstaande netwerk.
Als strategie om een toelaatbaar pad te bepalen kiezen we backtracking, waarbij om de andere iteratie eerst de knopen met de laagste, resp. hoogste index worden afgezocht. De capaciteiten op de takken zijn allemaal M, een erg groot getal, behalve op de tak 3 → 4, daar is de capaciteit 1. Je ziet eenvoudig de beste oplossing voor dit probleem: Er moet een stroom M door de bovenste takken lopen en ook een stroom M door de onderste takken. Dat deze stroom van 2M maximaal is zie je met behulp van een snede. Het algoritme van Ford-Fulkerson doet met de gekozen strategie het volgende. Eerst wordt een toelaatbaar pad gezocht met minimale indices. Dat betekent dat er vanuit 1 wordt gekozen voor 1, vervolgens voor 3 (ligt vast), dan voor 4, vervolgens voor 2 en dan naar t. Over het pad ns → n1 → n3 → n4 → n2 → t kan maximaal 1 eenheid lopen. De tweede iteratie kiest de hoogste indices en levert het pad ns → n6 → n4 → n3 → n5 → nt. Ook over dit pad kan 1 eenheid stromen. Het is duidelijk wat er gebeurt: Bij elke iteratie wordt de stroom met 1 eenheid verhoogd. Dit betekent dat het 2M iteraties duurt voordat je de maximale stroom hebt gevonden. Dat staat in schril contrast met de twee iteraties die nodig zijn als je de paden geschikt kiest. Om deze convergentieproblemen te vermijden werd door Edmonds en Karp (1972) voorgesteld om altijd het pad met de grootst mogelijk stroom op te zoeken, in plaats van de eerste de beste. Dit brengt natuurlijk wel meer rekenwerk met zich mee. Later (Gabow 1985 en Ahuja/Orlin, 1991) werd aangetoond dat het niet noodzakelijk is om de grootst mogelijke stroom op te zoeken, als de stroom maar “groot genoeg” is in een of andere zin. Het rekenwerk voor het zoeken van een geschikt pad wordt hierdoor minder. Voor gehele capaciteits-
grenzen op het netwerk is de rekentijd van de orde O(n4log U), waarbij n het aantal knopen is en U de grootste capaciteitsgrens in het netwerk.
0.2 Algoritme van Karzanov Het idee van het algoritme van Karzanov is dat je, in plaats van één pad per keer te gebruiken, zoals bij Ford-Fulkerson, ook de stroom op meer paden tegelijk kunt aanpassen. We zullen het algoritme bekijken aan de hand van een simpel voorbeeld:
De minimale snede van dit netwerk ligt om knoop nt, en heeft een waarde van 3 + 5 =8. Er is ook eenvoudig een maximale stroom 8 te maken. Een iteratie uit het algoritme van Karzanov bestaat telkens uit de volgende drie stappen: 1. Maak een zogenaamd gelaagd netwerk. Dat zijn een aantal paden die van beginpunt naar eindpunt lopen en waarover de stroom nog kan worden aangevuld. Dit werkt als volgt: Eerst verdeel je het netwerk in lagen. Laag 0 bevat de beginknoop. Laag k bevat de knopen die via k takken van het residuele netwerk (takken waarover nog stroom kan lopen) verbonden zijn met de beginknoop. Uit het residuele netwerk worden alle takken weggelaten die niet naar een volgende laag lopen, en ten slotte worden alle takken en knopen weggelaten die niet op een pad van begin- naar eindpunt liggen. 2. “Advance”: Loop de paden van begin- naar eindpunt af en laat er per tak de maximale hoeveelheid stroom doorlopen. Alles wat er in een knoop komt mag door, tenzij het beperkt wordt door de capaciteit van de uitgaande tak. Er is soms een vrijheid hoe je de stroom over de uitgaande takken verdeelt. Er ontstaat een zogenaamde “preflow”. Dat is nog geen gewone stroom, omdat er in de knopen geen balans hoeft te zijn. Er kan meer ingaan dan er uitkomt. Dat is ook meteen de definitie van een preflow: Een toewijzing van stroomwaarden aan de takken zodanig dat in elke knoop minstens evenveel stroom gaat als er uit komt. Hierin kan ook keuzevrijheid zijn. 3. “Balance”. Om van deze preflow een echte stroom te maken, worden vanuit het eindpunt naar het beginpunt alle knopen gebalanceerd: de stroom op de ingaande tak wordt zodanig gereduceerd dat er balans in de knoop ontstaat. In het voorbeeld van hierboven werkt dit als volgt. Eerst geef je de lagen aan en laat je alle overbodige takken weg. In ons geval verdwijnt tak n1 → n2, omdat die in laag 1 blijft:
Vervolgens laten we grootst mogelijke preflow door dit netwerk lopen (a):
Door de tak ns → n1 loopt een stroom ter grootte van de capaciteitsgrens: 6. Door de capaciteitsgrens 3 op tak n1 → nt blijft er op deze tak van de ingaande stroom 6 maar 3 over. Dit levert een preflow. De stroom is in n1 niet gebalanceerd. Vervolgens moet het netwerk gebalanceerd worden (b). Dit betekent dat de stroom op ns → n1 moet worden gereduceerd tot 3, om knoop n1 in balans te krijgen. De andere knopen zijn al in balans. We hebben nu van de preflow een stroom gemaakt en deze stroom is het resultaat van de eerste iteratie. We maken nu de gereduceerde graaf van deze stroom (a):
Er zijn in dit gereduceerde netwerk 4 lagen. Vervolgens laten we weer alle takken weg die niet naar een volgende laag lopen en krijgen het netwerk (b). De preflow die hierbij hoort is hieronder te zien (a)
Door de preflow te balanceren ontstaat een stroom (zie (b) hierboven). In het gereduceerde netwerk dat ontstaat (hieronder (b)) zie je dat er nu geen pad meer is met een stroom van begin- naar eindpunt.
De oplossing die door het algoritme is gevonden staat in (a). Bij een goede implementatie heeft het algoritme van Karzanov een complexiteit van O(n3). De meeste state-of-the-art algoritmes zijn tegenwoordig gebaseerd op het concept preflow.
0.3 Toelaatbare stromen De algoritmes die we hebben bekeken voor Max-Flow zijn gebaseerd op het successievelijk verbeteren van een bestaande stroming. Dat is geen probleem als er op de takken alleen maar bovengrenzen zijn gegeven voor de stromen. We kunnen dan altijd starten met de nulstroom, een stroom 0 op alle takken. Dat is een toelaatbare stroom en tijdens het algoritme wordt ervoor gezorgd dat de stroom toelaatbaar blijft. Als er ook ondergrenzen voor de stromen gegeven zijn zitten we met een probleem, want er moet dan eerst een toelaatbare stroom worden gevonden. Dat kan wel eens tegenvallen voor een groot netwerk. We zullen nu zien hoe je een toelaatbare stroom kunt vinden. Als xij de stroom van knoop i naar knoop j is, dan gelden er in het algemeen boven- en ondergrenzen voor deze stroom: lij ≤ xij ≤ uij Verder moet in elke knoop i de balansvergelijking gelden:
∑x −∑x ij
j
ji
=0
j
Als er geen ondergrenzen zijn: lij = 0, dan is de nulstroom xij = 0 (voor alle i en j) toelaatbaar.
Als dat niet zo is halen we de volgende truc uit: We definiëren nieuwe variabelen xij’ = xij - lij Het effect van dit verschuiven van de variabelen is dat de ondergrenzen verdwijnen maar dat in de knopen kunstmatige bronnen en putten ontstaan:
∑x j
ij
' − ∑ x ji ' = ∑ l ji − ∑ l ij =: bi j
j
j
0 ≤ xij’ ≤ uij - lij Dit is niet meer dan een herverdeling, want alle bronnen en putten die zo ontstaan heffen elkaar in totaal gezien op. Het idee is nu dat we deze artificiële tekorten en overschotten gaan koppelen aan een superbron ns* en een superput nt*, die we als twee extra knopen aan het originele netwerk zullen toevoegen. Als bi > 0 dan wordt ni verbonden met ns*, met capaciteit bi. Als bi < 0 dan wordt ni verbonden met nT*, met capaciteit -bi. De oorspronkelijke takken uit het netwerk krijgen als capaciteit uij - lij. Als voorbeeld bekijken we het volgende netwerk, waar bij elke tak de ondergrens en de bovengrens voor de stroom staat aangegeven.
De tak van eind- naar beginknoop geeft aan dat we een maximale stroom zoeken. De capaciteit M heeft een grote waarde, groter dan de totale stroom kan zijn (bijvoorbeeld de waarde van een snede). Door de ondergrenzen is een stroom 0 op alle takken niet toelaatbaar. De balansvergelijkingen in dit netwerk zijn: Knoop ns: – xts + xs1 + xs2 = 0. – xs1 + x1t + x12 = 0. Knoop n1: Knoop n2: – xs2 – x12 + x2t = 0. – x1t – x2t + xts = 0. Knoop nt: Nu gaan we over op nieuwe variabelen : xts = xts’. xs1 = xs1’ + 1. xs2 = xs2’ + 1. x1t = x1t’. x12 = x12’ + 2. x2t = x2t’ + 2. Met deze nieuwe variabelen krijgen we de volgende balansvergelijkingen : Knoop ns: – xts’ + xs1’ + xs2’ = – 2 = bs. – xs1’ + x1t’ + x12’ = – 1 = b1. Knoop n1: – xs2’ – x12’ + x2t’ = 1 = b2. Knoop n2: Knoop nt: – x1t’ – x2t’ + xts’ = 2 = bt. Het uitgebreide netwerk met de superbron en de superknoop ziet er nu als volgt uit :
In dit netwerk moeten we een toelaatbare stroom vinden, met de kanttekening dat de superbron 3 eenheden moet leveren en de superput 3 eenheden moet afvoeren. We gaan nu wegen van de superbron naar de superput zoeken waar stroom doorheen kan. Als eerste pakken we ns* → nt → ns → n1 → nt*. Over dit pad kan 1 eenheid stromen. Het pad ns* → n2 → nt → ns → nt* is ook goed voor 1 eenheid. Ten slotte kan er nog 1 eenheid langs ns* → nt → ns → nt*. De drie paden staan getekend in de volgende figuur:
Hiermee hebben we een stroom gevonden die aan alle grenzen voldoet. Vervolgens schrijven we op wat dit voor het oorspronkelijke netwerk betekent. Hierbij moet worden gecorrigeerd voor de verschuivingen die we in de stromen hebben geïntroduceerd door over te gaan op nieuwe variabelen:
Een toelaatbare stroom in het netwerk die we zo hebben geconstrueerd is te zien in het volgende plaatje: