Wprowadzenie do Teorii Algorytmów
(Introduction to Algorithms Theory)
Prof. Dr hab. Alexander Prokopenya
Szkoła Główna Gospodarstwa Wiejskiego w Warszawie
Katedra Zastosowań Informatyki
Wykład 6-7. Algorytmy grafowe
Grafy sÄ… powszechnie stosowane w informatyce, a algorytmy grafowe zaliczajÄ… siÄ™ do podsta-
wowych w tej dziedzienie. Istenieją setki interesujących problemów obliczeniowych, które wy-
raża się w języku grafów. Rozważmy dla przykładu zadanie pokolorowania mapy politycznej.
Jaka jest minimalna liczba kolorów potrzebna do pokolorowania mapy z oczywistym warun-
kiem, że kraje sąsiadujące mają różne kolory? Jedna z trudności, która pojawia się przy próbie
rozwiązania problemu, jest związana z tym, że nawet zredukowana wersja mapy, taka jak na ry-
sunku 1, jest zwykle zaśmiecona nieistotnymi informacjami, jak na przykład: skomplikowane
granice, punkty graniczne, w których zbiega się trzy lub więcej krajów, otwarte morza, wijące
sie rzeki. Tych zakłóceń nie ma w matematycznym modelu z rysunku 1, jakim jest graf z jed-
nym wierzchułkiem dla każdego kraju (1 to Brazylia, 11 to Argentyna) oraz krawędziami mię-
dzy sąsiadami. Zawiera on jedynie informacje potrzebne przy kolorowaniu i nic więcej. Precy-
zyjnym celem jest teraz przypisanie wierzchołkom grafu kolorów tak, aby żadna krawędz nie
miała końców w tym samym kolorze.
Rys. 1. Mapa oraz jej graf
Kolorawanie grafu nie jest wyłącznie zadaniem projektantów map. Przypuśćmy, że uni-
wersytet potrzebuje zaplanować egzaminy dla wszystkich grup i chce przeprowadzić je w moż-
liwe najkrótszym czasie. Jedynym ograniczeniem jest, że jeśli jakiś student będzie podchodził do
dwóch egzaminów, to nie mogą one odbywać się w tym samym czasie. Aby wyrazić powyższy
problem za pomocą grafu, przypiszemy jednemu wierzchółkowi jeden egzamin oraz wstawimy
1
krawędz między dwa wirzchółki, dla których ma miejsce konflikt, tzn. gdy istnieje osoba zdająca
egzaminy odpowiadające obu końcom krawiędzi. Przypuśćmy, że każdy przedział czasu, w któ-
rym odbywa się jakiś egzamin, ma przypicany unikatowy kolor. Wtedy przypisanie przedziałów
czasu egzaminom to dokładnie pokolorowanie grafu.
Pewne podstawowe operacje na grafach pojawiają się tak często i w tak przeróżnych kon-
tekstach, że wiele wysiłków włożono w znalezenie dla nich efektywnych procedur. W niniej-
szym wykładzie zajmiemy sie najbardziej podstawowymi algorytmami ukazującymi podstawo-
wą, spójną strukturę grafu.
Formalnie graf jest zdefiniowany jako zbiór wierzchołków (czasem nazywanych także
węzłami) V oraz zbiór krawędzi E między wybranymi parami wierzchołków. Na przykładzie z
mapa V =ð {1, 2, ..., 13}, a zbiór E zawiera miÄ™dzy innymi {1, 2}, {9, 11} oraz {7, 13}. KrawiÄ™dz
między wierzchołkami x oraz y oznacza, że x ma granicę z y . Jest to relacja symetryczna im-
plikuje, że także y ma granicÄ™ z x oznaczamy jÄ…, używajÄ…c notacji dla zbiorów, tzn. c =ð {x, y}.
Takie krawiędzie są nieskierowane i są częścią grafu nieskierowanego.
Czasami graf opisuje relację, która nie ma wzajemności, w takim przypadku koniecznie
jest użycie krawędzi z oznaczeniem kierunku. Wówczas graf może zawierać skierowaną kra-
wÄ™dz c z x do y (co zapisujemy c =ð (x, y) ) albo z y do x (co zapisujemy (y, x) ), albo obie.
Szczególnym przypadkiem olbrzymiego grafu skierowanego jest graf zależności w sieci World
Wide Web. Ma on wierzchołek dla każdej strony w Internecie oraz skierowaną krawędz (u, v) ,
kiedy ze strony u istnieje połączenie za stroną v; łącznie miliardy wierzchołków i krawędzi! Zro-
zumienie najbardziej podstawowych, spójnych własności sieci WWW jest wielkim ekonomicz-
nym i spółecznym wyzwaniem. Chociaż rozmiar problemu jest zniechęcający, zobaczymy nie-
długo, że wiele wartościowych informacji o strukturze grafu, na szczęście, można określić w
czasie liniowym.
1. Reprezentacje grafów
IstniejÄ… dwa standartowe sposoby reprezentowania grafu G =ð (V, E) : za pomocÄ… list sÄ…siedztwa
(ang. adjacency list) lub macierzy sÄ…siedztwa (ang. adjacency matrix). JeÅ›li mamy n =ð|V |
wierzchoÅ‚ków v1, v2 , ..., vn , to macierz sÄ…siedztwa grafu jest tablicÄ… n´ðn , której element (i, j)
jest równy
1, jesli istnieje krawiedz z vi do vj
ìð
aij =ð
íð
w przeciwnym razie
îð0,
Dla grafów nieskierowanych macierz ta jest symetryczna, ponieważ krawiędz {u, v} można po-
traktować jako krawiędz w obu kierunkach.
Największą zaletą tej reprezentacji jest to, że istnienie krawiędzi może być sprawdzone w
czasie stałym, z jednym dostępem do pamięci. Jednakż macierz zajmuje O(n2) miejsca, co jest
marnotrawstwem pamięci, gdy graf nie ma zbyt dużo krawiędzi.
Alternatywną reprezentacjaą grafu, której rozmiar jest proporcjonalny do liczby krawię-
dzi, są listy sąsiedztwa. Zawierają one |V | list z dowiązaniami, jedną dla każdego wierzchołka.
Na liście sąsiedztwa wierzchołka u są przechowywane nazwy wierzchołków, do których z u jest
2
wychodzÄ…ca krawiÄ™dz tzn. wierzchoÅ‚ki v, dla których (u, v) Îð E . Każda krawiÄ™dz pojawia siÄ™
na dokładnie jednej liście z dowiązaniami, gdy graf jest skierowany, a na dwóch listach, gdy graf
jest nieskierowany. W obu przypadkach całkowity rozmiar struktury wynosi O(| E |) . Wyszuki-
wanie konkretnej krawiędzi nie zajmuje już czasu stałego, gdyż wymaga przefiltrowania listy
sąsiedztwa dotyczącej wierzchołka u. Jednakże łatwe jest powtórzenie obliczeń dla wszystkich
sąsiadów danego wierzchołka (przez przeglądanie odpowiednej listy), co jak zobaczymy póżniej,
jest bardzo użyteczną operacją w algorytmach grafowych. Dla grafów nieskierowanych repre-
zentacja jest symetryczna: v jest na liście sąsiedztwa u wtedy i tylko wtedy, gdy u jest na liście
sÄ…siedztwa v.
Która z dwu opisanych reprezentacji jest lepsza: macierz sąsiedztwa czy listy sąsiedztwa?
Zależy to od relacji między liczbą wierzchołków grafu | V | oraz liczbą krawiędzi | E | . Zazwy-
czaj preferuje się reprezentację listową, ponieważ umożliwia ona przedstawienie w zwarty spo-
sób grafów rzadkich (ang. sparse) to jest takich, dla których | E | jest dużo mniejsze od | V |2 .
Kiedy graf jest gęsty (ang. dense) | E | jest blizkie | V |2 lub gdy istnieje potrzeba szybkiego
stwierdzania, czy istnieje krawiędz łącząca dwa dane wierzchołki, wtedy może być korzystniej-
sza reprezentacja macierzowa.
2. Przeszukiwanie grafu nieskierowanego
2.1. Przeszukiwanie wszerz
Przeszukiwanie wszerz jest jednym z najprostszych algorytmów przeszukiwania grafu i pierwo-
wzorem wielu innych algorytmów grafowych. Dane sÄ… graf G =ð (V, E) i wyróżniony wierzcho-
łek początkowy s (nazywany dalej zródlem). W przeszukiwaniu grafu wszerz są systematycznie
badane krawiędzie G w celu odwiedzania każdego wierzchołka, który jest osiągalny z s. Jedno-
cześnie są obliczne odleglości (najmniejsza liczba krawiędzi) od zródła s do wszystkich osiągal-
nych wierzchołków. Wynikiem działania algorytmu jest też dzewo przeszukiwania wszerz o ko-
rzeniu w s, które zawiera wszystkie osiągalne wierzchołki. Dla każdego wierzchołka v osiągal-
nego z s ścieżka w drzewie przeszukiwania wszerz od s do v odpowiada najkrótszej ścieżkie od s
do v w G, tzn. ścieżce zawieralącej najmniejszą możliwą liczbę krawiędzi. Algorytm przeszuki-
wania wszerz działa zarówno dla grafów skierowanych, jak i nieskierowanych.
Nazwa przeszukiwanie wszerz bierze się stąd, że w tym algorytmie granica między
wierzchołkami odwiedzonymi i nie odwiedzonymi jest przekraczana jednocześnie na calej jej
szerokości. To znaczy, wierzchołki w odległości k od zródla są odwiedzane przed wierzchołkami
w odlegÅ‚oÅ›ci k +ð1.
Podczas przeszukiwania wszerz każdy wierzchołek jest kolorowany na biało, szaro lub
czarno. Początkowo wszystkie wierzchołki są białe i mogą potem zmienić kolor na szary lub
czarny. Podczas przeszukiwania każdy nowo napotkany wierzchołek staje się odwiedzony, a je-
go kolor zmienia się na inny niż biały. Zatem wierzchołki szare i czarne są już odwiedzone, ale
rozróżnia siÄ™ je w celu zapewnienia poprawnoÅ›ci wykonywania przeszukiwania. JeÅ›li (u,v) Îð E
i wierzchołek u jest czarny, to wierzchołek v jest albo szary albo czarny; tzn. wszystkie wierz-
chołki sąsiadujące z u są już odwiedzone. Wierzchołki szare mogą mieć białych sąsiadów; two-
rzą one granicę między odwiedzonymi i nie odwiedzonymi wierzchołkami.
Algorytm przeszukiwania wszerz buduje drzewo przeszukiwania wszerz, poczÄ…tkowo
zawierające tylko korzeń, który jest wierzchołkiem zródłowym s. Gdy podczas przeglądania listy
sąsiedztwa odwiedzonego wierzchołka u napotkany jest nie odwiedzony wierzchołek v, krawiędz
(u,v) i wierzchołek v są dodawane do drzewa. Mówimy, że u jest poprzednikiem lub ojcem v
w drzewie przeszukiwania wszerz. Ponieważ wierzchołek jest odwiedzany co najmniej raz, ma
zatem co najwyżej jednego ojca. Relacje przodek-potomek w drzewie przeszukiwania wszerz są
definiowane względem korzenia s jak zazwyczaj: jeśli u znajduje się na ścieżce prowadzącej od
s do wierzchołka v, to u jest przodkiem v i v jest potomkiem u.
3
W algorytmie przeszukiwania wszerz BFS (ang. breadth-first search) zakłada się, że wej-
Å›ciowy graf G =ð (V, E) jest reprezentowany przez listy sÄ…siedztwa. Z każdym wierzchoÅ‚kiem w
grafie wiążemy kilka dodatkowych zmiennych. Kolor każdego wierzchoÅ‚ka u ÎðV jest pamiÄ™ta-
ny w zmiennej color[u], a poprzednik u jest pamiÄ™tany w zmiennej pð[u] . JeÅ›li u nie ma po-
przednika (np. jeÅ›li u =ð s lub u nie zastaÅ‚ odwiedzony), to pð[u] =ð NIL . Odleglość od zródÅ‚a s do
wierzchołka u jest obliczana w zmiennej d[u]. W algorytmie jest używana też kolejka Q typu
FIFO, w której pamięta się szare wierzchołki.
Implementacja kolejki w tablicy Q[1..n]: Atrybut head[Q] takiej kolejki wskazuje na jej głowę,
tj. początek, natomiast atrybut tail[Q] wyznacza następną wolną pozycję, na którą można wsta-
wić do kolejki nowy element. Elementy kolejki znajdują się na pozycjach head[Q],
head[Q]+ð1, ..., tail[Q]-ð1; umawiamy siÄ™ tutaj, że tablica Q jest cykliczna , tzn. pozycja o
numerze 1 jest bezpoÅ›rednim nastÄ™pnikem pozycji o numerze n. JeÅ›li head[Q] =ð tail[Q], to ko-
lejka jest pusta. PoczÄ…tkowo head[Q] =ð tail[Q] =ð 1. Jeżeli kolejka jest pusta, to próba usuniÄ™cia
elementu z kolejki za pomocą operacji Dequeue (element może zostać usunięty z kolejki tylko
wtedy, gdy znajduje się na jej początku) jest sygnalizowana jako błąd niedomiaru. Jeśli
head[Q] =ð tail[Q]+ð1, to kolejka jest peÅ‚na. Próba wstawienia nowego elementu do kolejki za
pomocą operacji Enqueue (zostaje on umieszczony na końcu kolejki) jest w takiej sytuacji sy-
gnalizowana jako błąd przepełnienia.
BFS(G, s)
1 for każdy wierzchoÅ‚ek u ÎðV[G]-ð{s}
2 do color[u] := Biały
3 d[u] :=ð Ä„ð
4 pð[u] =ð NIL od
5 color[s] := Szary
6 d[s] :=ð 0
7 pð[s]:=ð NIL
8 Q := {s}
9 while Q Ä…ð Ćð
10 do u :=ð head[Q]
11 for każdy v Îð Adj[u]
12 do if color[v] = Biały
13 then color[v] := Szary
14 d[v]:=ð d[u]+ð1
15 pð[v] :=ð u
16 Enqueue(Q, v) fi od
17 Dequeue(Q)
18 color[u] := Czarny od
Rysunek 2 ilustruje działanie procedury BFS na przykładowym grafie.
4
Rys. 2. Działanie procedury BFS na grafie nieskierowanym. Krawiędzie drzewowe tworzone
przez BFS są zacieniowane. W każdym wierzchołku u jest podana wartość d[u]. Stan kolejki Q
przedstawiony na początku każdej iteracji instrukcji while (wiersze 9-18). Poniżej wierzchołków
w kolejce znajdują się ich odległości od zródła.
Procedura BFS działa w następujący sposób. W wierszach 1-4 każdy wierzchołek u różny
od korzenia jest kolorowany na biało, zmienna d[u] jest inicjowana na nieskończoność, a
wskaznik pð[u] do ojca u przyjmuje wartość NIL. W wierszu 5 zródÅ‚o s jest kolorowane na sza-
ro, ponieważ przyjmuje się, że s zostaje odwiedzony na początku działania procedury. W wier-
szu 6 odległość d[s] jest inicjowana na 0, a w wierszu 7 wskaznik do ojca s przyjmuje wartość
NIL. W wierszu 8 jest inicjowana kolejka Q. Początkowo Q zawiera tylko wierzchołek s, pózniej
Q zawiera zawsze szare wierzchołki.
Główna pętla programu jest zapisana w wierszach 9-18. Pętla jest wykonywana tak dłu-
go, jak długo istnieją szare wierzchołki. Szare wierzchołki są wierzchołkami odwiedzonymi, któ-
rych listy sąsiedztwa nie zostały jeszcze w całości przejrzane. W wierszu 10 jest pobierany szary
wierzchołek u z początku kolejki Q. Każdy wierzchołek v na liście sąsiedztwa u jest rozważany
w pętli for w wierszach 11-16. Jeśli v jest biały, to nie był dotychczas odwiedzony. Wierzchołek
v jest kolorowany na szaro, a d[v] przyjmuje wartość d[u]+ð1. NastÄ™pnie wierzchoÅ‚ek u jest za-
pamiętywany jako ojciec wierzchołka v. Na koniec v jest umieszczany jako ostatni element w
kolejce Q. Kiedy wszystkie wierzchołki z listy sąsiedztwa u są już zbadane, jest usuwany z Q i
kolorowany na czarno (wiersze 17-18).
Operacje wstawiania do kolejki i usuwania z kolejki zajmujÄ… czas O(1) . StÄ…d lÄ…czny czas
wykonywania operacji na kolejce wynosi O(V ) . Ponieważ lista sąsiedztwa każdego wierzchołka
5
jest przeglądana tylko wtedy, gdy wierzchołek zostanie usunięty z kolejki, lista sąsiedztwa każ-
dego wierzchołka jest przeglądana co najwyżej raz. Ponieważ suma długości wszystkich list są-
siedztwa wynosi Qð(E) , Å‚Ä…czny czas spÄ™dzany na przeglÄ…daniu list sÄ…siedztwa wynosi O(E) . Ini-
cjowanie zabiera czas O(V ) i dlatego Å‚Ä…czny czas dzialania procedury BFS wynosi O(V +ð E) .
Tak więc przeszukiwanie wszerz jest wykonywane w czasie liniowo zależnym od rozmiaru re-
prezentacji liniowej grafu G(V, E) .
2.2. Przeszukiwanie w głąb
Przeszukiwanie w głąb (ang. depth-first search) jest zaskakująco wszechstronną procedurą dzia-
łająca w czasie liniowym, która polega na sięganiu głębiej w graf, jeżeli jest to tylko możliwe.
Najbardziej podstawowe pytanie, na które znajduje odpowiedz, brzmi następująco:
Jaka część grafu jest osiągalna z danego wierzchołka?
Aby zrozumieć, na czym polega to zadanie, spróbujmy postawić się na miejscu kompute-
ra, któremu właśnie dano nowy graf, powiedzmy w postaci list sąsiedztwa. Wykorzystując tę
reprezentację, można wykonać tylko jedną podstawową operację: wyszukiwanie sąsiadów
wierzchołka. Z tym podstawowym narzędziem problem osiągalności da się porównać do cho-
dzenia po labiryncie (Rys. 3). Rozpoczynamy w ustalonym miejscu, a po dotarciu do skrzyżo-
wania (wierzchołka), dostrzegamy dużą liczbę korytarzy (krawędzi), którymi możemy iść dalej.
Nieostrożny wybór korytarza może prowadzić do chodzenia w kółko lub spowodować przega-
pienie pewnej osiągalnej części labiryntu. Jasne jest zatem, że podczas przeglądania musimy za-
pisywać pewne informacje.
Rys. 3. Badanie grafu przypomina nawigacjcÄ™ w labiryncie
Każdy wie, że wszystko, czego potrzebujemy do przejścia labiryntu, to kłębek sznurka i
kawałek kredy. Zaznaczając kredą skrzyżowania, które już odwiedziliśmy, chronimy się przed
zapętleniem. Używając sznurka, możemy zawsze wrócić do punktu wyjścia oraz do korytarzu,
które ostatnio widzieliśmy, a które nie zostały jeszcze zbadane.
W jaki sposób możemy symulować za pomocą komputera te prymitywne narzędzia, ja-
kimi są kreda i sznurek? Ślady kredą łatwo jest zrealizować: dla każdego wierzchołka przecho-
wujemy zmienną boolowską wskazującą, czy dany wierzchołek był już przeglądany. Odpowied-
nią informatyczną analogią do kłębka sznurka jest stos. Za pomocą sznurka mamy realizować
dwie podstawowe operacji rozwijanie, aby dotrzeć do kolejnego skrzyżowania (odpowiedni-
6
kiem dla stosu jest operacja push wstaw nowy wierzchołek), i zawijanie, by wrocić do ostat-
niego skrzyżowania (operacja pop zdejmij ze stosu).
W procedurze użyjemy stosu w sposób pośredni poprzez rekursję (która jest implemen-
towana z użyciem stosu aktywnych rekordów). Uzyskany algorytm jest przedstawiony na rysun-
ku 4. Procedury previsit oraz postvisit są opcjonalne, służą one do oznaczenia operacji wykony-
wanych na wierzchołku, kiedy jest on odkryty po raz pierwszy i kiedy wychodzimy z nego po
raz ostatni. Zobaczymy wkrótce ciekawe ich zastosowania.
procedura explore (v)
WejÅ›cie: G =ð (V, E) jest grafem; v ÎðV
Wyjście: visited(u) ma wartość true dla wszystkich wierzchołków u osiągalnych z v
visited(v) := true
previsit(v)
for każda krawÄ™dz (v,u) Îð E
do if not visited(u) then explore(u) fi od
postvisit(v)
Najpierw należy potwierdzić, że operacja explore zawsze działa poprawnie. Z pewnością
nie jest to zbyt trudne, gdyż poruszamy się jedynie z wierzchołków do ich sąsiadów oraz nigdy
nie wskoczymy do obszaru nieosiągalnego z v. Ale czy znajdziemy wszystkie wierzchołki osią-
galne z v? Załóżmy, że jest pewien u, który został pominięty, wybierzmy ścieżkę z v do u,
spójrzmy na ostatni wierzchołek na tej ścieżce, który był przeglądany, i oznaczmy go przez z.
Niech w będzie wierzchołkiem następnym po z na tej samej ścieżce.
Wiemy, że z był przeglądany, natomiast w nie był. Mamy sprzeczność. Kiedy operacja explore
była w wierzchołku z, zauważyłaby w oraz poszła go odwiedzić.
Na rys. 4 przedstawiony jest wynik działania procedury explore na naszym przykłado-
wym grafie, stratującej w węzle A i przeglądającej wierzchołki w porządku alfabetycznym, kiedy
jest możliwość wybory kolejnego wierzchołka. Krawiędzie proste to te, przez które algorytm
wędrował, każda z nich była związana z wywołaniem operacji explore i prowadziła do odwie-
dzenia nowego wierzchołka.
Rys. 4. Wynik działania procedury explore(A) na grafie z rys. 3
Na przykład, podczas przeglądania B została zauważona krawiędz B-E, a ponieważ E nie
był jeszcze przeglądany, algorytm przeszedł ją przez wywołanie explore(E). Krawędzie zazna-
czone na rysunku linią ciągła tworzą drzewo (spójny graf bez cykli) i dlatego są nazywane kra-
7
wędziami drzewowymi. Krawędzie zaznaczone linią przerywaną były pomijane, ponieważ pro-
wadziły z powrotem do znajomego terenu, do wierzchołków wcześniej odwiedzonych. Są one
nazywane krawędziami powrotnymi.
Procedura explore odwiedza tylko ta część grafu, która jest osiągalna z punktu startowe-
go. Aby przebadać pozostałą część grafu, należy uruchomić procedurę kolejny raz w innym
miejscu, na jeszcze nieprzeglądanym wierzchołku. Algorytm, zwany przeszukiwaniem w gląb
(ang. depth-first search, DFS), wykonuje procedurę explore wielokrotnie, dopóki cały graf nie
zostanie przemierzony.
procedura dfs (G)
for każdy wierzchoÅ‚ek v ÎðV
do visited(v) := false od
for każdy wierzchoÅ‚ek v ÎðV
do if not visited(v) then explore(v) fi od
Analizując czas działania algorytmu DFS, możemy zauważyć, że dzięki użuciu tablicy
visited (kredowym znakom) każdy wierzchołek jest odwiedzany tylko raz. Podczas przeglądania
wierzchołka wykonywane są:
1. Zaznaczenie wierzchołka, że jest już odwiedzony, oraz operacje pre/postvisit, co po-
chłania pewną stałą ilość pracy.
2. Pętla, w której przeglądane są sąsiadujące krawędzie, w celu znalezenia nieodwie-
dzonych wierzchołków.
Dla każdego wierzchołka pętla ta zajmuje różną ilość czasu, rozważmy zatem wszystkie
wierzchołki razem. Aączna praca wykonana w kroku 1 to O(| V |) . W kroku 2, podczas przebie-
gu caÅ‚ego algorytmu DFS, każda krawiÄ™dz {x, y}Îð E jest rozważana dokÅ‚adnie dwa razy, raz
podczas explore(x), drugi raz podczas explore(y). Aączny czas kroku 2 jest więc równy O(| E |) ,
czyli czas dziaÅ‚ania przeszukiwania w gÅ‚Ä…b wynosi O(|V | +ð | E |) jest liniowy wzglÄ™dem roz-
miaru wejścia. Czas działania algorytmu jest możliwe najlepszy, jest to tylko czas potrzebny do
przeczytania list sÄ…siedztwa.
Na rys. 5 przedstawiony jest wynik przeszukiwania w głąb na grafie 12-
wierzchołkowym, w którym wierzchołki były przeglądane w kolejności alfabetycznej (ignoru-
jemy na razie pary numerów oznaczające czasy bycia w wierzchołku). Zewnętrzna pętla algo-
rytmu DFS wywołuje operację explore trzy razy, na A, C i w końcu na F. Jako wynik uzyskuje-
my trzy drzewa, każde z korzeniem w wierzchołku startowym. Tworzą one razem las.
8
Rys. 5. Graf 12-wierzchołkowy oraz las przeglądania DFS
2.3. Spójność w grafie nieskierowanym
Nieskierowany graf jest spójny (ang. connected), jeśli między każdą parą jego wierzchołków ist-
nieje ścieżka. Graf z rysunku 5 nie jest spójny, ponieważ nie ma na przykład ścieżki od A do K.
Ma on jednak trzy rozłączne spójne obszary odpowiadające zbiorom wierzchołków:
{A, B, E, I, J}, {C, D,G, H, K, L}, {F}.
Obszary te są nazywane spójnymi składowymi; każdy z nich jest podgrafem, który jest
spójny i nie ma krawędzi do pozostalych wierzchołków. Kiedy dla konkretnego wierzchołka
wywołana jest operacja explore, wyznacza ona precyzyjne spójną składową zawierającą ten
wierzchołek. Za każdym razem, kiedy zewnętrzna pętla algorytmu DFS wywołuje explore, wy-
znaczona jest nowa spójna skaładowa.
Przeszukiwanie w głąb można w trywialny sposób wykorzystać do sprawdzenia, czy graf
jest spójny, i bardziej ogólnie do przypisania każdemu wierzchołkowi v numeru ccnum[v], który
oznacza spójną skaładową, do której on należy. Wszystko, co wystarczy zrobić, to
procedura previsit (v)
ccnum[v] := cc
gdzie początkowa wartość cc jest równa zeru i jest incrementowana za każdym razem, kiedy
procedura DFS wywołuje explore.
2.4. PorzÄ…dki previsit i postvisit
Zobaczyliśmy, w jaki sposób przeszukiwanie w głąb kilka skromnych wierszy kodu jest w
stanie odkrywać spójną strukturę grafu nieskierowanego w liniowym czasie. Opisane wyszuki-
wanie jest jednak daleko bardziej wszechstronne. Aby rozwinąć tę myśl, podczas procesu prze-
glądania zbierzemy trochę więcej informacji o grafie: dla każdego wierzchołka zapiszemy czasy
dwóch ważnych wydarzeń, moment pierwszego odwiedzenia wierzchołka (odpowiadający ope-
racji previsit) oraz moment opuszczenia wierzchołka (postvisit). Na rysunku 5 pokazane są
omawiane czasy dla wcześniejszego przykładu, w którym są 24 wydarzenia. Piąte wydarzenie to
odwiedzenie I. Wydarzenie 21 to opuszczenie na dobre wierzchołka D.
9
JednÄ… z metod wyznaczenia tablic pre oraz post z numerami w porzÄ…dku previsit oraz
postvisit jest zdefiniowanie zwykłego licznika clock, ustawionego początkowo na 1, który jest
modyfikowany w następujący sposób.
procedura previsit (v)
pre[v] := clock
clock := clock + 1
procedura postvisit (v)
post[v] := clock
clock := clock + 1
Okaże się wkrótce, że odmierzenie czasu będzie miało duże znaczenie. Tymczasem, ko-
rzystając z rysunky 4, zauważmy, że:
Własność. Dla każdych dwóch wierzchołków u oraz v dwa przedziały [pre(u), post(u)] oraz
[pre(v), post(v)] są albo rozłączne, albo jeden zawiera się w drugim.
Dlaczego? Ponieważ [pre(u), post(u)] jest w istocie przedziałem czasu, w którym wierz-
chołek u jest na stosie. Zasada działania stosu: ostatni wchodzi, pierwszy wychodzi, wyjaśnia
resztÄ™.
3. Przeszukiwanie w głąb grafu skierowanego
3.1. Rodzaje krawędzi
Nasze przesukiwanie grafu w głąb możemy zastosować bez żadnych zmian dla grafu skierowa-
nego, uważając jedunie, aby przechodzić krawędzie tylko w zalecanym kierunku. Na rysunku 6
przedstawiony jest przykład grafu i jego drzewo przeszukiwań, które uzyskamy, kiedy rozważ-
my wierzchołki w porządku leksykograficznym. Podczas analizy grafów skierowanych pomocna
będzie terminologia dla ważnych relacji między węzlami drzewa. Wierzchołek A jest korzeniem
drzewa (ang. root), wszystkie pozostałe wierzchołki są jego potomkami (ang. descendant). Po-
dobnie, wierzchołek E ma potomków F, G oraz H i odwrotnie jest przodkiem (ang. ancestor)
tych trzech wierzchołków. Analogia rodziny jest przenoszona dalej: C jest ojcem D (ang. pa-
rent), które z kolei jest jego dzieckiem (ang. child).
Dla grafów nieskierowanych wyróżnialiśmy krawiędzie drzewowe oraz niedzewowe. W
przypadku grafu skierowanego taksonomia jest nieco bardziej złożona:
10
Rys. 6. Algorytm DFS na grafie skierowanym
Krawędzie drzewowe śa częścią lasu DFS.
Krawędzie w przód prowadzą od węzła do jego potomka w
dzewie DFS, który nie jest jego dzieckiem.
Krawędzie powrotne prowadzą do przodka w drzewie DFS.
Krawędzie poprzeczne nie prowadzą ani do przodka, ani do
potomka; prowadzą do węzła, który już był przeanalizowany
dokładnie (tzn. była dla niego wywołana procedura postvisit).
Graf na rysunku 6 ma dwie krawędzie w przód, dwie
powrotne i dwie poprzeczne.
Relacje bycia potomkiem i przodkiem, jak i typu kra-
wędzi wynikają wprost z numerów porządków pre i post.
Zgodnie ze strategią przesukiwania w głąb wierzchołek u jest
przodkiem wierzchołka v wtedy, gdy u jest odwiedzany jako
pierwszy oraz v jest przeglÄ…dane wewnÄ…trz operacji explore(u).
Inaczej mówiÄ…c pre(u) <ð pre(v) <ð post(v) <ð post(u) , co możemy przedstawić jako dwa za-
gnieżdżone przedziały:
[ [ ] ]
u v v u
Przypadek bycia potomkiem jest symetryczny, ponieważ u jest potomkiem v wtedy i tyl-
ko wtedy, gdy v jest przodkiem u. Ponieważ kategorie krawędzi są w zupełności oparte na relacji
bycia przodkiem i potomkiem, także mogą być zapisane za pomocą numerów porządków pre i
post. Poniżej znajduje się podsumowanie różnych możliwości dla krawędzi (u,v) :
[ [ ] ] krawiędz drzewowa / w przód;
PorzÄ…dek pre/post dla (u,v) :
u v v u
[ [ ] ] powrotna; [ ] [ ] poprzeczna.
v u u v v v u u
Każdy z powyższych porządków pre/post można uzasadnić, patrząc na diagram typów krawędzi.
11
3.2. Skierowany graf acykliczny
Cykl (ang. cycle) w grafie skierowanym to ścieżka rozpoczynająca się i kończąca się w tym sa-
mym wierzchoÅ‚ku v0 ®ð v1 ®ð v2 ®ð ... ®ð v0 . Graf z rysunku 6 ma ich wiele, na przykÅ‚ad,
B ®ð E ®ð F ®ð B . Graf bez cykli jest nazywany acyklicznym (ang. acyclic). Okazuje siÄ™, że
możemy sprawdzić, czy graf jest acykliczny w czasie liniowym za pomocą zwykłego przeszuki-
wania w głąb.
Własność. Graf skierowany ma cykl wtedy i tylko wtedy, gdy przeszukiwanie w głąb wy-
krywa krawędz powrotną.
Dowód. W jedną stronę jest w miarę łatwy: jeśli (u,v) jest krawędzią powrotna, to istnie-
je cykl składający się z tej krawędzi oraz ze ścieżki z v do u w drzewie przeszukiwań.
W drugÄ… stronÄ™, jeÅ›li graf ma cykl v0 ®ð v1 ®ð v2 ®ð ... ®ð vk ®ð v0 , to wybierzmy pierwszy
odwiedzony wierzchołek na tym cyklu (wierzchołek z najmniejszym numerem pre). Przypuść-
my, że jest to wierzchołek vi . Wszystkie pozostałe wierzchołki na cyklu są z niego osiągalne,
zatem sÄ… jego potomkami w drzewie. W szczególnoÅ›ci krawÄ™dz vi-ð1 ®ð vi (lub vk ®ð v0 , jeÅ›li
i =ð 0 ) prowadzi od wierzchoÅ‚ka do jego poprzednuka i z definicji jest to krawÄ™dz powrotna.
Skierowany graf acykliczny (ang. directed acyclic graph), czyli w skrócie dag, pojawia
się często. Jest on wygodny do modelowania różnych związków takich jak przyczynowość, hie-
rarchia, zależności czasowe. Przypuśćmy na przykład, że musimy wykonać wiele zadań, a nie-
które z nich nie mogą się rozpocząć, zanim pewne inne nie zostaną zakończone (musisz się obu-
dzić, zanim będziesz mógł wstać z łóżka, musisz wstać z łóżka, ale jeszcze nie być ubranym, by
wziąć prysznic itd.). Pytamy zatem, jaka jest właściwa kolejność wykonawania zadań?
Takie związki przyczynowe wygodnie jest reprezentować za pomocą skierowanego gra-
fu, w którym każde zadanie jest wierzchołkiem i w którym występuje krawędz z u do v, jeśli u
jest warunkiem dla v. Innymi słowy, zanim wykonamy określone zadanie, wszystkie inne zada-
nia, które na niego wskazują, muszą być już wykonane. Jeśli taki graf ma cykl, to nie ma żadnej
możliwości wykonania wszystkich zadań grafu nie da się uporządkować. Jednakże jeśli graf
jest dagiem, to chcielibyśmy, o ile to możliwe, uporządkować go liniowo (posortować topolo-
gicznie), ustawiając wierzchołki jeden za drugim w taki sposób, aby każda krawędz prowadziła
od wierzchołka wcześniejszego do póżniejszego, czyli aby wszystkie warunki były spełnione. Na
przykład, na rys. 7 jednym z odpowiednich uporządkowań jest B, A, D, C, E, F. (Jakie są inne
trzy?)
Rys. 7. Skierowany graf acykliczny z jednym żródłem, dwoma ujściami i czterema
możliwymi uporządkowaniami liniowymi
Jakie acykliczne grafy skierowane możemy uporządkować liniowo? Wszystkie. I znów
przeszukiwanie w głąb wskazuje metodę, jak to zrobić: należy po prostu wykonać zadania w po-
rządku malejących numerów porządków post. Jedyne krawędzie (u,v) w grafie, dla których
post(u) <ð post(v) (patrz opis typów krawÄ™dzi powyżej) to krawÄ™dzi powrotne a jak wiemy, w
dagu takich krawędzi nie ma. Otrzymujemy zatem:
Własność. W skierowanym grafie acyklicznym każda krawędz prowadzi do wierzchołka z
mniejszym numerem porządków post.
12
W ten sposób uzyskaliśmy algorytm liniowy wyznaczania porządku wierzchołków w da-
gu. Stąd oraz z naszych wcześniejszych obserwacji wynika, że trzy różnie nazwane własności
acykliczność, możliwość liniowego uporządkowania oraz brak krawędzi powrotnych w drzewie
przeszukiwania w głąb są w istocie tymi samymi rzeczami. Ponieważ dag możemy uporząd-
kować loniowo za pomocą malejących numerów post, wierzchołek o najmniejszym numerze
post pojawia się jako ostatni w porządku i jest ujściem wierzchołkiem bez wychodzących kra-
wędzi. I na odwrót, ten z największą wartością post jest zródłem, wierzchołkiem bez wchodzą-
cych krawędzi.
Własność. Każdy skierowany graf acykliczny ma co najmniej jedno zródło i co najmniej
jedno ujście.
Z tego, że zagwarantowane jest istnienie zródła w dagu, wynika alternatywny sposób wy-
znaczania porzÄ…dku liniowego:
Znajdz zródło, wypisz je i usuń z grafu.
Powtarzaj dopoki graf jest niepusty.
Czy umiesz pokazać, dlaczego wyznacza to właściwy liniowy porządek każdego skierowanego
grafu acyklicznego?
4. Składowe silnie spójne
4.1. Definicja spójności dla grafów skierowanych
Spójność grafu nieskierowanego jest pojęciem naturalnym: graf, który jest niespójny, można ła-
two rozłożyć na kilka składowych spójnych (trafnym przykładem jest graf z rys. 5). Jak widzieli-
śmy w punkcie 2.3, operacja explore robi to zręcznie, przy każdym wywołaniu zaznaczając no-
wą spójną składową.
W grafach skierowanych spójność jest bardziej wyrafinowana. W najprostszym sensie
graf skierowany z rys. 8 jest spójny , bo nie można go rozdzielić nieformalnie mówiąc
bez przecinania krawędzi. Taka definicja jest jednak mało interesująca i pouczająca. Nie może-
my uważać grafu z rys. 8 za spójny, ponieważ nie ma on ścieżki na przykład z G do B oraz z F
do A. Precyzyjne spójność dla grafów skierowanych definiuje się następująco:
Dwa wierzchołki u oraz v w grafie skierowanym są połączone, jeśli istnieje ścieżka z u do
v oraz ścieżka z v do u.
Rysunek 8. Graf skierowany i jego silnie spójne składowe a) oraz metagraf b)
Powyższa relacja dzieli V na rozłączne zbiory, które nazywamy składowymi silnie spój-
nymi (ang. strongly connected components). Graf z rys. 8 ma ich pięć.
13
Zastąpimy teraz każdą składową jednym metawierzchołkiem. Między jednym meta-
wierzchołkiem a drugim narysujemy krawiędz, jeśli instnieje krawiędz (w tą samą stronę) mię-
dzy odpowiadającymi im składowymi (rys. 8). Wynikowy metagraf musi być skierowanym gra-
fem acyklicznym. Powód jest prosty: cykl zawierający kilka silnie spójnych składowych łączyłby
je w jedną, silnie spójną składową. Stwierdzamy, że:
Własność. Każdy graf skierowany jest skierowanym grafem acyklicznym swoich silnie spójnych
składowych.
Własność ta mówi coś ważnego: spójna struktura grafu skierowanego jest dwuwarstwo-
wa. W wyższej warstwie mamy skierowany graf acykliczny, który ma raczej prostą strukturę w
szczególności, może być liniowo uporządkowany. Jeśli interesują nas szczególy, to możemy zaj-
rzeć wewnątrz jednego z jego wierzchołków i zbadać istniejącą w nim właściwą silnie spójną
składową.
4.2. Efektywny algorytm
Rozkład grafu skierowanego na silnie spójne składowe jest barszo pouczający i użyteczny. Oka-
zuje się, że może on być wykonany w czasie loniowym znów przy użyciu przeszukiwania w
głąb. Algorytm bazuje się na pewnych własnościach, które już poznaliśmy, a które teraz przea-
nalizujemy dokładniej.
Własność 1. Jeśli podprogram explore startuje w wierzchołku u, to zatrzyma się on dokladnie
wtedy, kiedy wszystkie wierzchołki osiągalne z u zostaną odwiedzone.
Jeśli wywołujemy explore na wierzchołku, który leży gdzieś w ujściowej silnie spójnej
składowej (silnie spójnej składowej, która jest ujściem w metagrafie), to uzyskamy dokładnie
całą tą składową. Graf z rys. 8 ma dwie ujściowe silnie spójne składowe. Na przykład, wywołu-
jąc explore na węzle K, przewędrujemy w całości największą składową i się zatrzymamy.
Z powyższego rozumowania wynika sposób znalezienia jednej silnie spójnej składowej,
ale wciąż pozostają otwarte dwa główne problemy: (A) w jaki sposób wyznaczyć wierzchołek, o
którym wiemy z pewnością, że leży w ujściowej silnie spójnej składowej i (B) jak kontynuować,
kiedy już wyznaczymy pierwszą składową?
Zacznijmy od problemu (A). Nie ma łatwiej, bezpośredniej metody wyznaczenia wierz-
chołka, który z pewnością leży w ujściowej silnie spójnej składowej. Znamy jednak sposób uzy-
skania wierzchołka ze zródłowej silnie spójnej składowej.
Własność 2. Wierzchołek, który otrzymuje największy numer post w porządku postvisit w prze-
szukiwaniu s głąb, musi leżeć w zródłowej silnie spójnej składowej.
Wynika to z następującej, bardziej ogólnej własności.
Własność 3. Jeśli C oraz C są silnie spójnymi składowymi oraz jeśli istnieje krawiędz z wierz-
chołka w C do wierzchołka w C , to największy numer post w porządku postvisit w C jest większy
niż największy numer post w porządku postvisit w C .
Dowód. Podczas dowodzenia własności 3 rozważmy dwa przypadki. Jeśli przy przeszukiwaniu
w głąb składowa C jest przeglądana przed składowa C , to oczywiste jest, że wszystko z C oraz z
C będzie odwiedzone, zanim procedura się zatrzyma (patrz własność 1). Czyli pierwszy wierz-
chołek odwiedzony w C będzie miał większy numer post od każdego wierzchołka w C . Jednak-
że jeśli C jest przeglądane wcześniej, to przeszukiwanie w głąb zatrzyma się po odwiedzeniu
wszystkich wierzchołków z C , a przed odwiedzeniem czegokolwiek z C. Również w tym przy-
padku dowodzona własność jest zachowana.
Z własności 3 możemy wywnioskować, że silnie spójne składowe mogą być uporządko-
wane liniowo przez ustawienie ich w kolejności malejących ich największych numerów post.
Jest to uogólnienie naszego wcześniejszego algorytmu sortującego dag; w dagu każdy wierzcho-
łek jest jednoelementową silnie spójną składową.
Własność 2 pomaga nam znalezć wierzchołek w zródłowej silnie spójnej składowej G.
To, czego potrzebyjemy, to wierzchołek w ujściowej składowej. To, co many, jest przeciwień-
14
stwem tego, czego potrzebujemy. Rozważmy teraz graf odwrócony GR, taki sam jak G, tylko z
odwróconymi krawędziami (rys. 9). GR ma dokładnie takie same silnie spójne składowe jak G.
Jeśli zatem wykonamy przeszukiwanie w głąb na GR, wierzchołek z największym numerem post
jest w zródłowej silnie spójnej składowej GR, czyli w ujściowej silnie spójnej składowej grafu G.
Tym samym rozwiązaliśmy problem (A).
Przechodzimy do problemu (B). W jaki sposób kontynuować algorytm po zidentyfiko-
waniu pierwszej ujściowej składowej? Rozwiązanie dostarcza nam własność 3. Najpierw znajdu-
jemy pierwszą silnie spójną składową i usuwamy ją z grafu, wierzchołek z największym nume-
rem post spośród pozostałych wierzchołków będzie należał do ujściowej silnie spójnej tego, co
pozostało w G. Możemy więc użyć kolejności numerów post z początkowego przeszukiwania w
głąb GR do sukcesywnego wyznaczania drugiej silnie spójnej składowej, trzeciej silnie spójnej
składowej itd. Uzyskujemy następujący algorytm:
1. Wykonaj przeszukiwanie w głąb na grafie GR.
2. Wykonaj algorytm znajdowania nieskierowanych spójnych składowych na grafie G, a
podczas wyszukiwania w głąb przeglądaj wierzchołki w kolejności malejących nume-
rów post z kroku 1.
Algorytm ten działa w czasie liniowym, jedyna stała w liniowym wyrażeniu jest związa-
na z dwukrotnym wywołaniem zwykłego przeszykiwania w głąb.
Rysunek 9. Odwrócenie grafu z rys. 8
Wykonajmy teraz ten algorytm na grafie z rys. 9. Jeśli w kroku 1 rozważane są wierz-
chołki w kolejności leksykograficznej, wówczas porządek wyznaczony dla kroku 2 (tzn. maleją-
ca kolejność numerów zdarzeń post wyznaczonych przez przeszukiwanie w głąb grafu GR) jest
następujący: G, I, J, L, K, H, D, C, F, B, E, A. Następnie w kroku 2 zostaną wyznaczone skła-
dowe w następującej kolejności: {G, H, I, J, K, L}, {D}, {C, F},{B, E},{A}.
15
Wyszukiwarka
Podobne podstrony:
Algorytmy wyklad 1Inforamtyka Algorytmy wykladyAlgorytmy wyklad 4 5Algorytmy wyklad 2 3Algorytmy grafowe, wykładAlgorytmy genetyczne i procesy ewolucyjne Wykład 2PLC mgr wyklad 11 algorytmyAlgorytmy genetyczne i procesy ewolucyjne Wykład 4wyklad algorytmyWykład 1 Standardowe algorytmy regulacji i sterowaniaalgorytmy pytania na egzamin pytania wyklad4Algorytmy I Struktury Danych (Wyklady) infoAlgorytmy i struktury danych Wyklad 4algorytmy pytania na egzamin pytania wyklad7Algorytmy i struktury danych Wyklad 3Wykład 3 Realizacja algorytmu DESAlgorytmy genetyczne i procesy ewolucyjne Wykład 1więcej podobnych podstron