W12 grafy 1


IIUJ Metody Programowania strona: 1
Wykład 12.
Wprowadzenie do algorytmów na grafach
Grafy to struktury danych podobne do drzew, ale w grafach nie wyróżnia się
korzenia. Grafy często reprezentują różne problemy spotykane w rzeczywistości.
Przykładowo węzły w grafie mogą reprezentują miasta na mapie, a krawędzie 
połączenia lotnicze między miastami. Graf nie odzwierciedla geograficznego układu
widocznych na mapie elementów. Przedstawia jedynie związki między wierzchołkami,
czyli to, z którymi wierzchołkami jest połączona dana krawędz.
Innym przykładem może być graf reprezentujący poszczególne zadania -
składowe pewnego projektu, natomiast skierowane krawędzie określają kolejność
wykonywania zadań.
Definicja
Grafem zorientowanym (skierowanym lub digrafem) nazywany strukturę zawierającą
G = (V, E), przy czym:
V - skończony niepusty zbiór wierzchołków,
E V V - zbiór krawędzi, krawędzie są parami wierzchołków.
W grafie skierowanym, jeśli (i, j) jest krawędzią to j jest następnikiem i, i jest
poprzednikiem j, przy czym i może być równe j  wtedy istnieje pętla od danego
wierzchołka do niego samego.
Poniższy rysunek przedstawia graf skierowany G = (V, E )
1
2
3
4
IIUJ Metody Programowania strona: 2
V= {1, 2, 3, 4} E = {(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 2), (3, 4), (4, 3)}
Definicja
Grafem niezorientowanym (nieskierowanym) nazywamy strukturę : G = (V, E),
przy czym:
V - skończony niepusty zbiór wierzchołków
E 2V  zbiór krawędzi, " e E : | e | = 2,
krawędzie są dwuelementowymi podzbiorami.
W grafie niezorientowanym, jeśli (i, j) jest krawędzią to j jest sąsiadem i, i jest
sąsiadem j.
W grafie niezorientowanym nie mogą występować pętle, każda krawędz zawiera
dokładnie dwa różne wierzchołki.
Typowe oznaczenie: |V|= n, |E| = m.
Liczba krawędzi:
q w grafie zorientowanym: 0 Ł m Ł n * n
q w grafie niezorientowanym: 0 Ł m Ł n * (n-1)/2
Reprezentacja komputerowa grafów
Utożsamiamy zbiór wierzchołków V = {1, ... ,n}
1. Tablica (macierz) sąsiedztwa, jej wielkość nie zależy od liczby krawędzi
w grafie:
1 (i, j) E
adjMat[i, j] =
0 (i, j) E
Dla grafu niezorientowanego jest symetryczna, z zerami na przekątnej.
Fakt: Rozmiar pamięci potrzebnej dla tablicy sąsiedztwa wynosi Q(n2).
2. Listy sąsiedztwa (listy przylegania), kolejność na listach dowolna:
IIUJ Metody Programowania strona: 3
Tablica L[n] nagłówków list (częste oznaczenie: Adj[n]  "adjacency")
L[i] - jest nagłówkiem listy zawierającej wszystkie j, takie że (i, j) jest krawędzią.
Dla grafu niezorientowanego każda krawędz (i, j) reprezentowana jest w liście L[i]
oraz w liście L[j].
Fakt: Rozmiar pamięci potrzebnej dla list sąsiedztwa wynosi Q(n + m).
Terminologia
Stopień wierzchołka: liczba jego następników (sąsiadów)
Ścieżka od wierzchołka u do wierzchołka w: ciąg wierzchołków v , v , ..., v taki, że
0 1 k
(v , v ) E, i=0,...,k-1, v , = u, v = w (inaczej: wierzchołek w jest osiągalny z
i i+1 0 k
wierzchołka u)
Ścieżka prosta (elementarna): wierzchołki na ścieżce nie powtarzają się (być może za
wyjątkiem v = v )
0 k
W grafie zorientowanym ścieżka v , v , ..., v tworzy cykl, jeśli v = v oraz zawiera co
0 1 k 0 k
najmniej jedną krawędz. Cykl nazywamy prostym, jeśli dodatkowo v , ..., v są różne.
1 k
W grafie niezorientowanym ścieżka v , v , ..., v tworzy cykl, jeśli v = v oraz v , ..., v
0 1 k 0 k 1 k
są różne i kł2 (zawiera co najmniej trzy wierzchołki).
IIUJ Metody Programowania strona: 4
Długość ścieżki: liczba krawędzi na ścieżce (tutaj: k)
Odległość od wierzchołka v do wierzchołka u - długość najkrótszej ścieżki od u do v
(przyjmujemy Ą jeśli v nie jest osiągalny z u)
Podgraf grafu G=(V, E) :
graf H=(V', E') taki, że V' V, E' E
Podgraf indukowany grafu G=(V, E) :
graf H=(V', E') taki, że V'V, E' = {(u, v) E: u, v V' }
Definicje  dla grafu niezorientowanego G=(V, E) :
G jest spójny: każdy wierzchołek jest osiągalny z każdego wierzchołka
Spójna składowa grafu G : maksymalny podgraf indukowany H taki, że H jest
spójny
G jest drzewem: gdy G jest spójny i nie zawiera cykli (często z wyróżnionym
wierzchołkiem zwanym korzeniem)
Drzewo rozpinające dla spójnego niezorientowanego grafu G=(V,E):
drzewo T=(V', E') takie, że V'=V, E' E ,
czyli podgraf bez cykli, łączący wszystkie wierzchołki
Definicje - dla grafu zorientowanego:
q G jest silnie spójny: gdy każdy wierzchołek jest osiągalny z każdego wierzchołka
q Silnie spójna składowa grafu G: maksymalny podgraf indukowany H taki, że H jest
silnie spójny
IIUJ Metody Programowania strona: 5
Reprezentacja grafu w Javie
W większości zastosowań praktycznych wierzchołek reprezentuje pewien obiekt
odpowiedniej klasy. W wierzchołkach przechowujemy zwykle label (etykietę) typu
znakowego oraz informację reprezentowaną przez zmienną wasVisited (był
odwiedzony) typu logicznego.
Zatem klasa wierzchołek ma postać:
class Vertex {
public char label; // etykieta , np. A
public boolean wasVisited; // czy był odwiedzony
public Vertex(char lab) // konstruktor
{
label = lab;
wasVisited = false; // nie był odwiedzony
}
} // end class Vertex
Obiekty klasy Vertex mogą być umieszczane w tablicy i mogą być wywoływane
przy użyciu numeru indeksu. W przykładowym programie użyjemy tablicy o nazwie
vertexList.
Wierzchołki można również przechowywać jako listę lub inną strukturę, przy
czym typ struktury nie jest powiązany z systemem łączenia wierzchołków za pomocą
krawędzi.
Do zapamiętania krawędzi grafu można użyć macierze sąsiedztwa lub listy
sąsiedztwa w reprezentacji wiązanej.
Poniżej podano definicję klasy graph, w której do zapamiętania krawędzi użyto
macierzy sąsiedztwa  adjMat[ ] [ ].
class Graph{
private int MAX_VERTS = 20;
private Vertex vertexList[]; // lista wierzchołków
private int adjMat[ ][ ]; // macierz sąsiedztwa
private int nVerts; // bieżąca liczba wierzchołków
// -------------------------------------------------------------------------------------------------
public Graph() { // konstruktor
vertexList = new Vertex[MAX_VERTS]; // tablica wierzchołków
adjMat = new int[MAX_VERTS][MAX_VERTS]; // macierz sąsiedztwa
nVerts = 0; // liczba węzłów
for(int j=0; j < MAX_VERTS; j++) // zerowanie macierzy sąsiedztwa
for(int k=0; k adjMat[ j ][ k ] = 0;
} // end constructor
IIUJ Metody Programowania strona: 6
// -------------------------------------------------------------
public void addVertex(char lab) {
vertexList[nVerts++] = new Vertex(lab);
}
// -------------------------------------------------------------
public void addEdge(int start, int end) { // dla grafu nieozrientowanego
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
// -------------------------------------------------------------
public void displayVertex(int v) {
System.out.print(vertexList[v].label);
}
}
Algorytmy przeglądu grafu
Przeglądanie grafu wszerz - metoda BFS (Breadth First Search)
Założenie: wszystkie wierzchołki są osiągalne z wierzchołka s
Idea metody:
Startujemy z s, chcemy dotrzeć do wszystkich wierzchołków w następującej
kolejności:
- najpierw wierzchołki odległe od wierzchołka startowego o 1
- następnie wierzchołki odległe o 2
- następnie wierzchołki odległe o 3, itd.
Założenie.
Dany graf G=(V, E), V- lista wierzchołków, E  zbiór krawędzi.
L(u)  lista węzłów przyległych do u (lista sąsiedztwa)
Podstawa realizacji - kolejka
Kolejka przechowuje węzły do których już dotarliśmy, ale dla których jeszcze nie
badaliśmy możliwych wyjść (krawędzi) prowadzących dalej.
Algorytm BFS (G, s) { // przegląd wszerz grafu G, wierzchołek startowy s
// color[ ], d[ ], P[ ]  pomocnicze tablice indeksowane wierzchołkami
// Q  kolejka wierzchołków
for each u V \ {s} do // kolejno dla każdego węzła inicjalizacja
{ // pomocniczych tablic
IIUJ Metody Programowania strona: 7
color[u] = white; // u  nieodwiedzony
d[u] = n+1 // albo plus nieskończoność ( d[u]: odległość u od s)
P[u] = null // poprzednik
}
color[s] = grey ; // s - odwiedzony
d[s] = 0;
P[s] = null ;
Queue Q = new Queue(); // utwórz kolejkę pustą
Q.insert(s); // początkowa zawartość kolejki  tylko s
while ( ! Q.is_empty() ) { // kolejka nie pusta
u = Q.first(); // wez pierwszy element
for each v L[u] do // to jest pętla przeglądająca listę następników
if (color[v] == white) {
color[v] = grey; // v - odwiedzony
d[v] = d[u] + 1;
P[v] = u; // u jest poprzednikiem v
Q.insert(v);
}
Q.delete() ; // wyrzuć pierwszy z kolejki, czyli u
color[u] = black; // wszystkie następniki u zostały odwiedzone
} // koniec while
} // koniec BFS
Wyniki:
q d[u] - odległość u od s (długość najkrótszej ścieżki z s do u mierzona liczbą krawędzi
na ścieżce)
q T = (V(G), {E=(P[u], u)} ) - drzewo rozpinające dla wszystkich wierzchołków
osiągalnych z s, dla ustalonego korzenia s - najniższe z możliwych.
Złożoność algorytmu BFS
każdy wierzchołek v jest wstawiany i usuwany z kolejki dokładnie raz, zatem
liczba iteracji pętli while wynosi n-1
każda krawędz wychodząca z wierzchołka jest badana dokładnie raz, zatem
liczba iteracji pętli for w sumie we wszystkich iteracjach pętli while jest równa
IIUJ Metody Programowania strona: 8
sumie długości list sąsiedztwa (czyli 2m jeśli graf niezorientowany, m jeśli graf
zorientowany)
Zatem całość ma złożoność: Q(n+m)
Przykład wykonania algorytmu BFS:
2 6
8
H
F
B
1
3
C
A
4 7 9
I
G
D
8
8
5
8
E
Listy sąsiedztwa dla podanych wierzchołków:
L[A] = B, C, D, E L[E] = Ć L[I] = Ć
L[B] = F, H L[F] = H
L[C] = Ć L[G] = I
L[D] = G, I L[H] = Ć
Kolorem czerwonym zaznaczono kolejność wypisywania węzłów w BFS.
Przykład - implementacji algorytmu BFS w Javie
// bfs.java
// demonstracja wyszukiwania "wszerz"
class Vertex{
public char label; // etykieta (np. 'A')
public boolean wasVisited;
// -------------------------------------------------------------
public Vertex(char lab) { // konstruktor
label = lab;
wasVisited = false;
}
// -------------------------------------------------------------
} // end class Vertex
class Graph {
private final int MAX_VERTS = 20;
private Vertex vertexList[]; // lista wierzchołków
private int adjMat[][]; // tablica sąsiedztwa
IIUJ Metody Programowania strona: 9
private int nVerts; // bieżąca liczba wierzchołków
// ------------------------------------------------------------
public Graph() { // konstruktor
vertexList = new Vertex[MAX_VERTS]; // lista wierzchołków
adjMat = new int[MAX_VERTS][MAX_VERTS]; // tablica sąsiedztwa
nVerts = 0;
for(int j=0; j for(int k=0; k adjMat[j][k] = 0;
} // end constructor
// -------------------------------------------------------------
public void addVertex(char lab)
{
vertexList[nVerts++] = new Vertex(lab);
}
// -------------------------------------------------------------
public void addEdge(int start, int end)
{
adjMat[start][end] = 1;
adjMat[end][start] = 1;
}
// -------------------------------------------------------------
public void displayVertex(int v)
{
System.out.print(vertexList[v].label+" ");
}
// -------------------------------------------------------------
public void bfs(int s) // wyszukiwanie "wszerz"
{ // rozpocznij od wierzchołka s
// wszystkie węzły są osiągalne z s
vertexList[s].wasVisited = true; // oznacz wierzchołek jako odwiedzony
displayVertex(s); // wyświetl wierzchołek
Queue Q= new Queue(); // utwórz kolejkę pustą
Q.insert(s); // wstaw do kolejki
int v;
while( !Q.isEmpty() ) // do opróżnienia kolejki,
{
int u = Q.first(); // pierwszy wierzchołek
while( (v=getAdjUnvisitedVertex(u)) != -1 ) // dopóki ma nie odwiedzonych sąsiadów,
{ // do v pobierz kolejnego sąsiada u,
vertexList[v].wasVisited = true; // oznacz v jako odwiedzony
displayVertex(v); // wyświetl v
Q.insert(v); // wstaw v do kolejki
} // end while
IIUJ Metody Programowania strona: 10
Q.delete(); // usuń z kolejki element u
} // end while(kolejka nie jest pusta)
} // end bfs()
// -------------------------------------------------------------
public int getAdjUnvisitedVertex(int v) // zwraca nie odwiedzony wierzchołek przyległy do v
{
for(int j=0; j if(adjMat[v][j] == 1 && vertexList[j].wasVisited == false)
return j;
return -1;
} // end getAdjUnvisitedVertex()
// -------------------------------------------------------------
} // end class Graph
class BFSApp
{
public static void main(String[] args)
{
Graph theGraph = new Graph();
theGraph.addVertex('A'); // 0 (początek wyszukiwania)
theGraph.addVertex('B'); // 1
theGraph.addVertex('C'); // 2 A -- B -- C
theGraph.addVertex('D'); // 3 |
theGraph.addVertex('E'); // 4 D -- E
theGraph.addEdge(0, 1); // AB
theGraph.addEdge(1, 2); // BC
theGraph.addEdge(0, 3); // AD
theGraph.addEdge(3, 4); // DE
System.out.print("Odwiedzone wierzcholki: ");
theGraph.bfs(0); // wyszukiwanie "wszerz" startując od 0
System.out.println();
} // end main()
} // end class BFSApp
IIUJ Metody Programowania strona: 11
Przeglądanie grafu w głąb - metoda DFS - (Depth First Search)
Idea: metoda nawrotów (backtracking)
Idz do nowych wierzchołków najdalej jak się da, jeśli dalej nie można to wycofaj
się do poprzedniego i próbuj inną krawędzią.
2
3
4
H
F
B
1
5
C
A
6 7 8
I
G
D
8
8
9
8
E
Kolorem czerwonym zaznaczono kolejność wypisywania węzłów w DFS.
Realizacja: podobnie jak BFS, ze stosem zamiast kolejki.
Stos można zrealizować niejawnie, za pomocą rekurencji.
Algorytm DFS (G)
{
for each u V do { // inicjalizacja
color[u] = white ;
P[u] = null ; // poprzednik w drzewie
}
time = 0 ; // zmienna globalna
for each u V do
if (color[u] == white)
DFS-Visit (u);
}
//--------------------------------------------------------------------------------------------
void DFS-Visit (u) {
color[u] = grey ; d[u] = ++time ;
for each v L[u] do // badaj krawędz (u, v)
IIUJ Metody Programowania strona: 12
if ( color[v] == white ) {
P[v] = u ;
DFS-Visit (v);
}
color[u] = black; f[u] = ++ time ;
}
Przykład wykonania przeglądu DFS w grafie skierowanym
Złożoność:
każdy wierzchołek odwiedzany raz
każda krawędz z danego wierzchołka badana raz
Zatem Q(n+m)
Wynik: T = (V(G), {E=(P[u], u)} ) - jest lasem rozpinającym dla G  tzw. las
przeszukiwania w głąb (dla grafu niezorientowanego spójnego jest to jedno drzewo).
Podstawowa własność przeszukiwania w głąb:
Twierdzenie o nawiasach
W dowolnym przeszukiwaniu w głąb (niezorientowanego lub zorientowanego)
grafu dla dowolnych dwóch wierzchołków u, v zachodzi dokładnie jeden z trzech
poniższych warunków dotyczących przedziałów (czasowych) między odkryciem węzła
a zakończeniem jego przetwarzania :
IIUJ Metody Programowania strona: 13
q [d(u), f(u)] [d(v), f(v)] = Ć, albo
q [d(u), f(u)] [d(v), f(v)], albo
q [d(v), f(v)] [d(u), f(u)]
Przykład nie rekurencyjnej implementacji DFS w Javie.
void dfs()
{ // wyszukiwanie "wgłąb" - rozpocznij od węzła 0
vertexList[0].wasVisited = true; // oznacz węzeł 0
displayVertex(0); // wyświetl węzeł 0
Stack S = new Stack(); // utwórz stos pusty
S.push(0); // zapisz na stos
while( ! S.isEmpty() ) // do opróżnienia stosu,
{
// pobierz nie odwiedzony węzeł, przyległy do szczytowego elementu stosu
int v = getAdjUnvisitedVertex( S.top() );
if( v == -1 ) // jeżeli nie ma takiego węzła,
S.pop();
else // jeżeli istnieje,
{
vertexList[v].wasVisited = true; // oznacz węzeł v
displayVertex(v); // wyświetl węzeł
S.push(v); // zapisz na stos
}
} // end while
} // end dfs
Minimalne drzewo rozpinające (MST- Minimum Spanning Tree)
Rozważmy problem, znalezienia podgrafu o najmniejszej liczbie krawędzi
wymaganej do połączenia wierzchołków w zadanym grafie (spójnym/silnie spójnym).
Taki podgraf jest nazywamy minimalnym drzewem rozpinającym. Dla danego grafu
spójnego istnieje wiele różnych minimalnych drzew rozpinających.
IIUJ Metody Programowania strona: 14
Poniżej przestawiono przykład grafu i jednego z minimalnych drzew rozpinających.
D
B
Graf niezorientowany spójny
A
E
C
D
B
Podgraf z minimalną liczbą krawędzi.
A
E
C
Ten problem występuje przy projektowaniu płytki drukowanej, która jest
reprezentowana przez graf niezorientowany. Na płytce rozmieszczone są różnorodne
elementy np. układy scalone, których nóżki są wprowadzane do dziurek w płytce.
Układy scalone są przylutowane do styków, a ich nóżki zostaną połączone z
innymi elementami płytki przy użyciu ścieżek- wąskich pasków metalu osadzonych na
powierzchni płytki. Każdy punkt montażowy może być reprezentowany w grafie jako
wierzchołek, a każda ścieżka  jako krawędz.
Szukając minimalnego drzewa rozpinającego chcemy sprawdzić, czy została
użyta minimalna liczba ścieżek. Dążymy do usunięcia zbędnych połączeń między
stykami elementów. Takie połączenia zajmują miejsce i utrudniają dołączanie innych
układów.
Algorytm tworzenia minimalnego drzewa jest prawie identyczny jak DFS:
void mst()
{ // minimalne drzewo rozpinające (wyszukiwanie "w głąb")
// rozpocznij od węzła 0
vertexList[0].wasVisited = true; // oznacz węzeł 0
Stack S = new Stack(); // utwórz stos pusty
S.push(0); // zapisz na stos
while( ! S.isEmpty() ) // do opróżnienia stosu,
{
IIUJ Metody Programowania strona: 15
int u = S.top(); // aktualny węzeł
int v = getAdjUnvisitedVertex(u); // pobierz nie odwiedzony węzeł, przyległy do
// szczytowego elementu stosu (u)
if( v == -1 ) // jeżeli nie ma takiego węzła,
S.pop();
else // jeżeli istnieje,
{
vertexList[v].wasVisited = true; // oznacz węzeł v
S.push(v); // zapisz na stos
displayVertex(u); // wyświetl krawędz od u
displayVertex(v); // do v
System.out.print( ,  )
}
} // end while
} // end mst()
Podobieństwo algorytmu znajdowania minimalnego drzewa rozpinającego do
DFS wynika ze spostrzeżenia, że algorytm DFS odwiedza każdy węzeł dokładnie jeden
raz. Żaden węzeł nie zostanie ponownie odwiedzony. Algorytm nigdy nie wybiera
krawędzi, która prowadzi do takiego węzła.
Ponieważ z ścieżce przeglądania DFS nie ma żadnych zbędnych krawędzi,
algorytm mst() wyznacza minimalne drzewo rozpinające.
Przykłady zastosowań algorytmów przeglądania grafów
1. Rozwiązanie problemu osiągalności
Czy wierzchołek v jest osiągalny z wierzchołka u.
2. Wyznaczanie spójnych składowych grafu niezorientowanego:
dla każdego u V należy obliczyć ss[u] = k - numer spójnej składowej
zawierającej wierzchołek u, (wierzchołki w tej samej składowej dostaną jeden
numer ss)
zmienną globalną k, zerowaną na początku, zwiększamy o 1 przy każdym
wywołaniu funkcji DFS-Visit z głównej pętli w algorytmie DFS
na początku funkcji DFS-Visit wykonujemy instrukcję ss[u]=k
IIUJ Metody Programowania strona: 16
Do obliczenia ss[] w algorytmie DFS można opuścić:
trzeci kolor (wystarczą dwa)
poprzednik w drzewie, P[v]
tablice d[ ], f[ ].
Oba powyższe problemy można rozwiązać również wykorzystując algorytm BFS.
3. Sprawdzanie czy graf skierowany zawiera cykle (test acykliczności grafu)
Definicja
Niech u - węzeł aktualny,
(u ą v) - badana krawędz w trakcie działania DFS-u
Krawędz (u ą v) grafu nazywamy:
1. drzewowa: gdy color[v] = white
2. wsteczna: gdy color[v] = grey ; (do przodka w drzewie)
3. wzdłużna (wyprzedzająca): gdy color[v] = black oraz d[u] < d[v] ;
(do potomka w drzewie)
4. poprzeczna: gdy color[v] = black oraz d[u] > d[v];
(do poddrzewa, którego przegląd się już zakończył)
Fakt.
IIUJ Metody Programowania strona: 17
W DFS w grafie niezorientowanym krawędzi poprzecznych nie ma,
a wsteczne pokrywają się z wzdłużnymi, za wyjątkiem wstecznej do poprzednika,
pokrywającej się z krawędzią drzewową - czyli są tylko dwa rodzaje krawędzi
Fakt.
(1) Graf zorientowany zawiera cykl wtedy i tylko wtedy gdy w dowolnym drzewie
DFS istnieje krawędz wsteczna.
(2) Dla ustalonego drzewa (lasu) DFS każdy cykl w grafie skierowanym zwiera co
najmniej jedną krawędz wsteczną.
Wniosek:
W czasie Q(n+m) można sprawdzić czy graf zawiera cykle.
4. Sortowanie topologiczne grafu zorientowanego
Sortowanie topologiczne wierzchołków polega na takim ustawieniu
wierzchołków w liniowej kolejności, aby dla każdej krawędzi (uv) grafu wierzchołek
v występował pózniej niż u. (***)
Sortowanie topologiczne nie może być stosowane w grafach, w których
występują cykle.
Zastosowanie: Szeregowanie czynności w produkcji, w obliczeniach zależności
funkcyjnych czy też ustalanie kolejności zaliczania kursów na studiach, gdy na wybrany
kurs można się zapisać jeśli zaliczone zostały inne kursy etc.
Rozwiązanie:
Zastosuj algorytm DFS(G), wpisując wierzchołek u na początek listy w
momencie jego kolorowania na czarno.
IIUJ Metody Programowania strona: 18
Lista definiuje porządek topologiczny. Po wypisaniu wierzchołków w tej kolejności
wszystkie krawędzie grafu skierowane są w prawą stronę.
f h g e a b
d
c
Uwaga.
Dla powyższego grafu nie jest to jedyna kolejność wierzchołków spełniających
wymaganie (***), np. listy : f, h, e, g, a, b, d, c oraz f, h, g, a, e, b, d, c też spełniają
wymagania sortowania topologicznego.
Wniosek:
Sortowanie topologiczne działa w czasie Q(n+m).


Wyszukiwarka