• Nie Znaleziono Wyników

Zadanie: Komiwojażer Bajtazar

2.4. Silnie spójne składowe

5

Poprawnym rozwi¸azaniem jest:

7

Ćwiczenia

Proste Średnie Trudne

acm.uva.es - zadanie 260 acm.uva.es - zadanie 871 acm.uva.es - zadanie 10410 acm.uva.es - zadanie 280 acm.uva.es - zadanie 10802 spoj.sphere.pl - zadanie 287 acm.uva.es - zadanie 459 spoj.sphere.pl - zadanie 38

acm.uva.es - zadanie 776 spoj.sphere.pl - zadanie 372

2.4. Silnie spójne składowe

Graf skierowany G = (V, E) nazywany jest silnie spójnym, Literatura [WDA] - 23.5

[ASD] - 7.3 jeżeli dla każdej pary wierzchołków u, v ∈ V z tego grafu,

ist-nieje ścieżka prowadz¸aca z wierzchołka u do wierzchołka v oraz z wierzchołka v do wierzchołka u (u ; v oraz v ; u).

Dla każdego skierowanego grafu G można dokonać takiego

podzi-ału jego wierzchołków na maksymalne (w sensie zawierania) rozł¸aczne zbiory, że grafy in-dukowane przez te zbiory stanowi¸a silnie spójne składowe (ang. strongly connected com-ponents). Wyznaczanie silnie spójnych składowych jest często istotne przy rozwi¸azywaniu różnych problemów grafowych, nie tylko ze względu na możliwość zredukowania wielkości problemu (poprzez niezależne analizowanie poszczególnych, silnie spójnych składowych), ale również ze względu na możliwość sprowadzenia grafu do postaci acyklicznej poprzez wyz-naczenie grafu silnie spójnych składowych (przykład takiego grafu przedstawiony jest na rysunku 2.4.b).

Do wyznaczania silnie spójnych składowych wykorzystuje się informacje wygenerowane przez algorytm DFS. W pierwszym kroku, dla wszystkich wierzchołków wyznacza się ich czasy przetworzenia (f) przez algorytm DFS, a następnie odwraca się skierowanie wszyst-kich krawędzi w grafie i wykonuje się algorytm DFS jeszcze raz, tym razem rozpoczynaj¸ac przeszukiwanie grafu z wierzchołków w kolejności malej¸acych wartości f. Każde pojedyncze drzewo przeszukiwania w gł¸ab z fazy drugiej stanowi jedn¸a silnie spójn¸a składow¸a.

Poprawność takiego algorytmu wynika z następuj¸acych trzech faktów:

ˆ jeśli w grafie istniej¸a ścieżki u ; v oraz v ; u, to wierzchołki u i v należ¸a do tej samej silnie spójnej składowej, a przeszukiwanie w gł¸ab umieści oba wierzchołki w tym samym drzewie DFS, gdyż bez względu na to który wierzchołek zostanie odwiedzony wcześniej podczas drugiej fazy algorytmu, drugi będzie jego potomkiem.

ˆ jeśli w grafie nie istnieje ścieżka u ; v, ani v ; u, to wierzchołki nie mog¸a znaleźć się w

tym samym drzewie przeszukiwań DFS oraz nie s¸a w tej samej silnie spójnej składowej.

ˆ jeśli istnieje tylko jedna ścieżka (dla ustalenia uwagi niech to będzie u ; v), to pierwsza faza algorytmu DFS przypisze mniejsz¸a wartość f wierzchołkowi v, co spowoduje, że w drugiej fazie, jako pierwszy, zostanie odwiedzony wierzchołek v. Ze względu na brak ścieżki v ; u, wierzchołki u i v trafi¸a do różnych drzew przeszukiwań DFS.

Funkcja void Graph<V,E>::SccS() dla każdego wierzchołka wyznacza numer silnie spójnej składowej do której on należy — informacja ta jest umieszczana w dodatkowej zmi-ennej wierzchołka int t. Implementacja tego algorytmu przedstawiona została na listingu 2.13.

Listing 2.13: Implementacja funkcji void Graph<V,E>::SccS() // Zmienna nr w pierwszej fazie algorytmu używana jest do pamiętania czasu // odwiedzania wierzchołków, natomiast w drugiej fazie algorytmu do numerowania // silnie spójnych składowych

01 intnr;

// Funkcja rekurencyjna, przeszukująca poddrzewo wierzchołka v. Jest ona // używana do realizacji obu faz przeszukiwania DFS

