• Nie Znaleziono Wyników

Zadanie: Przemytnicy

2.12.1. Maksymalny przepływ metod¸a Dinica

Algorytm Dinica służy do wyznaczania maksymalnego przepływu w czasie O(n2∗m). Pomysł jest następuj¸acy — dopóki w grafie istnieje ścieżka powiększaj¸aca między źródłem a ujściem:

0

Rysunek 2.12: (a) Graf nieskierowany o sześciu wierzchołkach. Przy krawędziach zaznaczone s¸a ich przepustowości. (b) Maksymalny przepływ wyznaczony dla sieci przedstawionej na ry-sunku (a). Skierowanie krawędzi reprezentuje kierunek przepływu, natomiast wartość przy krawędzi reprezentuje wielkość przepływu. Jak widać z rysunku, krawędzie (0, 3), (1, 4) oraz (2, 4) s¸a nienasycone — ich przepustowość jest większa od wartości przepływu przez nie płyn¸acego.

ˆ dokonuje się podziału zbioru wierzchołków grafu na warstwy. W kolejnych warstwach umieszczane s¸a wierzchołki o coraz większych odległościach od wierzchołka źródłowego (odległość między wierzchołkami u i v rozumiana jest jako liczba krawędzi znajduj¸acych się na najkrótszej ścieżce u ; v). Do warstwy zerowej należy wierzchołek źródłowy, do warstwy pierwszej — wierzchołki poł¸aczone krawędzi¸a ze źródłem, . . . , do warstwy k-tej — ujście. Podział na warstwy może być zrealizowany poprzez przeszukiwanie grafu wszerz w czasie O(n + m).

ˆ wyznacza się wszystkie ścieżki powiększaj¸ace między źródłem a ujściem, których wierz-chołki należ¸a do kolejnych warstw (0, 1, . . . k). W ten sposób wszystkie wyznaczone w jednej fazie algorytmu ścieżki powiększaj¸ace maj¸a tak¸a sam¸a długość k. Dla każdej krawędzi (u, v), przez któr¸a został zwiększony przepływ, zmniejsza się o tyle samo przepływ w krawędzi (v, u) (w kolejnych fazach może się okazać, że wyznaczenie maksy-malnego przepływu wymaga cofnięcia części aktualnego przepływu, co realizowane jest w algorytmie przez wprowadzenie ujemnego przepływu o przeciwnym zwrocie). Faza wyznaczania pojedynczej ścieżki powiększaj¸acej w przedstawionej tu implementacji re-alizowana jest poprzez przeszukiwanie grafu w gł¸ab.

ˆ usuwa się z grafu skierowane krawędzie nasycone, aby nie przetwarzać niepotrzebnie krawędzi, które nie mog¸a należeć do kolejnych ścieżek powiększaj¸acych.

Funkcja int Graph<V,E>::MaxFlow(int, int) przyjmuje jako parametry odpowiednio numery wierzchołków stanowi¸acych źródło oraz ujście wyznaczanego przepływu, a zwraca wielkość tego przepływu. Dodatkowo, dla każdej krawędzi w grafie wyznaczana jest wartość int f, która równa jest wielkości przepływu w tej krawędzi (w przypadku ujemnej wartości przepływu, jest to przepływ skierowany w przeciwn¸a stronę). Do działania funkcja wyma-ga wzbowyma-gacenia struktury wierzchołków o pole int t (jest ono wykorzystywane przez fazę przeszukiwania wszerz oraz w gł¸ab) oraz krawędzi o pole int c— przepustowości krawędzi, oraz int f — wyznaczona wartość przepływu. Grafy, na jakich operuje omawiana funkcja s¸a nieskierowane, jednak istnieje możliwość ustawiania różnych przepustowości krawędzi w różne strony — przed przyst¸apieniem do wyznaczania przepływu należy jedynie zmodyfikować odpowiednio wartości pól int ckrawędzi.

Listing 2.41: Implementacja funkcji int Graph<V,E>::MaxFlow(int, int) // Zmienna out reprezentuje numer wierzchołka-źródła

01 intout;

02#define ITER typename vector<Ed>::iterator

// Wektor itL zawiera dla każdego wierzchołka wskaźnik na aktualnie // przetwarzaną krawędź

03vector<ITER> itL;

04VI vis;

// Funkcja wykorzystuje czasy odwiedzenia wierzchołków z tablicy vis do // wyznaczania ścieżek poszerzających

