Pređi na sadržaj

Ford-Fulkersonov algoritam

S Vikipedije, slobodne enciklopedije

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 | uredi izvor]

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 | uredi izvor]

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 | uredi izvor]

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 | uredi izvor]

Slika 1.1
Slika 1.2 / Inicijalizacija

Primer 1.[uredi | uredi izvor]

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 | uredi izvor]

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 | uredi izvor]

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 | uredi izvor]

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 | uredi izvor]

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 | uredi izvor]

#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 | uredi izvor]

  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 | uredi izvor]

Spoljašnje veze[uredi | uredi izvor]