Ford-Fulkersonov algoritam
Ford-Fulkerson metod (nazvan po L. R. Fordu, ml. i D. R. Fulkersonu je algoritam koji računa maksimalni protok u transportnoj mreži. Objavljen je 1956. Ime "Ford-Fulkerson" se često koristi i za Edmonds-Karp algoritam, koji je specijalizacija Ford-Fulkersona.
Osnovna ideja je jednostavna. Dokle god postoji putanja od izvora (početni čvor) do ponora (krajnji čvor), sa dostupnim kapacitetima na svim granama putanje, slaćemo protok duž jedne od ovih putanja. Tada pronalazimo novu putanju itd. Putanja se dostupnim kapacitetom se naziva pojačavajuća putanja.
Algoritam
urediNeka je graf i neka za svaku granu od do predstavlja kapacitet, a protok duž grane. Mi želimo da pronađemo maksimalan protok od izvora do ponora . Nakon svakog koraka u algoritmu, sledeća tvrđenja su tačna:
Ograničenja kapaciteta: Protok kroz granu ne može premašiti njen kapacitet. Kosa simetrija: Ukupan protok od do mora biti suprotan od protoka od do (videti primer). Očuvanje protoka: Pod uslovom da nije ili . Ukupan protok ka nekom čvoru je nula, osim ako čvor nije izvor, koji "stvara" protok, ili ponor, koji "troši" protok.
Ovo znači da je protok kroz mrežu dozvoljeni protok nakon svakog ponavljanja u algoritmu. Definišemo rezidualnu mrežu kao mrežu kapaciteta i bez protoka. Obratite pažnju da može da se desi da protok od do bude dozvoljen u rezidualnoj mreži, iako nedozvoljen u početnoj mreži: ako i tada .
Algoritam Ford-Fulkerson
- Ulaz Graf sa kapacitetima , izvorom i ponorom
- Izlaz Protok od do koji je maksimalan
- za sve grane
- Dok postoji putanja od do u , takva da je za sve grane :
- Pronađi
- Za svaku granu
- (Pošalji protok duž putanje)
- (Protok se može "vratiti" kasnije)
Putanja iz koraka 2 se može pronaći uz pomoć pretrage u širinu ili pretrage u dubinu u grafu . Ukoliko koristite pretragu u širinu, onda se zove Edmonds-Karp algoritam.
Kada se više nijedna putanja ne može pronaći, tada se iz ne može doći do u rezidualnoj mreži. Ako je skup čvorova do kojih se može doći u rezidualnoj mreži iz , onda je ukupan kapacitet početne mreže grana od do sa jedne strane jednak ukupnom protoku koji smo pronašli od do , a sa druge strane služi kao gornja granica za sve takve protoke. To dokazuje da je protok koji smo pronašli maksimalan.
Glavni primer
urediSledeći primer pokazuje prve korake Ford-Fulkersona u protočnoj mreži sa 4 čvora, izvorom i ponorom . Ovaj primer pokazuje ponašanje algoritma u najgorem slučaju. U svakom koraku se korz mrežu šalje protok od . Da smo iskoristili pretragu u širinu, samo dva koraka bi bila potrebna.
Putanja | Kapacitet | Rezultujuća mreža |
---|---|---|
Početna mreža | ||
|
||
|
||
Nakon još 1998 koraka… | ||
Konačni izgled mreže. |
Primetite kako se protok "gura nazad" od do kada se traži putanja .
Nezavršavajući primer
urediRazmotrite mrežu datu desno, sa izvorom , ponorom i kapacitetima grana , i , i , a kapacitet svih drugih grana je neko . Konstanta je odabrana, jer . Koristimo pojačavajuće putanje u skladu sa datom tabelom, gde je , i .
Korak | Pojačavajuća putanja | Poslat protok | Preostali protok | ||
---|---|---|---|---|---|
0 | |||||
1 | |||||
2 | |||||
3 | |||||
4 | |||||
5 |
Obratite pažnju na to da nakon koraka 1, kao i nakon koraka 5, preostali kapacitet grana , i su u obliku , and , za neko . Ovo znači da možemo koristiti pojačavajuće putanje , , i beskonačno mnogo puta i preostali kapacitet ovih grana će uvek imati isti oblik. Ukupan protok u mreži nakon koraka 5 je . Ako nastavimo da koristimo ove pojačavajuće putanje, ukupan protok teži ka , dok je maksimalan protok . U ovom slučaju se algoritam nikad ne završava, a protok ni ne teži ka maksimalnom protoku.[1]
Dodatni primeri
urediPrimer 1.
urediPronađimo maksimalan protok za mrežu sa Slike 1.1
1. Iteracija :
U koraku 1, postavili smo da za svaki čvor prethodnik i rezerva budu jednaki simbolu -, da rezerva za bude jednaka " ∞ " i da je S = { }. Tada imamo mrežu sa Slike 1.2
U koraku 2, proveravamo da skup S nije prazan i biramo iz S.
U koraku 3, stavljamo da rezerva za bude jednaka min(7, ∞) = 7 i da prethodnik od bude čvor . Smeštamo u S. Takođe stavljamo da rezerva za bude jednaka min(6, ∞) = 6, a da prethodnik od bude jednak . Smeštamo u S.
Koraci 4,5 se ne mogu primeniti (4. korak zato što nemamo ni jednu nepravilno orijentisanu granu, a 5. korak jer nije označen), pa se vraćamo na korak 2.
U koraku 2, proveravamo da skup S nije prazan, a zatim iz S biramo neki čvor, recimo . Skup S sada sadrži samo
U koraku 3, stavljamo da rezerva za bude jednaka min(3, 7) = 3 i da prethodnik od bude čvor . Smeštamo u S tako da je S = { , }. Ponovo se Koraci 4,5 ne mogu primeniti, tako da se vraćamo na Korak 2.
U koraku 2, proveravamo da skup S nije prazan i biramo neki čvor, recimo iz S. Skup S ponovo sadrži samo .
U koraku 3, stavljamo rezervu za na min(3,5) = 3 i prethodnik od postaje čvor . Ne smeštamo u S.
U koraku 4, označili bismo ali je već označeno.
U koraku 5, vidimo da je označeno i koristeći funkciju prethodnika, otkrivamo da imamo lanac → → → sa težinama grana (7,0) → (3,0) → (5,0) i dodajuci 3, rezervu za protoku svake grane tj. drugoj kordinati grane, dobijamo lanac → → → sa tezinama (7,3) → (3,3) → (5,3) što nam daje mrežu sa Slike 1.3.
2. Iteracija:
Sada se vraćamo na korak 1, gde ponovo podešavamo oznake i prethodnike i neka je S = { }.
U koraku 2, biramo a iz S.
U koraku 3, podešavamo da rezerva za bude jednaka min(∞,7-3) = 4 i da prethodnik od bude čvor . Smeštamo u S. Takođe stavljamo da rezerva za bude jednaka 6 i stavljamo da prethodnik od bude čvor . Smeštamo u S.
Koraci 4,5 se ne mogu primeniti, tako da se vraćamo na korak 2.
U koraku 2, biramo iz S. Kako protok od do jednak kapacitetu grane od do , ne možemo da primenimo korak 3 i označimo .
U koraku 2, biramo iz S.
U koraku 3, podešavamo da rezerva za bude jednaka min(6,2) = 2 i da prethodnik od bude čvor . Ne smeštamo u S.
U koraku 5, vidimo da je označeno i koristeći funkciju prethodnika, otkrivamo da imamo lanac → → sa težinama grana (6,0) → (2,0) i dodajuci 2, rezervu za protoku svake grane tj. drugoj kordinati grane, dobijamo lanac → → sa težinama grana (6,2) → (2,2) što nam daje mrežu sa Slike 1.4.
3. Iteracija:
Sada se vraćamo na korak 1 i ponavljamo postupak dok ne dobijemo lanac → → → sa težinama grana (6,2) → (2,0) → (5,3) i dodajući 2, rezervu od protoku svake grane, dobijamo lanac → → → sa težinama grana (6,4) → (2,2) → (5,5) što nam daje Sliku 1.5
4. Iteracija:
Sada se vraćamo na korak 1 gde ponovo poništavamo oznake i prethodnike i postavljamo S = { }.
U koraku 2, biramo iz S.
U koraku 3, kao i ranije, stavljamo da rezerva za bude jednaka 4 i da prethodnik od bude čvor . Smeštamo u S. Takođe stavljamo da rezerva za bude jednaka 6 - 4 = 2 i smeštamo u S.
Vraćamo se na korak 2. Biramo iz S. Kako je protok od do jednak kapacitetu grane od do , ne možemo da označimo , tako da treba da se vratimo na korak 2.
Korak 2, biramo iz S. Kako je protok od do jednak njihovom kapacitetu, ne možemo da označimo i . Ponovo se vraćamo na korak 2, ali je skup S prazan, tako da smo završili.
Konačan protok prikazan je na Slici 1.6 i on predstavlja zbir protoka u svim iteracijama tj. u ovom slučaju 3 + 2 + 2 = 7.
[[Datoteka:Slika 2.2.png|350p|thumbnail|center|Lj
PRIMER 2. (sa nepravilno orijentisanom granom)
urediPronađimo maksimalan protok za mrežu sa Slike 2.1
1. Iteracija :
Ovoga puta daćemo manje detalja za nekoliko prvih iteracija.
U prvoj iteraciji označavamo (-,∞) , , , , , . To nam daje put - i dobijamo lanac → → → sa težinama grana (9,0) → (4,0) → (8,0) i dodajući 4, rezervu od protoku svake grane, dobijamo lanac → → → sa težinama grana (9,4) → (4,4) → (8,4) što nam daje Sliku 2.2
2. Iteracija :
U drugom prolazu označavamo (-,∞), , , , , . To nam daje put - i dobijamo lanac → → → sa težinama grana (8,0) → (9,0) → (7,0) i dodajući 7, rezervu od protoku svake grane, dobijamo lanac → → → sa težinama grana (8,7) → (9,7) → (7,7) što nam daje Sliku 2.3
3. Iteracija :
U trećem prolazu označavamo (-,∞) , , , , , . To nam daje put - i dobijamo lanac → → → sa težinama grana (8,7) → (4,0) → (8,4) i dodajući 1, rezervu od protoku svake grane, dobijamo lanac → → → sa težinama grana (8,8) → (4,1) → (8,5) što nam daje Sliku 2.4.
4. Iteracija :
Od sada postaje interesantno tako da ćemo obratiti pažnju na detalje. Označavamo ali ne možemo da označimo i smeštamo samo u S. Birajući iz S, ne možemo da označimo , ali označavamo i smeštamo u S. Birajući iz S, ne možemo da označimo , ali kako nije označeno, možemo to da učinimo . Kako grana ( , ) nije pravilno orijentisana, dopuštamo da rezerva od bude jednaka min(7,5) = 5. Stavljamo da prethodnik od bude jednak i smeštamo u S (Korak 4. u Postupku). Birajući iz S, jedini izbor koji imamo je da označimo i smestimo u S. Biramo iz S i označavamo .
To nam daje put - i dobijamo lanac → → ← → → sa težinama grana (9,4) → (6,0) → (9,7) ← (4,1) → (8,5) i kako je rezerva od jednaka 3, svakom protoku dodajemo 3, osim onom za granu ( , ). Pošto ona nije pravilno orijentisana, oduzimamo 3 od protoka ove grane i dobijamo lanac → → ← → → sa težinama grana (9,7) → (6,3) → (9,4) ← (4,4) → (8,8) što nam daje Sliku 2.5.
5. Iteracija :
Sada možemo da počnemo završni prolaz. Počinjemo sa S = { }. Uklanjamo iz S, označavamo i smeštamo u S. Biramo iz S, označavamo , ne možemo da označimo iz . Smeštamo u S. Biramo iz S , označavamo pošto je rezerva od jednaka minimumu protoka grane ( , ) i rezerva od (Korak 4. u Postupku). Zatim smeštamo u S. Potom biramo iz S, ali ne možemo da izvršimo bilo koji od preostalih koraka, pa se vraćamo na korak 2. Ali skup S je prazan, pa završavamo. Sada imamo Sliku 2.6.
Konačan protok prikazan je na Slici 2.6 i on predstavlja zbir protoka u svim iteracijama tj. u ovom slučaju 4 + 7 + 1 + 3 = 15.
Složenost
urediDodajući pojačavajuće putanje protoku koji je već utvrđen u grafu doći ćemo do maksimalnog protoka kada više ne budemo mogli da pronađemo nove pojačavajuće putanje. Međutim, ne postoji sigurnost da će se do ove situacije ikad doći, tako da je jedino sigurno da ukoliko se algoritam zaustavi da će odgovor biti tačan. U slučaju da algoritam radi beskonačno, može se desiti da protok uopšte ne konvergira ka maksimalnom protoku. Ali, ova situacija se događa samo sa iracionalnim vrednostima protoka. Kada su kapaciteti celi brojevi, vreme izvršavanja je ograničeno sa (videti veliko O), gde je broj grana u grafu i maksimalan protok grafa. Ovo je zato što se pojačavajuća putanja može pronaći u vremena i povećati protok za celobrojnu vrednost koja je minimum .
Varijacija Ford-Fulkerson algoritma koji garantuje završetak i vremenski je nezavisna od maksimalnog protoka u grafu je Edmonds-Karp algoritam koji radu vremenu .
Pajton implementacija
urediclass Edge(object):
def __init__(self, u, v, w):
self.source = u
self.sink = v
self.capacity = w
def __repr__(self):
return "%s->%s:%s" % (self.source, self.sink, self.capacity)
class FlowNetwork(object):
def __init__(self):
self.adj = {}
self.flow = {}
def add_vertex(self, vertex):
self.adj[vertex] = []
def get_edges(self, v):
return self.adj[v]
def add_edge(self, u, v, w=0):
if u == v:
raise ValueError("u == v")
edge = Edge(u,v,w)
redge = Edge(v,u,0)
edge.redge = redge
redge.redge = edge
self.adj[u].append(edge)
self.adj[v].append(redge)
self.flow[edge] = 0
self.flow[redge] = 0
def find_path(self, source, sink, path):
if source == sink:
return path
for edge in self.get_edges(source):
residual = edge.capacity - self.flow[edge]
if residual > 0 and edge not in path:
result = self.find_path( edge.sink, sink, path + [edge])
if result != None:
return result
def max_flow(self, source, sink):
path = self.find_path(source, sink, [])
while path != None:
residuals = [edge.capacity - self.flow[edge] for edge in path]
flow = min(residuals)
for edge in path:
self.flow[edge] += flow
self.flow[edge.redge] -= flow
path = self.find_path(source, sink, [])
return sum(self.flow[edge] for edge in self.get_edges(source))
Java implementacija
urediimport java.util.*;
import java.lang.*;
import java.io.*;
import java.util.LinkedList;
class MaxFlow
{
static final int V = 6; //Broj cvorova grafa
/* Vraca true ako postoji put od pocetnog
cvora do krajnjeg (cilja) u grafu,
takodje popunjava parent[] da zapamti put
*/
boolean bfs(int rGraph[][], int s, int t, int parent[])
{
// Napravi listu posecenih elemenata, na pocetku stavi da su svi
// neposeceni
boolean visited[] = new boolean[V];
for(int i=0; i<V; ++i)
visited[i]=false;
LinkedList<Integer> queue = new LinkedList<Integer>();
queue.add(s);
visited[s] = true;
parent[s]=-1;
// BFS pretraga
while (queue.size()!=0)
{
int u = queue.poll();
for (int v=0; v<V; v++)
{
if (visited[v]==false && rGraph[u][v] > 0)
{
queue.add(v);
parent[v] = u;
visited[v] = true;
}
}
}
// Ako smo dosli do ciljnog cvora u BFS-u onda
// vrati true u suprotnom vrati false
return (visited[t] == true);
}
// Vraca maksimalni protok od s do t za dati graf
int fordFulkerson(int graph[][], int s, int t)
{
int u, v;
int rGraph[][] = new int[V][V];
for (u = 0; u < V; u++)
for (v = 0; v < V; v++)
rGraph[u][v] = graph[u][v];
// Lista je popunjena u BFS- u i pamti puteve
int parent[] = new int[V];
int max_flow = 0; // na pocetku je protok 0
// Augmentuj protok dokle god postoji put od izvora do cilja
while (bfs(rGraph, s, t, parent))
{
// Pronadji maksimalan protok za pronadjeni put od s do t
int path_flow = Integer.MAX_VALUE;
for (v=t; v!=s; v=parent[v])
{
u = parent[v];
path_flow = Math.min(path_flow, rGraph[u][v]);
}
// Povecaj kapacitete grana i umanji ako ima onih sa obrnutim smerom,
// duz nadjene putanje
for (v=t; v != s; v=parent[v])
{
u = parent[v];
rGraph[u][v] -= path_flow;
rGraph[v][u] += path_flow;
}
// Dodajemo trenutni protok konacnom maksimalnom protoku kroz mrezu
max_flow += path_flow;
}
// Vracamo konacan max protok
return max_flow;
}
// Main metod za testiranje algoritma
public static void main (String[] args) throws java.lang.Exception
{
// Kreiramo proizvoljan graf
int graph[][] =new int[][] { {0, 16, 13, 0, 0, 0},
{0, 0, 10, 12, 0, 0},
{0, 4, 0, 0, 14, 0},
{0, 0, 9, 0, 0, 20},
{0, 0, 0, 7, 0, 4},
{0, 0, 0, 0, 0, 0}
};
MaxFlow m = new MaxFlow();
System.out.println("Maksimalan protok kroz mrezu je " +
m.fordFulkerson(graph, 0, 5));
}
}
C++ implementacija
uredi#include <iostream>
#include <limits.h>
#include <string.h>
#include <queue>
using namespace std;
// Broj cvorova u grafu
#define V 6
/* Vraca true ako postoji put od pocetnog
cvora do krajnjeg (cilja) u grafu,
takodje popunjava parent[] da zapamti put
*/
bool bfs(int rGraph[V][V], int s, int t, int parent[])
{
// Napravi listu posecenih elemenata, na pocetku stavi da su svi
// neposeceni
bool visited[V];
memset(visited, 0, sizeof(visited));
queue <int> q;
q.push(s);
visited[s] = true;
parent[s] = -1;
// BFS pretraga
while (!q.empty())
{
int u = q.front();
q.pop();
for (int v=0; v<V; v++)
{
if (visited[v]==false && rGraph[u][v] > 0)
{
q.push(v);
parent[v] = u;
visited[v] = true;
}
}
}
// Ako smo dosli do ciljnog cvora u BFS-u onda
// vrati true u suprotnom vrati false
return (visited[t] == true);
}
// Vraca maksimalni protok od s do t za dati graf
int fordFulkerson(int graph[V][V], int s, int t)
{
int u, v;
int rGraph[V][V];
for (u = 0; u < V; u++)
for (v = 0; v < V; v++)
rGraph[u][v] = graph[u][v];
int parent[V]; // Lista je popunjena u BFS- u i pamti puteve
int max_flow = 0;
// Augmentuj protok dokle god postoji put od izvora do cilja
while (bfs(rGraph, s, t, parent))
{
// Pronadji maksimalan protok za pronadjeni put od s do t
int path_flow = INT_MAX;
for (v=t; v!=s; v=parent[v])
{
u = parent[v];
path_flow = min(path_flow, rGraph[u][v]);
}
// Povecaj kapacitete grana i umanji ako ima onih sa obrnutim smerom,
// duz nadjene putanje
for (v=t; v != s; v=parent[v])
{
u = parent[v];
rGraph[u][v] -= path_flow;
rGraph[v][u] += path_flow;
}
// Dodaj trenutni protok maksimalnom protoku
max_flow += path_flow;
}
// Vrati max protok
return max_flow;
}
// Main funkcija za testiranje algoritma
int main()
{
int graph[V][V] = { {0, 16, 13, 0, 0, 0},
{0, 0, 10, 12, 0, 0},
{0, 4, 0, 0, 14, 0},
{0, 0, 9, 0, 0, 20},
{0, 0, 0, 7, 0, 4},
{0, 0, 0, 0, 0, 0}
};
cout << "Maksimalan protok kroz mrezu je : " << fordFulkerson(graph, 0, 5);
return 0;
}
Reference
uredi- ^ Zwick, Uri (21. 8. 1995). „The smallest networks on which the Ford-Fulkerson maximum flow procedure may fail to terminate”. Theoretical Computer Science. 148 (1): 165—170. doi:10.1016/0304-3975(95)00022-O.
Literatura
uredi- Heineman, George T.; Pollice, Gary; Selkow, Stanley (2008). „Chapter 8:Network Flow Algorithms”. Algorithms in a Nutshell. Oreilly Media. str. 226—250. ISBN 978-0-596-51624-6.
- Anderson, James A. (2004). „Chapter 16:Networks - Ford–Fulkerson algorithm”. Discrete Mathematics with Combinatorics (2nd Edition). Pearson Education. str. 783-789. ISBN 978-0130457912.
- Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford (2001). „Section 26.2: The Ford-Fulkerson method”. Introduction to Algorithms (Second izd.). MIT Press and McGraw-Hill. str. 651-664. ISBN 978-0-262-03293-3.
- Ford, L. R.; Fulkerson, D. R. (1956). „Maximal flow through a network”. Canadian Journal of Mathematics. 8: 399—404.