05 intFlowDfs(intx, int fl) { 06 int r = 0, f;

// Jeśli aktualny wierzchołek jest ujściem, lub nie można powiększyć przepływu, // to zwróć aktualny przepływ

07 if (x == out || !fl) return fl;

// Przetwórz kolejne krawędzie wierzchołka w celu znalezienia ścieżki // poszerzającej

08 for (ITER & it = itL[x]; it != g[x].end(); ++it) {

// Jeśli krawędź nie jest nasycona i prowadzi między kolejnymi warstwami...

09 if (vis[x] + 1 == vis[it->v] && it->c - it->f) {

// Wyznacz wartość przepływu, który można przeprowadzić przez przetwarzaną // krawędź oraz zaktualizuj odpowiednie zmienne

10 it->f += f = FlowDfs(it->v, min(fl, it->c - it->f));

11 g[it->v][it->rev].f -= f;

12 r += f;

13 fl -= f;

// Jeśli nie można powiększyć przepływu to przerwij

14 if (!fl) break;

15 }

16 }

17 return r;

18}

// Funkcja wyznacza maksymalny przepływ między wierzchołkami s oraz f 19 intMaxFlow(ints, int f) {

// Inicjalizacja zmiennych 20 int res = 0, n = SIZE(g);

21 vis.resize(n);

22 itL.resize(n);

23 out = f;

24 REP(x, n) FOREACH(it, g[x]) it->f = 0;

25 int q[n], b, e;

26 while (1) {

// Ustaw wszystkie wierzchołki jako nieodwiedzone 27 REP(x, n) vis[x] = -1, itL[x] = g[x].begin();

// Wykonaj algorytm BFS zaczynając ze źródła s i analizując tylko // nienasycone krawędzie

28 for(q[vis[s] = b = e = 0] = s; b <= e; ++b)

29 FOREACH(it, g[q[b]]) if(vis[it->v] == -1 && it->c > it->f)

Listing 2.41: (c.d. listingu z poprzedniej strony) 30 vis[q[++e] = it->v] = vis[q[b]] + 1;

// Jeśli nie istnieje ścieżka do ujścia f, to przerwij działanie 31 if (vis[f] == -1) break;

// Zwiększ aktualny przepływ 32 res += FlowDfs(s, INF);

33 }

34 returnres;

35}

Listing 2.42: Dla grafu przedstawionego na rysunku 2.12.a, wywołanie funkcji int Graph<V,E>::MaxFlow(0, 5)wyznaczy następuj¸ac¸a wartość maksymalnego przepływu

Wielkosc calkowitego przeplywu: 15

Wartosci przeplywu dla kolejnych krawedzi:

f(0, 1) = 10 f(0, 3) = 5 f(1, 2) = 7 f(1, 4) = 3 f(2, 4) = 2 f(2, 5) = 5 f(3, 4) = 5 f(4, 5) = 10

Listing 2.43: Kod źródłowy programu użytego do wyznaczenia wyniku z listingu 2.42. Pełny kod źródłowy programu znajduje się w pliku maxflow.cpp

// Wzbogacenie struktury wierzchołków oraz krawędzi o elementy wymagane przez // algorytm Dinica

01 struct Ve { 02 intrev, c, f;

03};

04 struct Vs { 05 intt;

06};

07 int main() {

08 intn, m, s, f, b, e;

// Wczytaj liczbę wierzchołków i krawędzi w grafie oraz źródło i ujście // wyznaczanego przepływu

09 cin >> n >> m >> s >> f;

// Skonstruuj graf o odpowiedniej wielkości, a następnie dodaj do niego // wszystkie krawędzie

10 Graph<Vs, Ve> g(n);

11 Ve l;

Listing 2.43: (c.d. listingu z poprzedniej strony) 12 REP(x, m) {

13 cin >> b >> e >> l.c;

14 g.EdgeU(b, e, l);

15 }

// Wypisz wielkość przepływu między źródłem a ujściem

16 cout << "Wielkosc calkowitego przeplywu: " << g.MaxFlow(s, f) << endl;

17 cout << "Wartosci przeplywu dla kolejnych krawedzi:" << endl;

18 REP(x, SIZE(g.g)) FOREACH(it, g.g[x]) if(it->f > 0)

19 cout << "f(" << x << ", " << it->v << ") = " << it->f << endl;

20 return 0;