02 void SccSDfs(intv) {

// Jeśli wierzchołek nie był odwiedzony, to go odwiedź 03 if (g[v].t == -1) {

04 g[v].t = nr;

// Odwiedź wszystkie wierzchołki, do których prowadzi krawędź z v 05 FOREACH(it, g[v]) SccSDfs(it->v);

// Jeśli wykonywana jest pierwsza faza algorytmu, to ustaw zmienną t // wierzchołka na czas przetworzenia (odejmowanie wartości 3 gwarantuje // przydzielanie numerów poczynając od 0 wzwyż)

06 if (nr < 0) g[v].t = -(--nr) - 3;

07 } 08}

// Właściwa funkcja, wykonująca wyznaczanie silnie spójnych składowych 09 void SccS() {

// Zbuduj graf transponowany gt oraz ustaw wartości zmiennych t // wierzchołków na -1 (nieodwiedzone)

10 Graph<V, E> gt(SIZE(g));

11 REP(x, SIZE(g)) {

12 g[x].t = gt.g[x].t = -1;

13 FOREACH(it, g[x]) gt.EdgeD(it->v, x);

14 }

15 gt.nr = -2;

16 nr = 0;

17 VI v(SIZE(g));

// Wykonaj pierwszą fazę przeszukiwania w głąb na grafie transponowanym oraz // wypełnij wektor v numerami wierzchołków w kolejności rosnących czasów // przetworzenia DFS

18 REP(x, SIZE(g)) { 19 gt.SccSDfs(x);

20 v[gt.g[x].t] = x;

21 }

0 1 3

7 6 2 4

5

(a)

0 2

4 3 1

(b)

Rysunek 2.4: (a) przykładowy graf skierowany o ośmiu wierzchołkach. (b) graf silnie spójnych skład-owych wyznaczony dla grafu z rysunku (a). Wierzchołek 0 reprezentuje zbiór wierzchołków {0, 1}, 1 — {5}, 2 — {2, 3, 4}, 3 — {6}, 4 — {7}

Listing 2.13: (c.d. listingu z poprzedniej strony) // Wykonaj drugą fazę przeszukiwania w głąb na oryginalnym grafie.

22 FORD(x, SIZE(g) - 1, 0) { 23 SccSDfs(v[x]);

24 nr++;

25 } 26}

W wielu przypadkach, wyliczenie dla każdego wierzchołka v z grafu G numeru silnie spójnej składowej, do której on należy, nie jest wystarczaj¸ace. Często przydaje się również skonstruowanie grafu silnie spójnych składowych G0, w którym każdy wierzchołek reprezentu-je reprezentu-jedn¸a silnie spójn¸a składow¸a. Niech u0oraz v0będ¸a wierzchołkami w grafie G0silnie spójnych składowych grafu G. Wierzchołki te s¸a poł¸aczone skierowan¸a krawędzi¸a (u0, v0) wtedy, gdy w grafie G istniej¸a wierzchołki u oraz v poł¸aczone krawędzi¸a (u, v), należ¸ace odpowiednio do sil-nie spójnych składowych, reprezentowanych przez wierzchołki u0oraz v0. Graf silnie spójnych składowych dla przykładowego grafu z rysunku 2.4.a przedstawiony jest na rysunku 2.4.b.

Każdy graf silnie spójnych składowych jest acykliczny — gdyby bowiem istniał w nim cykl, to wszystkie wierzchołki na nim leż¸ace reprezentowałyby tę sam¸a silnie spójn¸a składow¸a.

FunkcjaGraph<V,E> Graph<V,E>::Scc(), której implementacja została przedstawiona na listingu 2.14, oprócz wyznaczania wartości zmiennych intt, jako wynik swojego działania zwraca dodatkowo graf silnie spójnych składowych. Wierzchołki tego grafu s¸a numerowane zgodnie z porz¸adkiem topologicznym (od wierzchołka o większym numerze nie prowadzi krawędź do wierzchołka o numerze mniejszym), co jest bardzo pożyteczn¸a własności¸a podczas rozwi¸azywania wielu zadań.

Listing 2.14: Implementacja funkcjiGraph<V,E> Graph<V,E>::Scc() // Wektor służący do odznaczania odwiedzonych wierzchołków

01VI vis;

// Wskaźnik do konstruowanego grafu silnie spójnych składowych 02Graph<V, E> *sccRes;

// Funkcja przechodząca graf algorytmem DFS. Jest ona wykorzystywana dwukrotnie -// w pierwszej fazie podczas wyznaczania kolejności wierzchołków dla drugiej fazy, // oraz podczas drugiej fazy - do wyznaczania silnie spójnych składowych oraz // konstrukcji grafu silnie spójnych składowych

