Форд-Фулкерсонов алгоритам

С Википедије, слободне енциклопедије

Форд-Фулкерсон метод (назван по Л. Р. Форду, мл. и Д. Р. Фулкерсону је алгоритам који рачуна максимални проток у транспортној мрежи. Објављен је 1956. Име "Форд-Фулкерсон" се често користи и за Едмондс-Карп алгоритам, који је специјализација Форд-Фулкерсона.

Основна идеја је једноставна. Докле год постоји путања од извора (почетни чвор) до понора (крајњи чвор), са доступним капацитетима на свим гранама путање, слаћемо проток дуж једне од ових путања. Тада проналазимо нову путању итд. Путања се доступним капацитетом се назива појачавајућа путања.

Алгоритам[уреди | уреди извор]

Нека је граф и нека за сваку грану од до представља капацитет, а проток дуж гране. Ми желимо да пронађемо максималан проток од извора до понора . Након сваког корака у алгоритму, следећа тврђења су тачна:

Ограничења капацитета: Проток кроз грану не може премашити њен капацитет.
Коса симетрија: Укупан проток од до мора бити супротан од протока од до (видети пример).
Очување протока: Под условом да није или . Укупан проток ка неком чвору је нула, осим ако чвор није извор, који "ствара" проток, или понор, који "троши" проток.

Ово значи да је проток кроз мрежу дозвољени проток након сваког понављања у алгоритму. Дефинишемо резидуалну мрежу као мрежу капацитета и без протока. Обратите пажњу да може да се деси да проток од до буде дозвољен у резидуалној мрежи, иако недозвољен у почетној мрежи: ако и тада .

Алгоритам Форд-Фулкерсон

Улаз Граф са капацитетима , извором и понором
Излаз Проток од до који је максималан
  1. за све гране
  2. Док постоји путања од до у , таква да је за све гране :
    1. Пронађи
    2. За сваку грану
      1. (Пошаљи проток дуж путање)
      2. (Проток се може "вратити" касније)

Путања из корака 2 се може пронаћи уз помоћ претраге у ширину или претраге у дубину у графу . Уколико користите претрагу у ширину, онда се зове Едмондс-Карп алгоритам.

Када се више ниједна путања не може пронаћи, тада се из не може доћи до у резидуалној мрежи. Ако је скуп чворова до којих се може доћи у резидуалној мрежи из , онда је укупан капацитет почетне мреже грана од до са једне стране једнак укупном протоку који смо пронашли од до , а са друге стране служи као горња граница за све такве протоке. То доказује да је проток који смо пронашли максималан.

Главни пример[уреди | уреди извор]

Следећи пример показује прве кораке Форд-Фулкерсона у проточној мрежи са 4 чвора, извором и понором . Овај пример показује понашање алгоритма у најгорем случају. У сваком кораку се корз мрежу шаље проток од . Да смо искористили претрагу у ширину, само два корака би била потребна.

Путања Капацитет Резултујућа мрежа
Почетна мрежа




Након још 1998 корака…
Коначни изглед мреже.

Приметите како се проток "гура назад" од до када се тражи путања .

Незавршавајући пример[уреди | уреди извор]

Размотрите мрежу дату десно, са извором , понором и капацитетима грана , и , и , респективно, а капацитет свих других грана је неко . Константа је одабрана, јер . Користимо појачавајуће путање у складу са датом табелом, где је , и .

Корак Појачавајућа путања Послат проток Преостали проток
0
1
2
3
4
5

Обратите пажњу на то да након корака 1, као и након корака 5, преостали капацитет грана , и су у облику , and , за неко . Ово значи да можемо користити појачавајуће путање , , и бесконачно много пута и преостали капацитет ових грана ће увек имати исти облик. Укупан проток у мрежи након корака 5 је . Ако наставимо да користимо ове појачавајуће путање, укупан проток тежи ка , док је максималан проток . У овом случају се алгоритам никад не завршава, а проток ни не тежи ка максималном протоку.[1]

Додатни примери[уреди | уреди извор]

Слика 1.1
Слика 1.2 / Иницијализација

Пример 1.[уреди | уреди извор]

Пронађимо максималан проток за мрежу са Слике 1.1

1. Итерација :

У кораку 1, поставили смо да за сваки чвор претходник и резерва буду једнаки симболу -, да резерва за буде једнака " ∞ " и да је S = {}. Тада имамо мрежу са Слике 1.2

У кораку 2, проверавамо да скуп S није празан и бирамо из S.

У кораку 3, стављамо да резерва за буде једнака min(7, ∞) = 7 и да претходник од буде чвор . Смештамо у S. Такође стављамо да резерва за буде једнака min(6, ∞) = 6, а да претходник од буде једнак . Смештамо у S.

Кораци 4,5 се не могу применити (4. корак зато што немамо ни једну неправилно оријентисану грану, а 5. корак јер није означен), па се враћамо на корак 2.

У кораку 2, проверавамо да скуп С није празан, а затим из S бирамо неки чвор, рецимо . Скуп С сада садржи само

У кораку 3, стављамо да резерва за буде једнака min(3, 7) = 3 и да претходник од буде чвор . Смештамо у S тако да је S = {,}. Поново се Кораци 4,5 не могу применити, тако да се враћамо на Корак 2.

У кораку 2, проверавамо да скуп С није празан и бирамо неки чвор, рецимо из S. Скуп S поново садржи само .

У кораку 3, стављамо резерву за на min(3,5) = 3 и претходник од постаје чвор . Не смештамо у S.

У кораку 4, означили бисмо али је већ означено.

У кораку 5, видимо да је означено и користећи функцију претходника, откривамо да имамо ланац са тежинама грана (7,0) → (3,0) → (5,0) и додајуци 3, резерву за протоку сваке гране тј. другој кординати гране, добијамо ланац са тезинама (7,3) → (3,3) → (5,3) што нам даје мрежу са Слике 1.3.

Слика 1.3
Слика 1.4

2. Итерација:

Сада се враћамо на корак 1, где поново подешавамо ознаке и претходнике и нека је S = {}.

У кораку 2, бирамо а из S.

U кораку 3, подешавамо да резерва за буде једнака min(∞,7-3) = 4 и да претходник од буде чвор . Смештамо у S. Такође стављамо да резерва за буде једнака 6 и стављамо да претходник од буде чвор . Смештамо у S.

Кораци 4,5 се не могу применити, тако да се враћамо на корак 2.

У кораку 2, бирамо из С. Како проток од до једнак капацитету гране од до , не можемо да применимо корак 3 и означимо .

У кораку 2, бирамо из S.

У кораку 3, подешавамо да резерва за буде једнака min(6,2) = 2 и да претходник од буде чвор . Не смештамо у S.

У кораку 5, видимо да је означено и користећи функцију претходника, откривамо да имамо ланац са тежинама грана (6,0) → (2,0) и додајуци 2, резерву за протоку сваке гране тј. другој кординати гране, добијамо ланац са тежинама грана (6,2) → (2,2) што нам даје мрежу са Слике 1.4.

Слика 1.5
Слика 1.6

3. Итерација:

Сада се враћамо на корак 1 и понављамо поступак док не добијемо ланаc са тежинама грана (6,2) → (2,0) → (5,3) и додајући 2, резерву од протоку сваке гране, добијамо ланац са тежинама грана (6,4) → (2,2) → (5,5) што нам даје Слику 1.5

4. Итерација:

Сада се враћамо на корак 1 где поново поништавамо ознаке и претходнике и постављамо S = {}.

У кораку 2, бирамо из S.

У кораку 3, као и раније, стављамо да резерва за буде једнака 4 и да претходник од буде чвор . Смештамо у S. Такође стављамо да резерва за буде једнака 6 - 4 = 2 и смештамо у S.

Враћамо се на корак 2. Бирамо из S. Како је проток од до једнак капацитету гране од до , не можемо да означимо , тако да треба да се вратимо на корак 2.

Корак 2, бирамо из S. Како је проток од до једнак њиховом капацитету, не можемо да означимо и . Поново се враћамо на корак 2, али је скуп S празан, тако да смо завршили.

Коначан проток приказан је на Слици 1.6 и он представља збир протока у свим итерацијама тј. у овом случају 3 + 2 + 2 = 7.

Слика 2.1

[[Датотека:Slika 2.2.png|350п|thumbnail|center|Љ

ПРИМЕР 2. (са неправилно оријентисаном граном)[уреди | уреди извор]

Пронађимо максималан проток за мрежу са Слике 2.1

1. Итерација :

Овога пута даћемо мање детаља за неколико првих итерација.

У првој итерацији означавамо (-,∞) , , , , , . То нам даје пут - и добијамо ланац са тежинама грана (9,0) → (4,0) → (8,0) и додајући 4, резерву од протоку сваке гране, добијамо ланац са тежинама грана (9,4) → (4,4) → (8,4) што нам даје Слику 2.2

Слика 2.3
Слика 2.4

2. Итерација :

У другом пролазу означавамо (-,∞), , , , , . То нам даје пут - и добијамо ланац са тежинама грана (8,0) → (9,0) → (7,0) и додајући 7, резерву од протоку сваке гране, добијамо ланац са тежинама грана (8,7) → (9,7) → (7,7) што нам даје Слику 2.3

3. Итерација :

У трећем пролазу означавамо (-,∞) , , , , , . То нам даје пут - и добијамо ланац са тежинама грана (8,7) → (4,0) → (8,4) и додајући 1, резерву од протоку сваке гране, добијамо ланац са тежинама грана (8,8) → (4,1) → (8,5) што нам даје Слику 2.4.

Слика 2.5
Слика 2.6

4. Итерација :

Од сада постаје интересантно тако да ћемо обратити пажњу на детаље. Означавамо али не можемо да означимо и смештамо само у S. Бирајући из S, не можемо да означимо , али означавамо и смештамо у S. Бирајући из S, не можемо да означимо , али како није означено, можемо то да учинимо . Како грана (,) није правилно оријентисана, допуштамо да резерва од буде једнака min(7,5) = 5. Стављамо да претходник од буде једнак и смештамо у S (Корак 4. у Поступку). Бирајући из S, једини избор који имамо је да означимо и сместимо у S. Бирамо из S и означавамо .

То нам даје пут - и добијамо ланац са тежинама грана (9,4) → (6,0) → (9,7) ← (4,1) → (8,5) и како је резерва од једнака 3, сваком протоку додајемо 3, осим оном за грану (,). Пошто она није правилно оријентисана, одузимамо 3 од протока ове гране и добијамо ланац са тежинама грана (9,7) → (6,3) → (9,4) ← (4,4) → (8,8) што нам даје Слику 2.5.

5. Итерација :

Сада можемо да почнемо завршни пролаз. Почињемо са S = {}. Уклањамо из S, означавамо и смештамо у S. Бирамо из S, означавамо , не можемо да означимо из . Смештамо у S. Бирамо из S , означавамо пошто је резерва од једнака минимуму протока гране (,) и резерва од (Корак 4. у Поступку). Затим смештамо у S. Потом бирамо из S, али не можемо да извршимо било који од преосталих корака, па се враћамо на корак 2. Али скуп С је празан, па завршавамо. Сада имамо Слику 2.6.

Коначан проток приказан је на Слици 2.6 и он представља збир протока у свим итерацијама тј. у овом случају 4 + 7 + 1 + 3 = 15.

Сложеност[уреди | уреди извор]

Додајући појачавајуће путање протоку који је већ утврђен у графу доћи ћемо до максималног протока када више не будемо могли да пронађемо нове појачавајуће путање. Међутим, не постоји сигурност да ће се до ове ситуације икад доћи, тако да је једино сигурно да уколико се алгоритам заустави да ће одговор бити тачан. У случају да алгоритам ради бесконачно, може се десити да проток уопште не конвергира ка максималном протоку. Али, ова ситуација се догађа само са ирационалним вредностима протока. Када су капацитети цели бројеви, време извршавања је ограничено са (видети велико О), где је број грана у графу и максималан проток графа. Ово је зато што се појачавајућа путања може пронаћи у времена и повећати проток за целобројну вредност која је минимум .

Варијација Форд-Фулкерсон алгоритма који гарантује завршетак и временски је независна од максималног протока у графу је Едмондс-Карп алгоритам који раду времену .

Пајтон имплементација[уреди | уреди извор]

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))

Јава имплементација[уреди | уреди извор]

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++ имплементација[уреди | уреди извор]

#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;
}

Референце[уреди | уреди извор]

  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. 

Литература[уреди | уреди извор]

Спољашње везе[уреди | уреди извор]