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

uredi

Neka 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
  1.   za sve grane  
  2. Dok postoji putanja   od   do   u  , takva da je   za sve grane  :
    1. Pronađi  
    2. Za svaku granu 
      1.   (Pošalji protok duž putanje)
      2.   (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

uredi

Sledeć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

uredi
 

Razmotrite 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

uredi
 
Slika 1.1
 
Slika 1.2 / Inicijalizacija

Primer 1.

uredi

Pronađ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.

 
Slika 1.3
 
Slika 1.4

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.

 
Slika 1.5
 
Slika 1.6

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.

 
Slika 2.1

[[Datoteka:Slika 2.2.png|350p|thumbnail|center|Lj

PRIMER 2. (sa nepravilno orijentisanom granom)

uredi

Pronađ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

 
Slika 2.3
 
Slika 2.4

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.

 
Slika 2.5
 
Slika 2.6

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

uredi

Dodajuć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

uredi
class 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

uredi
import 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
  1. ^ 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

Spoljašnje veze

uredi