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;