• Nie Znaleziono Wyników

Zadanie: Wirusy

2.8. Ścieżka i cykl Eulera

Ścieżka Eulera w grafie G = (V, E), jest to dowolna ścież- Literatura [KDP] - 2.7 [ASD] - 7.4 [MD] - 6.2 ka przechodz¸aca przez wszystkie krawędzie tego grafu dokładnie

raz. W przypadku, gdy pierwszy wierzchołek na ścieżce jest równy wierzchołkowi ostatniemu, to ścieżkę Eulera nazywamy cyklem Eu-lera. W zależności od przyjętych założeń, ścieżka Eulera musi prze-chodzić przez wszystkie wierzchołki w grafie, lub też nie przechodzi

przez wierzchołki izolowane (czyli takie, z których nie wychodz¸a żadne krawędzie). W tym rozdziale zakładamy, że wierzchołki izolowane nie musz¸a być odwiedzone — dzięki takiemu podejściu, przed rozpoczęciem wyszukiwania ścieżki Eulera, nie musimy się martwić o elim-inowanie z grafu wierzchołków izolowanych, które często pojawiaj¸a się przypadkowo przy szybkim implementowaniu programów podczas zawodów.

Ścieżki oraz cykle Eulera można wyznaczać zarówno dla grafów skierowanych jak i nieskie-rowanych. Warunki na ich istnienie w grafie (oprócz spójności grafu z pominięciem ewentu-alnych wierzchołków izolowanych), s¸a następuj¸ace:

ˆ W grafie nieskierowanym cykl Eulera istnieje wtedy i tylko wtedy, gdy stopień każdego wierzchołka jest parzysty.

ˆ W grafie skierowanym cykl Eulera istnieje wtedy i tylko wtedy, gdy stopień wejściowy każdego wierzchołka jest równy stopniowi wyjściowemu.

0

4 2 3

1 6 5

(a)

0

4 2 3

1 6 5

(b)

Rysunek 2.8: (a) Graf skierowany zawieraj¸acy cykl Eulera 0 → 2 → 3 → 4 → 5 → 3 → 6 → 2 → 1 → 0.

(b) Graf skierowany nieposiadaj¸acy cyklu Eulera (stopień wejściowy wierzchołka 6 nie jest równy stopniu wyjściowemu). Graf ten zawiera ścieżkę Eulera 6 → 2 → 1 → 0 → 2 → 3 → 4 → 5 → 3.

ˆ W grafie nieskierowanym, ścieżka Eulera, niebęd¸aca cyklem Eulera, istnieje wtedy i tylko wtedy, gdy dwa wierzchołki maj¸a stopień nieparzysty, natomiast wszystkie inne wierzchołki — parzysty.

ˆ W grafie skierowanym, ścieżka Eulera niebęd¸aca cyklem Eulera istnieje wtedy i tylko wtedy, gdy jeden wierzchołek w grafie ma o jeden większy stopień wejściowy od wyjś-ciowego, jeden wierzchołek ma o jeden mniejszy stopień wejściowy od wyjśwyjś-ciowego, natomiast reszta wierzchołków ma równe stopnie wejściowe i wyjściowe.

Prezentowane algorytmy wyszukiwania ścieżki Eulera bazuj¸a na przeszukiwaniu grafu w gł¸ab. Główna różnica w podejściu, w stosunku do oryginalnego przeszukiwania DFS jest taka, że podczas wyznaczania ścieżki Eulera, wierzchołki mog¸a być odwiedzane wielokrot-nie, natomiast krawędzie — tylko raz. Dla kolejno odwiedzanych wierzchołków, wybierane s¸a krawędzie jeszcze nieodwiedzone. W momencie znalezienia się w wierzchołku, z którego nie da się wyjść (gdyż wszystkie krawędzie z niego wychodz¸ace zostały już przetworzone), algo-rytm wycofuje się z tego wierzchołka, jednocześnie dodaj¸ac krawędzie, po których się cofa, do wyznaczanej ścieżki Eulera. Jedyna różnica w sposobie realizacji algorytmu dla grafów nieskierowanych polega na tym, że przechodz¸ac krawędzi¸a (v, u), należy również zaznaczyć krawędź (u, v) jako odwiedzon¸a.

