Wykład 7
Algorytmy grafowe
Definicje i własności
Reprezentacja
Przeszukiwanie wszerz (Breadth-First Search)
Przeszukiwanie w głąb (Depth-First Search)
Sortowanie topologiczne
Literatura
– Cormen, Leiserson, Rivest, “Wprowadzenie do algorytmów”, rozdział 23
Wiele zjawisk może być opisanych poprzez binarne relacje pomiędzy obiektami:
– Wszelkiego typu mapy drogowe i plany – Odsyłacze w stronach WWW
Graf jest abstrakcyjną strukturą opisującą takie binarne relacje pomiędzy elementami.
Bardzo dużo problemów można sprowadzić do rozwiązywania zadań grafowych: najkrótszej ścieżki, połączenia pomiędzy węzłami,
minimalnego drzewa rozpinającego, etc.
• każdy wierzchołek reprezentuje miasto.
• każda krawędź reprezentuje bezpośrednie połączenie lotnicze pomiędzy miastami.
• pytanie o bezpośrednie połączenie = pytanie czy istnieje krawędź.
• pytanie o połączenie = czy istnieje droga z A do B.
• z połączeniami możemy powiązać koszty (grafy ważone), wtedy
sensowne staje się pytanie o najtańsze połączenie z A do B.
Stacje są
wierzchołkami
Połączenia pomiędzy stacjami są
krawędziami
Najkrótsza droga=
najmniejsza odległość,
najmniejszy czas.
Stacje, do których można dojechać (osiągalne).
wierzchołków, E = {e1, .. em} jest zbiorem krawędzi. Krawędź ek = (vi ,vj) łączy dwa wierzchołki vi i vjze zbioru V.
Krawędzie mogą być skierowane lub nie (uporządkowane lub nieuporządkowane): eij: vi— vj lub eij: vi—> vj
Graf G nazywamy skończonym jeśli |V| i |E| są skończone.
Przez rozmiar grafu G rozumiemy |G| = |V| + |E|.
Niech V = {1,2,3,4,5,6}
1 2 3
4 5 6
Graf skierowany
1 2 3
4 5 6
Graf nieskierowany
Każdy graf można uważać za graf ważony (dla każdej krawędzi
przypisujemy wagę 1). Jeśli dwa wierzchołki nie są połączone to można je traktować jako połączone z wagą ∞.
1 2 3
4 5 6
4
2
8
1 5
Koszt ścieżki: suma
kosztów poszczególnych krawędzi
( ) ∑ ( )
=
=
− ki
i
i
v
v c p
c
1
1
,
6
W grafie skierowanym krawędzie są skierowane tj. e = (u,v) zaczyna się w u i kończy w v (v jest połączone z u).
Dopuszczalne jest połączenie wierzchołka z samym sobą e = (u,u)
Stopień wchodzący (indegree) din(v) dla wierzchołka v jest ilością krawędzi wchodzących do v. Odpowiednio stopień wychodzący
(outdegree) dout(v) dla wierzchołka v jest ilością krawędzi wychodzących z v. Σdin(vi) = Σdout(vi)
Ścieżka z u do v w grafie G = (V,E) o długości k jest sekwencją
wierzchołków <u = v0,v1,…, vk = v> taką że dla i =1,…,k para (vi–1,vi) należy do E.
Grafy nieskierowane nie mogą mieć połączeń wierzchołka z samym sobą
Połączenie jest relacją symetryczną: jeśli e = (u,v) to u jest sąsiadem v oraz v jest sąsiadem u.
Stopień wierzchołka d(v) jest ilością jego sąsiadów - Σd(vi) = 2|E|.
Określenie ścieżki w grafie – tak samo jak dla grafu skierowanego.
Cyklem nazywamy ścieżkę, która rozpoczyna się i kończy w tym samym wierzchołku.
Grafem spójnym nazywamy nieskierowany graf dla którego istnieje ścieżka pomiędzy dwoma dowolnymi wierzchołkami (każdy wierzchołek jest
osiągalny, z każdego innego wierzchołka).
Grafem silnie spójnym nazywamy graf skierowany, dla którego dla każdych dwóch wierzchołków u i v istnieje droga z u do v oraz z v do u.
Graf G’= (V’,E’) jest pod-grafem G = (V,E), jeśli G’ ⊆⊆⊆⊆ G jeśli V’ ⊆⊆⊆⊆ V oraz E’ ⊆⊆⊆⊆ E.
– Graf dla którego E| = |V| nazywamy kliką (każde dwa wierzchołki są połączone krawędzią).
W każdym spójnym grafie jest co najmniej |E| ≥≥≥≥ |V|–1 krawędzi.
– Dowód: przez indukcje dla |V|.
Graf planarny (graf płaski) – graf, który da się narysować na płaszczyźnie tak, by łuki obrazujące krawędzie grafu nie przecinały się. Dla grafu planarnego mamy |E| = O(|V|). Dwa minimalne grafy, które nie są planarne, to K5 i K3,3. Twierdzenie Kuratowskiego (1930) mówi, że graf skończony jest planarny wtedy i tylko wtedy, gdy nie zawiera podgrafu homeomorficznego z grafem K5 ani z grafem K3,3.
Ścieżkę nazywamy prostą jeżeli żaden wierzchołek nie występuje w niej więcej niż raz
• Składa się z różnych wierzchołków
Ścieżkę nazywamy cyklem wtedy i tylko wtedy jeśli v
0= v
k.
• Rozpoczyna się i kończy w tym samym wierzchołku!
W ścieżkach zawierających cykl jako podścieżkę wierzchołki mogą
pojawiać się kilkakrotnie.
1. {a,c,f,e} → ścieżka prosta, L = 3 2. {a,b,d,c,f,e} → ścieżka prosta, L = 5 3. {a,c,d,b,d,c,f,e} → ścieżka zawiera
cykl {d,b,d}
4. {a,c,d,b,a} → cykl, L = 4
5. {a,c,f,e,b,d,c,a} → cykl, L = 7
Drzewo jest to spójny graf, który nie zawiera cykli.
Drzewo ma |E| =|V|–1 krawędzi.
Następujące 3 własności są równoważne:
1. G jest drzewem.
2. G nie ma cykli; dodanie nowej krawędzi buduje cykl.
3. G jest spójny; po usunięciu dowolnego węzła przestaje taki być.
Podobnie można podąć definicje drzewa skierowanego.
jego sąsiadów w grafie. Rozmiar takiej reprezentacji to: ΘΘΘ(|V|+|E|).Θ 2. Macierz sąsiedztwa: macierz |V| ×|V|, w której krawędź e = (u,v)
jest reprezentowana przez niezerowe wejście (u,v). Rozmiar takiej reprezentacji to : ΘΘΘ(|V|Θ 2).
Lista sąsiedztwa jest wygodniejsza dla dla „rzadkich” grafów.
Macierz sąsiedztwa jest wygodniejsza dla dla „gęstych” grafów.
1 2 3
4 5 6
V L i
1 2 3 4 5 6
nul l
6
3
5 2
1 5
2 1
V = {1,2,3,4,5,6}
E = {(1,2),(1,5),(2,5),(3,6)}
1 2 3
4 5 6
1 2 3 4 5 6
1 2 3 4 5 6
1
1 1
1 1
1
1
1
Dla grafów nieskierowanych, A = A
T
lista sąsiedztwa
bardziej zwarta niż macierz sąsiedztwa (dobra dla rzadkich grafów)
stwierdzenie czy istnieje krawędź zajmuje więcej czasu
macierz sąsiedztwa
zawsze wymaga Θ(n
2) pamięci
często „marnujemy” dużo pamięci
szybko możemy odnaleźć informację o krawędzi
2
4
3
5 1
7
6 9
9 8 7 6 5 4 3 2 1 0
9 7 3 2 8
8 4 1
5 4 1
3 2
6 3
7 5
6 1
9 2 0
8 1
Lista sąsiedztwa
0 1 0 0 0 0 0 0 1 9 0
1 0 0 0 0 0 0 1 0 8 1
0 0 0 1 0 0 0 0 1 7 0
0 0 1 0 1 0 0 0 0 6 0
0 0 0 1 0 0 1 0 0 5 0
0 0 0 0 0 0 1 1 0 4 0
0 0 0 0 1 1 0 0 1 3 0
0 1 0 0 0 1 0 0 1 2 0
Θ Θ Θ Θ(E) Θ
Θ Θ Θ(E) Θ
ΘΘ Θ(V2) Odnalezienie wszystkich krawędzi
ΘΘ ΘΘ(E) ΘΘ
ΘΘ(E) ΘΘ
ΘΘ(V) Odnajdowanie krawędzi
kończących się wv
Θ Θ ΘΘ(E) Θ
ΘΘ
Θ(V + E) Θ
ΘΘ Θ(V2) pamięć
Θ Θ ΘΘ(E) Ο
Ο
ΟΟ(outdegree[u]) Θ
Θ ΘΘ(V) Odnajdowanie krawędzi
rozpoczynających się w u
Ο Ο Ο Ο(E) Ο
Ο Ο
Ο(outdegree[u]) Θ
Θ Θ Θ(1) Sprawdzenie istnienia połączenia
(u, v)
Lista krawędzi Lista sąsiedztwa
Macierz sąsiedztwa
– Najbardziej popularne są dwa algorytmy:
• Breadth-First Search (BFS) – przeszukiwanie wszerz
Odnajduje drogi (najkrótsze drogi) w grafach nieskierowanych.
Odnajduje połączone wierzchołki.
• Depth-First Search (DFS) – przeszukiwanie w głąb
Odnajduje drogi w grafach nieskierowanych..
Odnajduje połączone wierzchołki.
Przeprowadza topologiczne sortowanie w grafach skierowanych (liniowe uporządkowanie wierzchołków, w którym jeśli istnieje krawędź skierowana prowadząca od wierzchołka x do y, to x znajdzie się przed wierzchołkiem y. Innymi słowy, każdy wierzchołek poprzedza wszystkie te wierzchołki, do których prowadzą wychodzące od niego krawędzie).
Mając dany dowolny wierzchołek s, BFS odwiedza wierzchołki w porządku rosnącym (względem odległości od s). W każdym kroku, BFS odwiedza wszystkie osiągalne wierzchołki o stałej odległości. W taki sposób, BFS odkrywa wszystkie drogi od s do innych wierzchołków.
Co oznacza termin “odległość”? Ilość krawędzi w ścieżce od s.
2
4
3
5 1
7
6 9
8 0
s= 1
Wierzchołki odległe o1?
2, 3, 7, 9
1
1 1
1 2
2
s
Przykład
Wierzchołki odległe o 2?
8, 6, 5, 4
Wierzchołki odległe o 3?
// flag[ ]: visited table
2
4
3
5 1
7
6 9
8 0
Lista sąsiedztwa
source
9 8 7 6 5 4 3 2 1 0
Visited Table (T/F)
F F F F F F F F F F
Q = { }
Inicjalizacja tablicy (wszystko false)
Inicjalizacja kolejki Q (pusta)
2
4
3
5 1
7
6
source 9
9 8 7 6 5
F F F F F
Q = { 2 }
Oznaczamy 2 jako odwiedzone
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
F T F F F T F T T F
Q = {2} → Q = { 8, 1, 4 }
Oznacz sąsiadów 1, 4, 8
jako odwiedzonych
Dequeue 2.
Umieść wszystkich nieodwiedzonych sąsiadów 2 w kolejce
Neighbors
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T F F F
Q = { 8, 1, 4 } → Q = { 1, 4, 0, 9 }
Oznacz
nowo-odwiedzonych 0, 9
Dequeue 8.
- umieść wszystkich nieodwiedzonych sąsiadów 8 w kolejce
Neighbors
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T F F T T T T T
Q = { 1, 4, 0, 9 } → Q = { 4, 0, 9, 3, 7 }
Oznacz
nowo-odwiedzonych 3, 7
Dequeue 1.
- dodaj do kolejki nieodwiedzonych sąsiadów1.
Neighbors
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T F F
Q = { 4, 0, 9, 3, 7 } → Q = { 0, 9, 3, 7 } Dequeue 4.
- 4 nie posiada nieodwiedzonych sąsiadów!
Neighbors
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T F F T T T T T
Q = { 0, 9, 3, 7 } → Q = { 9, 3, 7 } Dequeue 0.
- 0 nie ma nieodwiedzonych sąsiadów!
Neighbors
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T F F
Q = { 9, 3, 7 } → Q = { 3, 7 } Dequeue 9.
- 9 nie ma nieodwiedzonych sąsiadów!
Neighbors
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T F T T T T T T
Q = { 3, 7 } → Q = { 7, 5 } Dequeue 3.
- dodajemy 5 do kolejki.
Neighbors
Oznaczamy 5
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T T T
Q = { 7, 5 } → Q = { 5, 6 } Dequeue 7.
- dodajemy 6 do kolejki
Neighbors
Oznaczamy 6
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
Q = Q = Q =
Q = { 5, 6} → Q = Q = Q = Q = { 6 } Dequeue 5.
- nie ma nieodwiedzonych sąsiadów 5
Neighbors
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T T T T
Q = { 6 } → Q = { } Dequeue 6.
- brak nieodwiedzonych sąsiadów 6
Neighbors
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
Q = { }
STOP!!! Q jest pusta!!!
Co osiągnęliśmy?
Wszystkie wierzchołki zostały odwiedzone.
Istnieje droga od wierzchołka 2 Do wszystkich pozostałych w grafie
Każdy wierzchołek trafia do Q (enqueued i dequeued)
co najwyżej raz.
Każda iteracja zajmuje czas proporcjonalny do deg(v) + 1 ( “+1” ze względu na dequeue).
O(n + m)
W grafie o m krawędziach suma stopni wierzchołków wynosi?
Stąd całkowity czas działania (pętla while):
Suma wszystkich iteracji w pętli while!
O( Σ
vertex v(deg(v) + 1) ) = O(2m+n) = O(n+m)
Σ
vertex vdeg(v) = 2m
Odnalezienie sąsiadów v wymaga przejrzenia wszystkich elementów w wierszu. Zajmuje to czas O(n).
Sumując to dla wszystkich iteracji dostajemy O(n2).
O(n 2 )
Dostajemy stąd, że, BFS w tej wersji ma
złożoność O(n2) niezależnie od ilości krawędzi m.
Dla listy sąsiedztwa mieliśmy O(n+m);
Jeśli m=O(n2), (graf gęsty) to dostajemy O(n+m)=O(n2).
BFS pokazuje jedynie czy istnieje połączenie ze źródła s do pozostałych wierzchołków v.
– Nie odnajduje ścieżki!
– Potrzebujemy modyfikacji algorytmu odnajdującej ścieżki
Jak to zrobić?
– Nie wiemy które wierzchołki leżą na ścieżce dopóki nie dojdziemy do v!
– Ale, dla pary {w,v}, wiemy czy w był odwiedzony z v.
– Efektywne rozwiązanie:
• Wykorzystać dodatkową tablicę pred[0..n-1] do zapamiętywania poprzedników dla każdego wierzchołka.
• Pred[w] = v oznacza, że w został odwiedzony z v.
inicjujemy pred[v]
Oznaczamy skąd
przyszliśmy.
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
Visited Table (T/F)
F F F F F F F F F F
Q = { }
- - - - - - - - - -
Pred
2
4
3
5 1
7
6
source
99 8 7 6 5
F F F F F
Q =
{ 2 }- - - - -
Pred
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
F T F F F T F T T F
Q =
{2} →Q =
{ 8, 1, 4 }Oznaczamy że przyszliśmy z 2.
Neighbors
- 2 - - - 2 - - 2 -
Pred
Dequeue 2
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T F F F
Q =
{ 8, 1, 4 } →Q =
{ 1, 4, 0, 9 }Oznaczamy, że przyszliśmy z 8.
Dequeue 8.
Neighbors
8 2 - - -
Pred
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T F F T T T T T
Q =
{ 1, 4, 0, 9 } →Q =
{ 4, 0, 9, 3, 7 }Przyszliśmy z 1
Dequeue 1.
Neighbors
8 2 1 - - 2 1 - 2 8
Pred
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T F F
Q =
{ 4, 0, 9, 3, 7 } →Q =
{ 0, 9, 3, 7 } Dequeue 4.- 4 nie ma nieodwiedzonych sąsiadów!
Neighbors
8 2 1 - -
Pred
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T F F T T T T T
Q =
{ 0, 9, 3, 7 } →Q =
{ 9, 3, 7 } Dequeue 0.- 0 nie ma nieodwiedzonych sąsiadów!
Neighbors
8 2 1 - - 2 1 - 2 8
Pred
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T F F
Q =
{ 9, 3, 7 } →Q =
{ 3, 7 } Dequeue 9.- 9 nie ma nieodwiedzonych sąsiadów!
Neighbors 8
2 1 - -
Pred
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T F T T T T T T
Q =
{ 3, 7 } →Q =
{ 7, 5 } Dequeue 3.Neighbors
8 2 1 - 3 2 1 - 2 8
Pred
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T T T
Q =
{ 7, 5 } →Q =
{ 5, 6 } Dequeue 7.Neighbors
8 2 1 7 3
Pred
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
Q =
{ 5, 6} →Q =
{ 6 } Dequeue 5.Neighbors
8 2 1 7 3 2 1 - 2 8
Pred
2
4
3
5 1
7
6
source 9
9 8 7 6 5
T T T T T
Q =
{ 6 } →Q =
{ } Dequeue 6.Neighbors
8 2 1 7 3
Pred
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
Q =
{ }STOP!!! Q jest pusta!!!
Tablicę Pred można teraz
wykorzystać do pokazania ścieżek!
8 2 1 7 3 2 1 - 2 8
Pred
8 2 1 7 3 2 1 -
9 8 7 6 5 4 3 2
Np:
Path(0) →2,8,0 Path(6) →2,1,7,6 Path(1) →2,1
Algorytm rekursywny
Ścieżki odnalezione przez BFS można przedstawić w postaci drzewa z korzeniem (nazywa się je BFS tree), gdzie początkowy wierzchołek jest korzeniem.
BFS tree dla wierzchołka s=2.
DFS może pokazać informacje o grafie które trudno uzyskać przy BFS.
– Np. czy w grafie istnieją cykle?
Algorytm DFS odwiedza sąsiadów korzystając z rekurencji.
– Kiedykolwiek odwiedzamy v z u, rekursywnie odwiedzamy wszystkich nieodwiedzonych do tej pory sąsiadów v. Potem powracamy do u...
– Zauważmy: możliwe jest, że w2 nie był odwiedzony kiedy rekursywnie odwiedzaliśmy w1, ale zostanie odwiedzony w czasie kiedy powrócimy z wywołania rekursywnego.
u v
w1 w2
w3
oznaczamy wszystkie
nieodwiedzone wierzchołki
Oznaczamy węzeł jako odwiedzony.
Dla wszystkich nieodwiedzonych sąsiadów wywołaj rekursywnie RDFS(w) Można podobnie jak poprzednio oznaczać
Ścieżki przy pomocy pred[ ].
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
Visited Table (T/F)
F F F F F F F F F F
- - - - - - - - - -
Pred
4
3
5 1
7
6
9 8 7 6 5
F F F F F
Oznaczamy 2 Jako odwiedzone
- - - - -
Pred
RDFS( 2 )
teraz wywołujemy RDFS(8)
2
4
3
5 1
7
6 9
8
source
9 8 7 6 5 4 3 2 1 0
F T F F F F F T F F
8 - odwiedzone Pred[8] oznaczone
- 2 - - - - - - - -
Pred
RDFS( 2 ) RDFS(8)
2 już odwiedzone, wywołujemy RDFS(0)
Rekursywne wywołanie
4
3
5 1
7
6
9 8 7 6 5
F T F F F
Oznaczamy 0 jako odwiedzone i Pred[0]
- 2 - - -
Pred
RDFS( 2 ) RDFS(8)
RDFS(0) -> brak nieodwiedzonych sąsiadów, powrót do RDFS(8)
Rekursywne wywołanie
2
4
3
5 1
7
6 9
8 0
source
9 8 7 6 5 4 3 2 1 0
F T F F F F F T F T
- 2 - - - - - - - 8
Pred
RDFS( 2 ) RDFS(8)
teraz odwiedzamy 9 -> RDFS(9)
Rekursywne wywołanie
4
3
5 1
7
6
9 8 7 6 5
T T F F F
Ozn. 9 i Pred[9]
8 2 - - -
Pred
RDFS( 2 ) RDFS(8)
RDFS(9)
-> odwiedzamy 1, RDFS(1)
Rekursywne wywołanie
2
4
3
5 1
7
6 9
8
source
9 8 7 6 5 4 3 2 1 0
T T F F F F F T T T
Oznaczamy 1 i Pred[1]
8 2 - - - - - - 9 8
Pred
RDFS( 2 ) RDFS(8)
RDFS(9) RDFS(1)
odwiedzamy RDFS(3)
Rekursywne wywołanie
4
3
5 1
7
6
9 8 7 6 5
T T F F F
Oznaczamy 3 i Pred[3]
8 2 - - -
Pred
RDFS( 2 ) RDFS(8)
RDFS(9) RDFS(1)
RDFS(3)
Rekursywne wywołanie
RDFS( 2 ) RDFS(8)
RDFS(9) RDFS(1)
RDFS(3)
RDFS(4) STOP wszyscy sąsiedzi 4 są już odwiedzeni wracamy do RDFS(3)
2
4
3
5 1
7
6 9
8
source
9 8 7 6 5 4 3 2 1 0
T T F F F T T T T T
Oznaczamy 4 i Pred[4]
8 2 - - - 3 1 - 9 8
Pred
Rekursywne wywołanie
4
3
5 1
7
6
9 8 7 6 5
T T F F F
8 2 - - -
Pred RDFS( 2 )
RDFS(8) RDFS(9)
RDFS(1)
RDFS(3)
odwiedzamy 5 -> RDFS(5) Powrót do 3
Rekursywne wywołanie
2
4
3
5 1
7
6 9
8
source
9 8 7 6 5 4 3 2 1 0
T T F F T T T T T T
8 2 - - 3 3 1 - 9 8
Pred RDFS( 2 )
RDFS(8) RDFS(9)
RDFS(1)
RDFS(3) RDFS(5)
3 już odwiedzone, wracamy do 6 -> RDFS(6)
Oznaczamy 5 i Pred[5]
Rekursywne wywołanie
4
3
5 1
7
6
9 8 7 6 5
T T F T T
8 2 - 5 3
RDFS( 2 ) Pred RDFS(8)
RDFS(9) RDFS(1)
RDFS(3) RDFS(5)
RDFS(6)
Oznaczamy 6 i Pred[6]
Rekursywne wywołanie
2
4
3
5 1
7
6 9
8
source
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
8 2 6 5 3 3 1 - 9 8
RDFS( 2 ) Pred RDFS(8)
RDFS(9) RDFS(1)
RDFS(3) RDFS(5)
RDFS(6)
Oznaczamy 7 i Pred[7]
Rekursywne wywołanie
9 8 7 6 5
T T T T T
8 2 6 5 3
RDFS( 2 ) Pred RDFS(8)
RDFS(9) RDFS(1)
RDFS(3) RDFS(5)
RDFS(6) -> Stop 4
3
5 1
7
6
Rekursywne wywołanie
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
8 2 6 5 3 3 1 - 9 8
RDFS( 2 ) Pred RDFS(8)
RDFS(9) RDFS(1)
RDFS(3)
RDFS(5) -> Stop 2
4
3
5 1
7
6 9
8 0
source
Rekursywne wywołanie
9 8 7 6 5
T T T T T
8 2 6 5 3
RDFS( 2 ) Pred RDFS(8)
RDFS(9) RDFS(1)
RDFS(3) -> Stop 4
3
5 1
7
6
Rekursywne wywołanie
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
8 2 6 5 3 3 1 - 9 8
RDFS( 2 ) Pred RDFS(8)
RDFS(9)
RDFS(1) -> Stop 2
4
3
5 1
7
6 9
8 0
source
Rekursywne wywołanie
9 8 7 6 5
T T T T T
8 2 6 5 3
RDFS( 2 ) Pred RDFS(8)
RDFS(9) -> Stop 4
3
5 1
7
6
Rekursywne wywołanie
9 8 7 6 5 4 3 2 1 0
T T T T T T T T T T
8 2 6 5 3 3 1 - 9 8
RDFS( 2 ) Pred
RDFS(8) -> Stop 2
4
3
5 1
7
6 9
8 0
source
Rekursywne wywołanie
9 8 7 6 5
T T T T T
8 2 6 5 3
RDFS( 2 ) -> Stop Pred
Rekurencyjne wywołania zakończone
4
3
5 1
7
6
9 8 7 6 5 4 3 2 1 0
Visited Table (T/F)
T T T T T T T T T T
8 2 6 5 3 3 1 - 9 8
Pred
Np.
Path(0) ->2 8 0
Path(6) ->2 8 9 1 3 5 6 Path(7) ->2 8 9 1 3 5 6 7 2
4
3
5 1
7
6 9
8 0
source
Odzwierciedla on strukturę wywołań rekurencyjnych.
-
kiedy odwiedzamy sąsiada w wierzchołka v, dodajemy w jako dziecko v.
-
kiedy DFS powraca z wierzchołka v,
powracamy do rodzica v.
(z zastosowaniem list sąsiedztwa)
Nigdy nie odwiedzamy wierzchołka więcej niż raz.
Musimy zatem sprawdzić wszystkie wierzchołki grafu.
– Wiemy, że Σvertex v degree(v) = 2m, gdzie m jest ilością krawędzi.
Dla każdego z wierzchołków zabiera to czas proporcjonalny do deg(v) + 1.
Stąd czas wykonania DFS jest proporcjonalny do sumy ilości wierzchołków i krawędzi (tak jak dla BFS).
– O(n + m) gdzie n – ilość wierzchołków, m – ilość krawędzi.
Inny zapis:
– O(|v|+|e|) |v| = ilość wierzchołków (n)
|e| = ilość krawędzi (m)
D
E
A
C
F B
G
K
H L
N
M
O R
P Q
s
Jak stwierdzić czy dwa wierzchołki są połączone?
Czy A jest połączone z F?
Czy A jest połączone z L?
G =
Graf nazywamy spójnym jeśli dla każdej pary wierzchołków istnieje droga łącząca je.
Jak można stwierdzić czy graf jest spójny?
– Wykorzystać BFS lub DFS (wybierając dowolnie jakiś wierzchołek jako początek).
– Jeśli odwiedziliśmy wszystkie wierzchołki to graf jest spójny.
– Czas takiej procedury? O(n + m)
niespójny spójny
Spójną składową grafu jest maksymalny spójny podgraf tego grafu.
Zbiór spójnych składowych jest jednoznaczny dla każdego grafu.
Przykład: poniższy graf można jednoznacznie przedstawić w postaci sumy 3 spójnych podgrafów: C1, C2 i C3.
Odnajdujemy wszystkie wierzchołki połączone z
“v” => tworzą jedną spójną składową
Zwykły DFS
dla każdego z podgrafów (i=1,2,…):
Stąd dla całego grafu:
) ( n i m i O +
∑
∑
∑ + = + = +
i
i i
i i
i
i
m O n m O n m
n
O ( ) ( ) ( )
Dopuszczalne jest połączenie wierzchołka z samym sobą e = (u,u)
Stopień wchodzący (in-degree) din(v) dla wierzchołka v jest ilością krawędzi wchodzących do v. Odpowiednio stopień wychodzący (out-
degree) dout(v) dla wierzchołka v jest ilością krawędzi wychodzących z v.
Σdin(vi) = Σdout(vi)
Ścieżka z u do v w grafie G = (V,E) o długości k jest sekwencją
wierzchołków <u = v0,v1,…, vk = v> taką że dla i =1,…,k para (vi–1,vi) należy do E.
Można korzystać zarówno z macierzy sąsiedztwa, jak i z list sąsiedztwa.
z
do
Cykl skierowany jest skierowaną ścieżką o tożsamym początku i końcu.
Graf skierowany jest acykliczny jeśli nie zawiera cykli skierowanych.
W wypadku grafów skierowanych nie można po prostu mówić o stopniu wierzchołka deg(v)
Zamiast tego będziemy mówić o ilości krawędzi „wchodzących” Indegree(v) oraz „wychodzących” z wierzchołka Outdegree(v)
Każdej krawędź arc(u,v) będzie liczona 2 razy: jako outdegree dla u oraz indegree dla v.
vertex vertex
indegree( ) outdegree( )
v v
v = v = m
∑ ∑
Obliczanie Indegree
– Rozpoczynamy od indegree[v]=0 dla każdego wierzchołka v.
– Przeglądamy listy adj[v] dla każdego v.
• Dla każdego odnalezionego wierzchołka: indegree[w]++;
• Czas wykonania: O(n+m).
0
1
2
3
4
5 6
7
8
9
Indeg(2)? 2 Indeg(8)? 2 Outdeg(0)? 3 Ilość krawędzi?
m = 13
outdegree? 13
indegree? 13
zależnych.
– Tj. takich że każde następne może rozpocząć się dopiero po zakończeniu innego.
Zależności takie mogą być modelowane poprzez krawędzie skierowane (arc).
arc (i,j) oznacza, że zadanie j nie może rozpocząć się przed zakończeniem zadania i.
Oczywiście, żeby praca się zakończyła taki graf nie może być cykliczny.
i j
liniowe uporządkowanie wierzchołków, w którym jeśli istnieje krawędź skierowana prowadząca od wierzchołka x do y, to x znajdzie się przed wierzchołkiem y. Innymi słowy, każdy wierzchołek poprzedza wszystkie te wierzchołki, do których prowadzą wychodzące od niego krawędzie.
Wierzchołki w każdym grafie acyklicznym skierowanym można posortować topologicznie na jeden lub więcej sposobów.
0
1 2
3
4 5
6
7
8
9
np. 0 6 1 4 3 2 7 5 8 9
Algorytm:
1. Jeśli indegree dla wierzchołka wynosi zero można od niego zacząć (wypisujemy).
2. Jeśli wierzchołek i został wypisany to krawędzie wychodzące z niego nie są już potrzebne – możemy usunąć wszystkie krawędzie wychodzące z i oraz samo i.
3. Po usunięciu z grafu wierzchołka i pozostaje on acykliczny i można rozumowanie powtórzyć.
Znajdujemy punkt startowy
redukujemy indegree(w)
Wkładamy nowy start do kolejki Q
0
1 2
3
4 5
6
7
8
9
9 8 7 6 5 4 3
2 7 5
8 5
3 2
8
9 9
9 8 7 6 5 4 3 2
2 2 1 1 2 1 1 2
Q = { 0 }
OUTPUT:
0
0
1 2
3
4 5
6
7
8
9
9 8 7 6 5 4 3 2 1 0
2
6 1 4
7 5
8 5
3 2
8
9 9
9 8 7 6 5 4 3 2 1 0
2 2 1 1 2 1 1 2 1 0
Dequeue 0 Q = { }
-> usuwamy krawędzie z 0 uaktualniamy indegree sąsiadów (6, 1, 4)
OUTPUT:
0
-1 -1
-1
0
1 2
4 5
7
8
9
9 8 7 6 5 4
3 8
5
3 2
8
9 9
9 8 7 6 5 4 3
2 2 1 0 2 0 1
Q = { 6, 1, 4 }
Wkładamy do kolejki wszystkie nowe punkty startowe
OUTPUT: 0
1 2 3
4 5
6
7
8
9
9 8 7 6 5 4 3 2 1 0
2
6 1 4
7 5
8 5
3 2
8
9 9
9 8 7 6 5 4 3 2 1 0
2 2 1 0 2 0 1 2 0 0
Dequeue 6 Q = { 1, 4 } usuwamy krawędzie..
uaktualniamy indegree sąsiadów (3, 2)
OUTPUT: 0 6
-1 -1
1 2
4 5
7
8
9
9 8 7 6 5 4
3 8
5
3 2
8
9 9
9 8 7 6 5 4 3
2 2 1 0 2 0 0
Q = { 1, 4, 3 }
Enqueue 3
OUTPUT: 0 6
1 2 3
4 5
7
8
9
9 8 7 6 5 4 3 2 1 0
2
6 1 4
7 5
8 5
3 2
8
9 9
9 8 7 6 5 4 3 2 1 0
2 2 1 0 2 0 0 1 0 0
Dequeue 1 Q = { 4, 3 }
OUTPUT: 0 6 1
-1
2
4 5
7
8
9
9 8 7 6 5 4
3 8
5
3 2
8
9 9
9 8 7 6 5 4 3
2 2 1 0 2 0 0
Dequeue 1 Q = { 4, 3, 2 } Enqueue 2
OUTPUT: 0 6 1
2 3
4 5
7
8
9
9 8 7 6 5 4 3 2 1 0
2
6 1 4
7 5
8 5
3 2
8
9 9
9 8 7 6 5 4 3 2 1 0
2 2 1 0 2 0 0 0 0 0
Dequeue 4 Q = { 3, 2 }
OUTPUT: 0 6 1 4
-1
2
5 7
8
9
9 8 7 6 5 4
3 8
5
3 2
8
9 9
9 8 7 6 5 4 3
2 2 1 0 1 0 0
Dequeue 3 Q = { 2 }
OUTPUT: 0 6 1 4 3
-1