Podstawowe algorytmy grafowe
i ich zastosowania
dr Andrzej Mróz (UMK w Toruniu) 2013
Projekt wspóªnansowany ze ±rodków Unii Europejskiej w ramach Europejskiego Funduszu Spoªecznego Projekt pn. Wzmocnienie potencjaªu dydaktycznego UMK w Toruniu w dziedzinach
matematyczno-przyrodniczych
1 Wst¦p
1.1 Przeszukiwanie grafu
Przeszukiwanie (przegl¡danie) grafu = systematyczne przechodzenie wzdªu» jego kraw¦dzi w celu odwiedzenia wszystkich wierzchoªków.
• Sªu»y m.in. do zbierania informacji o strukturze grafu.
• Algorytmy grafowe cz¦sto zaczyna si¦ od przeszukania wej±ciowego grafu.
• Wiele bardziej zaawansowanych algorytmów grafowych jest modykacj¡ podstawowych algorytmów przeszukiwania.
Dwie metody przeszukiwania grafu: • w gª¡b (ang. depth-rst search, DFS), • wszerz (ang. breadth-rst search, BFS).
W obu metodach b¦dziemy iterowa¢ po s¡siadach danego wierzchoªka. Dlatego najwygod-niejsz¡ reprezentacj¡ grafu s¡ listy s¡siedztwa.
Oznaczenia: Adj = tablica list s¡siedztwa; dla u ∈ V , Adj[u] = lista s¡siadów u w grae G.
Uwaga. Iteracj¦ po s¡siadach danego wierzchoªka mo»na ªatwo zrealizowa¢ równie» na macierzy s¡siedztwa. Jest to jednak bardziej kosztowne w sensie zªo»ono±ci obliczeniowej.
2 DFS
2.1 Przeszukiwanie grafu w gª¡b
Ustalmy graf niezorientowany G = (V, E).
Z ustalonego wierzchoªka ¹ródªowego w si¦gamy coraz gª¦biej w graf, je»eli jest to tylko mo»liwe.
• badamy wszystkie niezbadane dot¡d kraw¦dzie ostatnio odwiedzonego wierzchoªka v, • gdy wszystkie kraw¦dzie v s¡ zbadane, wracamy do wierzchoªka, z którego v zostaª
odwiedzony,
• proces kontynuujemy dopóki wszystkie wierzchoªki osi¡galne z wierzchoªka ¹ródªowego wnie zostan¡ odwiedzone.
Je»eli po powy»szym procesie pozostanie jakikolwiek nie odwiedzony wierzchoªek u, kontynu-ujemy przeszukiwanie traktuj¡c go jako nowy wierzchoªek ¹ródªowy.
Caªy proces powtarzamy, a» wszystkie wierzchoªki w grae zostan¡ odwiedzone. Nale»y zatem zapami¦tywa¢ stany, w jakich znajduj¡ si¦ w danej chwili wierzchoªki. Do zapisania aktualnego stanu wierzchoªka u»ywamy jednego z trzech kolorów:
• biaªy wszystkie wierzchoªki na pocz¡tku;
• wierzchoªek odwiedzany po raz pierwszy kolorujemy na szaro;
• wierzchoªek przetworzony (= lista jego s¡siadów jest caªkowicie zbadana) kolorujemy na czarno.
Uwaga. Tak naprawd¦ w najprostszej implementacji wystarcz¡ dwa stany: nieodwiedzony i odwiedzony. Jednak dokªadniejsze rozró»nienie stanów pozwala lepiej zrozumie¢ ide¦ DFS oraz jest wykorzystywane w niektórych zastosowaniach.
DFS-Visit(G, u) 1 begin
2 kolor(u) := szary; 3 forka»dy v ∈ Adj[u]do
4 ifkolor(v) = biaªythen DFS-Visit(G, v); 5 kolor(u) := czarny;
6 end;
Przebieg algorytmu dla u = 1
@ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6
@ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6
@ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6 @ @ @ @ @ @ w g wg wg w g wg wg 1 2 3 4 5 6
Zauwa»my, »e po wywoªaniu DFS-Visit dla wierzchoªka u wierzchoªki, które nie s¡ osi¡galne z u pozostan¡ nieodwiedzone (biaªe). Aby zatem przejrze¢ caªy graf, nale»y wywoªywa¢ DFS-Visit dopóki b¦d¡ biaªe wierzchoªki.
DFS(G) 1 begin
2 forka»dy u ∈ V(G)do 3 kolor(u) := biaªy; 4 forka»dy u ∈ V(G)do 5 ifkolor(u) = biaªythen
6 DFS-Visit(G, u);
7 end;
• Zªo»ono±¢ czasowa: O(|V | + |E|): odwiedzamy ka»dy wierzchoªek i (dwukrotnie!) ka»d¡ kraw¦d¹.
• Zªo»ono±¢ pami¦ciowa: O(|V |): przechowywanie kolorów, przechowywanie wierz-choªków na stosie rekurencji.
3 Zastosowania DFS
3.1 Badanie spójno±ciPodstawowe zastosowania:
• Sprawdzanie spójno±ci grafu.
• Wyznaczanie skªadowych spójno±ci grafu.
Zauwa»my, »e w procedurze DFS wywoªujemy DFS-Visit dokªadnie tyle razy, ile jest skªad-owych spójno±ci w grae G.
W szczególno±ci, gdy graf jest spójny, DFS-Visit zostanie wywoªana dokªadnie raz przez procedur¦ DFS.
Nietrudno uzupeªni¢ procedur¦ DFS o kod zliczaj¡cy skªadowe spójno±ci, jak równie» przyp-isuj¡cy wierzchoªkom numer skªadowej.
3.2 DFS - inne zastosowania
Inne zastosowania:
• Wyznaczanie silnie spójnych skªadowych (w wersji dla grafu skierowanego). • Sortowanie topologiczne grafu zorientowanego (bez zorientowanych cykli). • Generowanie labiryntów.
• Znajdowanie drogi w labiryncie.
Zainteresowanych odsyªamy do literatury (patrz te» dodatkowe materiaªy do wykªadu i zaj¦¢ laboratoryjnych).
4 BFS
4.1 Przeszukiwanie grafu wszerz
Z ustalonego wierzchoªka ¹ródªowego s przegl¡damy kolejne wierzchoªki z niego osi¡galne. • wierzchoªki w odlegªo±ci (=najmniejszej liczbie kraw¦dzi) k od ¹ródªa s¡ odwiedzane
• granica mi¦dzy wierzchoªkami odwiedzonymi i nieodwiedzonymi jest przekraczana jed-nocze±nie na caªej jej szeroko±ci.
Je»eli po powy»szym procesie pozostanie jakikolwiek nie odwiedzony wierzchoªek u, kontynu-ujemy przeszukiwanie traktuj¡c go jako nowy wierzchoªek ¹ródªowy.
Caªy proces powtarzamy, a» wszystkie wierzchoªki w grae zostan¡ odwiedzone.
Ka»dy wierzchoªek posiada jeden z 3 kolorów: biaªy, szary lub czarny (podobnie jak w DFS). Na pocz¡tku wszystkie wierzchoªki s¡ biaªe.
Wierzchoªki szare s¡ przechowywane w kolejce FIFO. Po jej opuszczeniu kolorujemy je na czarno.
4.2 Kolejka FIFO
Kolejka FIFO Q jest dynamiczn¡ struktur¡ danych w formie ci¡gu, do której mo»na doª¡czy¢ skªadnik tylko w jednym ko«cu (na ko«cu kolejki - tail), a usun¡¢ tylko w drugim ko«cu (na pocz¡tku kolejki - head). Mamy zatem dwie podstawowe operacje:
• Enqueue(Q, v) = dodanie elementu v na ko«cu kolejki, • Dequeue(Q) = usuni¦cie elementu z pocz¡tku kolejki. Dodatkowo wykorzystamy operacj¦
• Head(Q) = zwrócenie elementu z pocz¡tku kolejki (bez usuwania go).
Uwaga. Kolejk¦ implementujemy przy u»yciu struktur wska¹nikowych. Mo»na te» zasy-mulowa¢ jej dziaªanie na zwykªej (statycznej) tablicy, albo wykorzysta¢ gotowe struktury bib-lioteczne (np. queue biblioteki STL w C++).
4.3 Algorytm BFS-Visit(G, s) 1 begin 2 kolor(s) := szary; 3 Q := {s}; 4 whileQ <> ∅ do begin 5 u := Head(Q);
6 forka»dy v ∈ Adj[u]do 7 ifkolor(v) = biaªythen begin
8 kolor(v) := szary; 9 Enqueue(Q, v) 10 end; 11 Dequeue(Q); 12 kolor(u) := czarny 13 end 14 end
Przebieg algorytmu dla s = 1
@ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q:
@ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 2 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 2 5 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 2 5 4 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 2 5 4 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 5 4 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 5 4 3 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 5 4 3 6 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 4 3 6 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 3 6 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 6
@ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q:
Zauwa»my, »e po wywoªaniu BFS-Visit dla wierzchoªka u wierzchoªki, które nie s¡ osi¡galne z u pozostan¡ nieodwiedzone (biaªe). Aby zatem przejrze¢ caªy graf, nale»y wywoªywa¢ BFS-Visit dopóki b¦d¡ biaªe wierzchoªki.
Peªen przebieg algorytmu BFS jest realizowany przez poni»sz¡ procedur¦: BFS(G)
1 begin
2 forka»dy u ∈ V(G)do 3 kolor(u) := biaªy; 4 forka»dy u ∈ V(G)do 5 ifkolor(u) = biaªythen
6 BFS-Visit(G, u);
7 end;
• Zªo»ono±¢ czasowa: O(|V | + |E|) (analogicznie jak dla DFS). • Zªo»ono±¢ pami¦ciowa: O(|V |) (przechowywanie kolorów, kolejka).
5 Zastosowania BFS
5.1 Najkrótsza drogaZastosowanie: znajdowanie najkrótszej drogi pomi¦dzy wierzchoªkami s i v. Modykacja procedury BFS-Visit: przechowywanie wektora poprzedników π. Tj. poprzednikiem wierzchoªka v na najkrótszej drodze z s do v jest wierzchoªek π[v]; poprzednikiem wierzchoªka π[v] jest wierzchoªek π[π[v]]...
Przygotowanie: forka»dy u ∈ V(G)do begin kolor(u) := biaªy; π[u] := ∞ end; 5.2 Zmodykowany BFS BFS-Visit(G, s) 1 begin 2 kolor(s) := szary; 3 Q := {s}; 4 whileQ <> ∅ do begin 5 u := Head(Q);
6 forka»dy v ∈ Adj[u]do 7 ifkolor(v) = biaªythen begin
8 kolor(v) := szary; 9 π[v] := u; 10 Enqueue(Q, v) 11 end; 12 Dequeue(Q); 13 kolor(u) := czarny 14 end 15 end
Przebieg algorytmu dla s = 1 w π[w] 1 ∞ 2 ∞ 3 ∞ 4 ∞ 5 ∞ 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: w π[w] 1 ∞ 2 ∞ 3 ∞ 4 ∞ 5 ∞ 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 w π[w] 1 ∞ 2 1 3 ∞ 4 ∞ 5 ∞ 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 2 w π[w] 1 ∞ 2 1 3 ∞ 4 ∞ 5 1 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 2 5 w π[w] 1 ∞ 2 1 3 ∞ 4 1 5 1 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 1 2 5 4 w π[w] 1 ∞ 2 1 3 ∞ 4 1 5 1 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 2 5 4 w π[w] 1 ∞ 2 1 3 ∞ 4 1 5 1 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 5 4 w π[w] 1 ∞ 2 1 3 5 4 1 5 1 6 ∞ @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 5 4 3
w π[w] 1 ∞ 2 1 3 5 4 1 5 1 6 5 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 5 4 3 6 w π[w] 1 ∞ 2 1 3 5 4 1 5 1 6 5 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 4 3 6 w π[w] 1 ∞ 2 1 3 5 4 1 5 1 6 5 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 3 6 w π[w] 1 ∞ 2 1 3 5 4 1 5 1 6 5 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 6 w π[w] 1 ∞ 2 1 3 5 4 1 5 1 6 5 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6 Q: 5.3 Odczytanie dróg
Po wykonaniu BFS-Visit(G, s) w wektorze π zakodowane s¡ najkrótsze drogi z wierzchoªka ¹ródªowego s do wszystkich wierzchoªków w osi¡galnych z s. W naszym przykªadzie s = 1:
w π[w] 1 ∞ 2 1 3 5 4 1 5 1 6 5 @ @ @ @ v f 1 vf2 vf3 v f 4 5vf vf6
Np. najkrótsza droga z s = 1 do 6 to: 6 ← π[6] ← π[π[6]], czyli
6 ← 5 ← 1.
A najkrótsza droga z s = 1 do 3 to: 3 ← π[3] ← π[π[3]], czyli
3 ← 5 ← 1.
Odczytanie i wypisanie drogi z s do v od ko«ca mo»e by¢ zrealizowane przez poni»szy pseudokod:
PrintPathRev(s, v) 1 begin
2 ifs = vthenwypisz(v)
3 else ifπ[v] = ∞thenwypisz('Nie ma drogi') 4 else begin 5 whilev <> sdo begin 6 wypisz(v); 7 v := π[v] 8 end; 9 wypisz(s) 10 end 11 end;
Jednak bardziej naturalnym byªoby wypisanie drogi z s do v od pocz¡tku. Do tego mo»e posªu»y¢ poni»sza elegancka procedura rekurencyjna:
PrintPath(s, v) 1 begin
2 ifs = vthenwypisz(v)
3 else ifπ[v] = ∞thenwypisz('Nie ma drogi') 4 else begin 5 PrintPath(s, π[v]); 6 wypisz(v) 7 end 8 end; 5.4 Podsumowanie
Je»eli interesuje nas najkrótsza droga pomi¦dzy dwoma ustalonymi wierzchoªkami s i t, to: • uruchamiamy BFS-Visit dla wierzchoªka s,
• odczytujemy drog¦ z s do t z wektora π.
Wektor π b¦dzie zawieraª, jako skutek uboczny, najkrótsze drogi z s do wszystkich wierz-choªków osi¡galnych z s (nie tylko t!). Wyliczania tej nadmiarowej informacji nie da si¦ tu unikn¡¢.
Zauwa»my, »e tu przez najkrótsz¡ drog¦ rozumieli±my drog¦ o najmniejszej liczbie kraw¦dzi. Mo»na rozwa»a¢ grafy, w których kraw¦dzie maj¡ dªugo±¢ (lub inny koszt) i poszukiwa¢ najkrótszych dróg wzgl¦dem tego parametru.