Wyznaczanie ścieżek Eulera w prosty sposób można zrealizować przy użyciu funkcji rekurencyjnej. Takie podejście jednak nie jest bezpieczne ze względu na możliwość przepełnie-nia stosu. Prezentowane w tym rozdziale funkcje — boolGraph<V,E>EulerD::(VI&)oraz bool Graph<V,E>::EulerU(VI&)s¸a iteracyjn¸a realizacj¸a przedstawionej wyżej koncepcji.

Obie funkcje przyjmuj¸a jako parametr referencję na wektor zmiennych typu int. W przy-padku, gdy w grafie nie istnieje ścieżka Eulera, omawiane funkcje zwracaj¸a fałsz. Gdy ścież-ka istnieje, zwracana jest prawda, natomiast prześcież-kazany przez referencję wektor wypełniony zostaje numerami, kolejno odwiedzanych na wyznaczonej ścieżce wierzchołków. W przypadku gdy ścieżka Eulera jest cyklem, pierwszy i ostatni element wektora s¸a równe (ścieżka kończy się w tym samym wierzchołku, co zaczyna). Odpowiednie implementacje przedstawione s¸a na listingach 2.28 oraz 2.29.

Listing 2.28: Implementacja funkcji boolGraph<V,E>::EulerD(VI&) 01 bool EulerD(VI &r) {

// Inicjalizacja wymaganych zmiennych 02 intv = -1, kr = 1, h;

03 r.clear();

Listing 2.28: (c.d. listingu z poprzedniej strony) 04 VI l, st(SIZE(g), 0);

// Dla wszystkich wierzchołków wyliczany jest stopień wejściowy 05 FOREACH(it, g) FOREACH(it2, *it) ++st[it2->v];

// Należy wyznaczyć wierzchołek v, od którego rozpoczęte // zostanie wyszukiwanie ścieżki Eulera.

06 REP(x, SIZE(g)) {

// Jeśli wierzchołek ma większy stopień wyjściowy od wejściowego, // to jest wierzchołkiem startowym

07 if ((h = SIZE(g[x])) > st[x]) v = x; else

// Jeśli wierzchołek ma jakieś wychodzące krawędzie oraz nie znaleziono // jeszcze wierzchołka początkowego, to użyj go jako wierzchołek startowy 08 if (h && v == -1) v = x;

09 kr += st[x] = h;

10 }

// Konstrukcja ścieżki Eulera jest rozpoczynana w wierzchołku v 11 if (v != -1) l.PB(v);

// Dopóki istnieją wierzchołki na stosie, przeszukuj graf metodą DFS 12 while(SIZE(l)) if (!st[v = l.back()]) {

13 l.pop back();

14 r.PB(v);

15 } else l.PB(v = g[v][--st[v]].v);

// Wyznaczona ścieżka Eulera została skonstruowana w odwrotnym kierunku, // więc trzeba ją odwrócić

16 reverse(ALL(r));

// Algorytm zwraca prawdę, jeśli wykorzystane zostały wszystkie krawędzie // w grafie

17 return SIZE(r) == kr;

18}

Listing 2.29: Implementacja funkcji bool Graph<V,E>::EulerU(VI&) 01 boolEulerU(VI & ce) {

// Inicjalizacja wymaganych zmiennych 02 int v = -1;

03 ce.clear();

04 VI l, st(SIZE(g), 0), of(SIZE(g) + 1, 0);

// Należy wyznaczyć wierzchołek v, od którego rozpoczęte zostanie // wyszukiwanie ścieżki Eulera

05 REP(x, SIZE(g)) {

06 of[x + 1] = of[x] + (st[x] = SIZE(g[x]));

07 if ((st[x] & 1) || (v == -1 && st[x])) v = x;

08 }

// Wektor służący do odznaczania wykorzystanych krawędzi.

09 vector<bool> us(of[SIZE(g)], 0);

// Konstrukcja ścieżki Eulera jest rozpoczynana w wierzchołku v 10 if (v != -1) l.PB(v);

// Dopóki istnieją wierzchołki na stosie, przeszukuj graf metodą DFS 11 while (SIZE(l)) {

Listing 2.29: (c.d. listingu z poprzedniej strony) 12 v = l.back();

// Dopóki kolejne krawędzie zostały już przetworzone, pomiń je 13 while(st[v] && us[of[v] + st[v] - 1]) --st[v];

// Jeśli nie ma już więcej krawędzi, to wyjdź z wierzchołka i dodaj krawędź, // po której się cofasz do wyniku

14 if (!st[v]) { 15 l.pop back();

16 ce.PB(v);

17 } else {

// Przejdź po jeszcze niewykorzystanej krawędzi 18 intu = g[v][--st[v]].v;

19 us[of[u] + g[v][st[v]].rev] = 1;

20 l.PB(v = u);

21 }

22 }