03 void SccDfs(intv, intnr, bool phase) { // Zaznacz wierzchołek jako odwiedzony 04 g[v].t = 1;

// Jeśli wykonywana jest druga faza przeszukiwania, to ustaw dla wierzchołka // numer silnie spójnej składowej

05 if (!phase) vis[v] = nr;

// Odwiedź kolejne wierzchołki oraz (jeśli wykonywana jest druga faza) dodaj // krawędź do konstruowanego grafu silnie spójnych składowych

06 FOREACH(it, g[v]) if(g[it->v].t == -1) SccDfs(it->v, nr, phase);

07 else if (!phase && nr > vis[it->v])

08 sccRes->EdgeD(g[it->v].t, vis[it->v] = nr);

// Jeśli wykonywana jest pierwsza faza, to wstaw wierzchołek do listy, jeśli // natomiast druga, to zaktualizuj jego czas

09 if (phase) vis.PB(v);

10 else g[v].t = nr;

11}

// Funkcja wyznaczająca silnie spójne składowe w grafie 12Graph<V, E> Scc() {

// Graf gt to graf transponowany, natomiast res to konstruowany graf // silnie spójnych składowych

13 Graph<V, E> gt(SIZE(g)), res(SIZE(g)), *tab[] = { 14 this, &gt};

15 gt.sccRes = &res;

16 gt.vis.resize(SIZE(g), -1);

17 vis.clear();

// Budowa grafu transponowanego

18 REP(i, SIZE(g)) FOREACH(it, g[i]) gt.EdgeD(it->v, i);

// Przeprowadź dwie fazy algorytmu DFS...

19 REP(i, 2) {

// Zaznacz wierzchołki jako nieodwiedzone 20 FOREACH(it, tab[i]->g) it->t = -1;

21 intcomp = 0, v;

// Dla kolejnych, nieodwiedzonych wierzchołków, wykonaj przeszukiwanie 22 FORD(j, SIZE(g) - 1, 0)

23 if (tab[i]->g[v = (i ? vis[j] : j)].t == -1) 24 tab[i]->SccDfs(v, comp++, 1 - i);

25 if (i) res.g.resize(comp);

26 }

27 REP(i, SIZE(g)) g[i].t = gt.g[i].t;

Listing 2.14: (c.d. listingu z poprzedniej strony) 28 returnres;

29}

Złożoność czasowa obu przedstawionych funkcji — void Graph<V,E>::SccS() oraz Graph

<V,E> Graph<V,E>::Scc()jest liniowa ze względu na wielkość wejściowego grafu (O(n +

m)). Wynika to z faktu, że obie funkcje wykonuj¸a dwukrotnie zmodyfikowany algorytm DFS.

Funkcja void Graph<V,E>::SccS()nie tylko jest krótsza w implementacji, ale w praktyce okazuje się być szybsza, ze względu na brak konieczności konstruowania grafu wynikowego, dlatego też, jeśli rezultat przez ni¸a wyznaczany jest wystarczaj¸acy, nie należy korzystać z Graph<V,E> Graph<V,E>::Scc().

Listing 2.15: Wynik wyznaczony przez funkcjęGraph<V,E>::Scc()dla grafu z rysunku 2.4.a Wierzcholek 0 nalezy do silnie spojnej skladowej numer 0

Wierzcholek 1 nalezy do silnie spojnej skladowej numer 0 Wierzcholek 2 nalezy do silnie spojnej skladowej numer 2 Wierzcholek 3 nalezy do silnie spojnej skladowej numer 2 Wierzcholek 4 nalezy do silnie spojnej skladowej numer 2 Wierzcholek 5 nalezy do silnie spojnej skladowej numer 1 Wierzcholek 6 nalezy do silnie spojnej skladowej numer 3 Wierzcholek 7 nalezy do silnie spojnej skladowej numer 4 Graf silnie spojnych skladowych:

0: 2 3 4 1: 2 2:

3: 4 4:

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

01 struct Ve {};

// Wzbogacenie wierzchołków zawiera pole, w którym umieszczany jest numer // silnie spójnej składowej

02 struct Vs { 03 intt;

04};

05 int main() {

06 intn, m, s, b, e;

07 Ve ed;

// Wczytaj liczbę wierzchołków oraz krawędzi

08 cin >> n >> m;

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

// Dodaj do grafu wszystkie krawędzie 10 REP(x, m) {

11 cin >> b >> e;

12 g.EdgeD(b, e);

Listing 2.16: (c.d. listingu z poprzedniej strony) 13 }

// Wykonaj algorytm Scc oraz wypisz wyznaczony wynik 14 Graph<Vs, Ve> scc = g.Scc();

15 REP(x,n) cout << "Wierzcholek " << x <<

16 " nalezy do silnie spojnej skladowej numer " << g.g[x].t << endl;

17 cout << "Graf silnie spojnych skladowych: " << endl;

18 scc.Write();

19 return 0;

20}