Netwerkmodellen en Algoritmen 1 0 Algoritmes voor Max-Flow Een belangrijk type netwerkprobleem is het maximale stroomprobleem (Max-Flow problem). Het probleem bestaat uit het vinden van een zo groot mogelijke stroom door een netwerk van een gegeven beginknoop naar een gegeven eindknoop, waarbij capaciteiten (bovengrenzen voor de stroom) op de takken zijn vastgelegd. Het netwerkje hierboven is daar een simpel voorbeeldje van. Je kunt er 9 eenheden doorheen laten stromen: 5 via n1 en 4 via n2. We zullen hier twee algoritmes behandelen waarmee in het algemeen zo’n maximale stroom kan worden bepaald. 0.1 Max-flow min-cut stelling Als je naar een netwerk kijkt, waarin een maximale stroom wordt gezocht, kun je vaak op voorhand al uitspraken doen over de mogelijke waarden van een stroom door het netwerk. Als je bijvoorbeeld de capaciteiten optelt van alle takken die van de beginknoop uitgaan geeft dit een bovengrens voor de stroom die door het netwerk kan lopen, want alle stroom moet vanuit de beginknoop het netwerk in. In het bovenstaande voorbeeldje levert dat 5 + 7 = 12. Evenzo is de som van de capaciteiten van alle takken die in de eindknoop uitkomen ook een bovengrens. In het voorbeeld: 7 + 4 = 11. De kleinste van de twee waarden die we zo vinden is weer een bovengrens voor de stroom. (In het voorbeeld is dit 11). Wat algemener kunnen we ook een “muurtje” maken dat het netwerk doorsnijdt, zodanig dat de in- en de uitgang van het netwerk aan verschillende kanten van het muurtje liggen. Alle stroom moet van de ingang naar de uitgang en moet de muur passeren. Als je dus de totale capaciteit neemt van alle takken die door het muurtje heen gaan krijg je een bovengrens voor de stroom door het netwerk. Bij het optellen van de capaciteiten moet je opletten dat je alleen de takken telt die de muur in de goede richting snijden, van ingang naar uitgang, want alleen die takken kunnen een positieve bijdrage leveren aan de totale stroom. De takken die terug lopen kunnen de totale stroom alleen verkleinen. Hun grootste positieve bijdrage is 0. Netwerkmodellen en Algoritmen 2 Een muurtje dat het netwerk in tweeën deelt, met de in- en uitgang elk aan verschillende kanten heet ook wel een snede (cut). Wat formeler geformuleerd is een snede een partitie (verdeling) van alle knopen in twee verzamelingen, waarbij de in- en uitgangsknoop in verschillende verzamelingen zitten. Voor een netwerk met n knopen zijn er dus 2n-2 verschillende sneden mogelijk, als je de ingangsknoop in de eerste verzameling doet en de uitgangsknoop in de tweede. Elke andere knoop kun je namelijk telkens in de eerste óf in de tweede verzameling stoppen. Bij elke snede hoort een bovengrens voor de stroom die je vindt door de capaciteiten van de (doorsneden) takken van verzameling 1 naar verzameling 2 op te tellen. Het minimum van deze bovengrenzen over alle mogelijk sneden blijkt een scherpe bovengrens te zijn voor de totale stroom door het netwerk, d.w.z., er is een stroom te vinden die deze bovengrens aanneemt. Dit is de inhoud van de Max-flow min-cut stelling: Er is een stroom (max-flow) in het netwerk die de waarde van de kleinste capaciteitsgrens over alle snedes (min-cut) aanneemt. Voorbeeld: In het simpele netwerk van hierboven zijn vier mogelijke sneden. Dit levert de volgende bovengrenzen op: 5+7 = 12, 7+2+7 = 16, 5+4 = 9, 7+4 = 11. De kleinste bovengrens is 9 en er is inderdaad een stroom 9 door het netwerk mogelijk: 5 eenheden door de bovenste twee takken en 4 door de onderste twee. 0.2 Algoritme van Ford en Fulkerson Hoe kun je nu een maximale stroming door een netwerk bepalen? Een voor de hand liggend idee is het volgende: Je zoekt in het netwerk een pad van begin- naar eindpunt, kijkt hoeveel er langs dit pad kan (het minimum van de capaciteiten langs dit pad) en laat dit er vervolgens doorheen lopen. Je kunt alle capaciteiten langs het pad dan met het bedrag van de stroom verlagen. Vervolgens zoek je weer een ander pad, etc. Dit kun je doen totdat er geen pad meer mogelijk is waarlangs nog stroom kan lopen. Heb je dan de maximale stroom gevonden? Het antwoord is helaas: Nee! Kijk maar naar het volgende voorbeeld: Netwerkmodellen en Algoritmen 3 Kies als eerste pad: ns n1 n2 nt. Langs dit pad kan maximaal 2 stromen. Kies vervolgens: ns n1 nt. Hierlangs kan nog 3 stromen. Tenslotte kun je langs ns n2 nt nog 2 eenheden laten stromen. Nu lijkt het netwerk verzadigd. Vanuit s kan er niets meer naar n1, die stroom is al maximaal. Er kan nog wel iets naar n2, maar dat kan niet naar nt omdat de stroom van n2 naar nt al maximaal is en de pijl van n2 naar n1 de verkeerde kant op staat. We hebben nu een totale stroom van 7 gevonden, maar die is niet maximaal. We zagen namelijk eerder dat de minimale snede van dit netwerk 9 oplevert en dat je inderdaad een stroom 9 kunt realiseren door 5 eenheden langs n1 te sturen en 4 eenheden langs n2. Waarom werkt dit algoritme niet? Dat komt omdat we door de keuze van het eerste pad twee eenheden die over n1 kwamen de verkeerde kant uit hebben gestuurd, via n2, en daardoor capaciteit van de tak n2 nt hebben opgesnoept, die beter gebruikt had kunnen worden voor de stroom over ns n2 nt. Ook kun je zeggen dat we onszelf gefopt hebben door te denken dat er vanuit n2 geen stroom naar n1 kan lopen omdat de pijl de verkeerde kant opstaat. Je kunt namelijk wél een stroom ter grootte 2 van n2 naar n1 laten lopen, omdat er al een stroom van n1 naar n2 liep. Die twee eenheden kun je terugnemen, waarmee je feitelijk een extra stroom van n2 naar n1 hebt laten lopen. Effectief komt het er op neer dat er dan geen stroom meer loopt van n1 naar n2, je hebt de eerder ingestelde stroom in tweede instantie teruggenomen. Dat betekent dat het mogelijk is om nog een stroom ter grootte 2 te laten lopen over ns n2 n1 nt. Dit brengt de stroom in totaal op 9, en dat is wel maximaal. Om duidelijker te maken hoe het zit met het “terugnemen” van stroom kun je het beter hebben over de residuele graaf (residual graph) die hoort bij een stroom in de graaf. In deze graaf wordt elke gerichte tak in het originele netwerk vervangen door twee takken met tegengestelde richtingen met elk een capaciteit. De capaciteiten geven aan hoeveel er maximaal nog in die richting kan stromen. Een tak met een capaciteit van 5 waardoor momenteel 3 eenheden stromen wordt dus vervangen door een tak met capaciteit 2 en een tak terug met capaciteit 3. Als er nog niets doorheen stroomt blijft het een tak met capaciteit 5 (de tak terug heeft capaciteit 0), en als er een maximale stroom doorheen gaat is er alleen een tak terug met capaciteit 5 (de tak heen heeft capaciteit 0). Ter illustratie volgen hieronder Netwerkmodellen en Algoritmen 4 de verschillende residuele grafen als gevolg van de drie stappen die we eerder hebben beschreven. Je ziet aan de laatste residuele graaf onmiddellijk dat er nog 2 eenheden over het pad s 2 1 t kunnen stromen. Dit levert de volgende residuele graaf: In deze graaf is er geen stroom meer mogelijk van s naar t. Dat betekent dat het algoritme hier stopt. We hebben nu een stroom van in totaal 9 eenheden gevonden van s naar t en dat is maximaal. De oplossing die we nu gevonden hebben is: De oplossing krijg je door de capaciteiten van de takken uit de residuele graaf te nemen die tegengesteld lopen aan takken uit de originele graaf en deze waarden als stroom bij de bijbehorende tak in de originele graaf te nemen. Het algoritme dat we nu aan de hand van een voorbeeld hebben beschreven is het algoritme van Ford en Fulkerson (1956): Start met een toelaatbare stroom (meestal stroom 0 op alle takken). while er is nog een pad waar een stroom kan lopen do Kies een pad waarlangs nog een stroom kan lopen. Laat de maximale stroom langs dit pad lopen. Pas de residuele graaf aan. Netwerkmodellen en Algoritmen 5 od Een illustratie met behulp van Java applets is bijvoorbeeld te vinden op http://www-b2.is.tokushima-u.ac.jp/~ikeda/suuri/maxflow/Maxflow.shtml 0.3 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. Netwerkmodellen en Algoritmen 6 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 capaciteitsgrenzen 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.4 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. Netwerkmodellen en Algoritmen 7 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): Netwerkmodellen en Algoritmen 8 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.5 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: Netwerkmodellen en Algoritmen x x ij j ji 9 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 lij : 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. Knoop n1: – xs1 + x1t + x12 = 0. Knoop n2: – xs2 – x12 + x2t = 0. Knoop nt: – x1t – x2t + xts = 0. 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. Knoop n1: – xs1’ + x1t’ + x12’ = – 1 = b1. Netwerkmodellen en Algoritmen 10 Knoop n2: – xs2’ – x12’ + x2t’ = 1 = b2. 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 superknoop 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: Netwerkmodellen en Algoritmen 11