// Algorytm zwraca prawdę, jeśli wykorzystane zostały wszystkie krawędzie w // grafie

23 return2 * (SIZE(ce) - 1) == of[SIZE(g)];

24}

Zarówno w przypadku grafów skierowanych, jak i nieskierowanych, złożoność czasowa pro-cesu wyznaczania ścieżki Eulera to O(n + m) — każda krawędź w grafie przetwarzana jest jednokrotnie.

Dla grafu przedstawionego na rysunku 2.8.a, wywołanie funkcji bool Graph<V,E>::

EulerD(VI &l)spowoduje wypełnienie wektoralnastępuj¸acymi liczbami, reprezentuj¸acymi numery kolejno odwiedzanych wierzchołków na wyznaczonym cyklu Eulera:

{0, 2, 3, 4, 5, 3, 6, 2, 1, 0}

Gdyby ten sam graf potraktować jako nieskierowany, to zawartość wektoral po wywołaniu funkcji bool Graph<V,E>::EulerU(VI &l)byłaby następuj¸aca:

{0, 1, 2, 3, 5, 4, 3, 6, 2, 0}

Po usunięciu z grafu krawędzi ł¸acz¸acej wierzchołki 3i6 (graf ten przedstawiony jest na ry-sunku 2.8.b), wywołanie bool Graph<V,E>::EulerD(VI &l)spowoduje wypełnienie wek-toralnastępuj¸acymi liczbami:

{6, 2, 1, 0, 2, 3, 4, 5, 3}

Gdyby ten sam graf potraktować jako nieskierowany, to zawartość wektoral po wywołaniu funkcji bool Graph<V,E>::EulerU(VI &l)byłaby następuj¸aca:

{6, 2, 0, 1, 2, 3, 5, 4, 3}

Podane wyniki działania obu funkcji s¸a przykładowe i zależ¸a od kolejności wstawiania krawędzi do grafu.

Listing 2.30: Program prezentuj¸acy użycie funkcji bool Graph<V,E>::EulerD(VI &l). Pełny kod źródłowy tego programu znajduje się w pliku eulerpath.cpp.

// Zarówno krawędzie, jak i wierzchołki nie wymagają dodatkowych wzbogaceń 01 struct Ve { };

02 struct Vs { };

03 intmain() { 04 VI res;

05 int n, m, b, e;

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

06 cin >> n >> m;

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

// Dodaj do grafu odpowiednie krawędzie 08 REP(x, m) {

09 cin >> b >> e;

10 g.EdgeD(b, e);

11 }

// Jeśli graf zawiera ścieżkę Eulera - wypisz ją, jeśli nie - poinformuj o tym 12 if (g.EulerD(res)) {

13 FOREACH(it, res) cout << *it << " ";

14 cout << endl;

15 } else {

16 cout << "No Euler Path" << endl;

17 }

18 return 0;

19}

Listing 2.31: Program prezentuj¸acy użycie funkcji bool Graph<V,E>::EulerU(VI &l). Pełny kod źródłowy tego programu znajduje się w pliku eulerupath.cpp

// Wymagane wzbogacenie krawędzi w grafie nieskierowanym 01 struct Ve {

02 int rev;

03};

// Wierzchołki nie wymagają dodatkowych wzbogaceń 04 struct Vs { };

05 intmain() { 06 VI res;

07 int n, m, b, e;

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

08 cin >> n >> m;

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

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

11 cin >> b >> e;

12 g.EdgeU(b, e);

13 }

// Jeśli graf zawiera ścieżkę Eulera - wypisz ją, jeśli nie - poinformuj o tym 14 if (g.EulerU(res)) {

15 FOREACH(it, res) cout << *it << " ";

Listing 2.31: (c.d. listingu z poprzedniej strony)

16 cout << endl;

17 } else {

18 cout << "No Euler Path" << endl;

19 }

20 return0;

21}