Wykład dwunasty: Wprowadzenie do grafów
Transkrypt
Wykład dwunasty: Wprowadzenie do grafów
Podstawy Programowania
semestr drugi
Wykład jedenasty
1.
Teoria grafów
Grafy są w informatyce strukturami danych, które mają szerokie zastosowania. Zanim zaczęto stosować je w algorytmice istniały już w
matematyce, gdzie wprowadził je Leonhard Euler rozwiązując problem siedmiu mostów w Królewcu i tworząc tym samym nową dyscyplinę
matematyczną topologię. Od tego czasu grafy były stosowane do rozwiązywania innych, bardziej zaawansowanych problemów, niemniej po
dziś dzień nie udało się wypracować dla nich jednolitej terminologii. Poniżej znajdują się definicje, które będą obowiązywać na tym
wykładzie:
graf skierowany1 G jest opisany parą (V, E), gdzie V jest zbiorem skończonym, którego elementami są wierzchołki grafu G, a E jest
. Zbiór V nazywamy krótko zbiorem wierzchołków, natomiast zbiór E jest nazywany zbiorem
relacją binarną w V i
krawędzi G, a jego elementy nazywamy krawędziami.
graf nieskierowany jest grafem, którego zbiór E jest nieuporządkowany. Oznacza to, że krawędź jest zbiorem {u,v}, gdzie
i
. Krawędzi będziemy oznaczać używając zapisu (u,v). Zapisy (u,v) i (v,u) oznaczają tę samą krawędź. W
grafie nieskierowanym nie mogą występować pętle, czyli krawędzie prowadzące do tego samego wierzchołka w którym się
zaczynają.
Przykład grafu skierowanego:
1
2
3
4
Przykład grafu nieskierowanego:
1
2
3
4
w grafie skierowanym G=(V,E) krawędź (u,v) jest krawędzią wychodzącą z wierzchołka u i wchodzącą do wierzchołka v. W grafie
nieskierowanym mówimy, że krawędź (u,v) jest incydentna z wierzchołkami u i v.
wierzchołek v jest sąsiedni do wierzchołka u, w grafie G=(V,E) jeśli łączy te wierzchołki krawędź (v,u). W grafie nieskierowanym
relacja sąsiedztwa nie musi być symetryczna.
stopniem wierzchołka w grafie nieskierowanym jest liczba incydentnych z nim krawędzi. W grafie skierowanym stopniem
wejściowym wierzchołka nazywamy liczbę krawędzi wchodzących do tego wierzchołka, a stopniem wyjściowym liczbę krawędzi z
niego wychodzących. W grafie skierowanym stopniem wierzchołka jest suma stopnia wyjściowego i wyjściowego.
Droga (ścieżka) długości k z wierzchołka u do wierzchołka u' w grafie G=(V,E) jest ciągiem wierzchołków <v0, v1, v2, ..., vk> ,
takich, że u=v0, u'=vk i
dla i = 1, 2, ..., k. Długość ścieżki jest liczbą krawędzi ścieżki. Ścieżka zawiera wierzchołki
v0, v1, v2, ..., vk i krawędzie (v0, v1), (v1, v2), ..., (vk-1, vk). Jeśli istnieje ścieżka z u do u', to mówimy, że u' jest osiągalny z u po ścieżce
p. Ścieżka jest nazywana ścieżką prostą jeśli wszystkie jej wierzchołki są różne.
Ścieżka <v0, v1, v2, ..., vk> tworzy cykl jeśli v0=vk. Cykl nazywamy cyklem prostym jeśli dodatkowo wszystkie jego wierzchołki są
różne. Pętla jest cyklem o długości 1. Graf skierowany nie posiadający pętli nazywamy grafem prostym. Graf, który nie zawiera
cykli nazywamy grafem acyklicznym.
Graf nieskierowany jest spójny, jeśli każda para wierzchołków jest połączona ścieżką. Graf jest silnie spójny, jeśli każde dwa
wierzchołki są osiągalne jeden z drugiego.
, takie że jeśli
Dwa grafy G=(V,E) i G'=(V',E') są izomorficzne jeśli istnieje wzajemnie jednoznaczne odwzorowanie
, to
. Z tej własności grafów wynika, że mając graf skierowany możemy go zastąpić wersją
skierowaną zamieniając każdą krawędź na dwie przeciwnie skierowane krawędzie. Graf skierowany możemy zastąpić wersją
nieskierowaną zastępując każdą krawędź skierowaną krawędzią nieskierowaną i usuwając pętle.
Graf nieskierowany nazywamy grafem pełnym jeśli każda para jego wierzchołków jest połączona krawędzią. Ilość krawędzi w
takim grafie jest równa
, gdzie n jest liczbą wierzchołków grafu. Graf zawierający małą liczbę krawędzi w stosunku do
liczby wierzchołków nazywamy grafem rzadkim.
2.
Grafy jako struktury danych
Istnieją dwa podstawowe sposoby reprezentowania grafu: za pomocą macierzy sąsiedztwa i za pomocą list sąsiedztwa. Listy sąsiedztwa
stosujemy zazwyczaj do grafów rzadkich, ze względu na oszczędność pamięci jaką te listy oferują, natomiast dla grafów gęstych, czyli dla
grafów pełnych lub zbliżonych do pełnych stosujemy reprezentację za pomocą macierzy2. Reprezentację macierzową stosuje się również
wszędzie tam, gdzie trzeba szybko stwierdzić, czy istnieje krawędź łącząca dwa dane wierzchołki. Przykład obu reprezentacji dla grafu
skierowanego i nieskierowanego:
1
2
Graf skierowany jest również określany jako digraf.
Statystycznie częściej korzysta się z list sąsiedztwa.
1
Podstawy Programowania
1
2
3
5
1
4
4
2
5
3
6
semestr drugi
1
2
5
2
1
5
3
2
4
4
2
5
3
5 /
4
1
2
1
2
2
5
3
6
4
2
/
5
4
/
6 /
6
/
/
4
3
/
/
4
/
5
/
/
/
/
W reprezentacji grafu za pomocą list sąsiedztwa dana jest lista lub tablica zawierająca po jednej liście dla każdego wierzchołka grafu.
Elementami tych list są wierzchołki sąsiadujące z danym wierzchołkiem. Porządek wierzchołków w każdej z tych list jest dowolny. Jeśli graf
jest skierowany, to suma długości wszystkich list sąsiedztwa wynosi |E|3, natomiast dla grafu nieskierowanego wynosi ona 2|E|. Reprezentacja
listowa wymaga O(V+E) pamięci. Reprezentacja listowa nadaje się również do reprezentowania grafów z wagami. Macierz sąsiedztwa grafu
pamięci i może być również używana do reprezentowania grafów z wagami. Macierze sąsiedztwa są macierzami
wymaga
kwadratowymi i symetrycznymi względem głównej przekątnej. Jeśli graf, który reprezentuje macierz sąsiedztwa A jest grafem skierowanym, to
zachodzi równość A=AT, gdzie wyrażenie po lewej stronie znaku równości oznacza operację transpozycji macierzy. Dla takich grafów można
zaoszczędzić ilość pamięci wymaganej do zapamiętania ich macierzy sąsiedztwa pamiętając tylko elementy powyżej głównej przekątnej (tzw.
macierz trójkątną górną). W przypadku macierzy sąsiedztwa grafów bez wag można zastosować inną metodę oszczędności pamięci
poszczególne elementy można pamiętać za pomocą jednego bitu. Reprezentacja macierzowa może okazać się wygodniejsza do zastosowania w
przypadku grafów o małych rozmiarach. Można oczywiście jedną reprezentację zamienić na inną. Poniżej przedstawiony jest program, który
zamienia reprezentację grafu za pomocą macierzy sąsiedztwa na reprezentację w postaci list sąsiedztwa:
3
Zapis ten oznacza liczebność zbioru.
2
Podstawy Programowania
semestr drugi
1 program graf;
2 uses crt;
3 type wskaznik=^element;
4
element=record
5
dana:integer;
6
next:wskaznik;
7
down:wskaznik;
8
9
10
end;
tablica = array [1..5] of byte;
macierz = array [1..5] of tablica;
11 const
12
matrix:macierz=((0,1,0,0,1),(1,0,1,1,1),(0,1,0,1,0),(0,1,1,0,1),(1,1,0,1,0));
13 var
14 state:wskaznik;
15 ma,m:longint;
16
17 procedure convert(var s:wskaznik; const m:macierz);
18 var
19 nowy,v,h:wskaznik;
20 j,i:byte;
21 begin
22 for i:=low(tablica) to high(tablica) do begin
23
new(nowy);
24
nowy^.dana:=i;
25
nowy^.down:=nil;
26
nowy^.next:=nil;
27
if s=nil then
28
begin
29
s:=nowy;
30
h:=s;
31
end
32
33
else
begin
34
h^.down:=nowy;
35
36
h:=h^.down;
end;
37
end;
38
h:=s;
39
v:=h;
40
for i:=low(tablica) to high(tablica) do begin
41
42
for j:=low(tablica) to high(tablica) do
if m[i,j]=1 then begin
43
new(nowy);
44
nowy^.dana:=j;
45
nowy^.down:=nil;
46
nowy^.next:=nil;
3
Każdy element listy sąsiedztwa (wiersze 3 -8 będzie
zawierał dwa wskaźniki: „down”, który będzie służył
do budowania lisy „pionowej” i „next”, który będzie
służył do budowania list sąsiedztwa. Ponadto każdy
taki element będzie przechowywał numer wierzchołka
w polu „dana”. Macierz jest reprezentowana przez
zmienną zainicjowaną „matrix”, typu „macierz”. Jest
to tablic dwuwymiarowa 5x5. W programie jest
również zadeklarowana zmienna wskaźnikowa „state”,
która będzie pamiętać adres pierwszego elementu na
liście „pionowej”.
Procedurą, która realizuje główne zadanie, czyli
zmianę reprezentacji macierzowej na listową jest
procedura „convert”. Posiada ona dwa parametry.
Pierwszy jest parametrem wyjściowym i przez niego
przekazywany jest „na zewnątrz” adres list sąsiedztwa,
natomiast drugi parametr jest parametrem wejściowym
i przez niego przekazywana jest macierz sąsiedztwa,
na podstawie której zostanie utworzona reprezentacja
listowa. Procedura ta posiada również pięć zmiennych
lokalnych. Zmienne „i”, „j” służą do indeksowania
macierzy, zmienna wskaźnikowa „nowy” służy do
przechowywania adresu nowego elementu, natomiast
zmienne „h” i „v”, służą do „poruszania się”
odpowiednio po „pionowej” liście, której elementy
reprezentują wszystkie wierzchołki w grafie i która
dalej będzie nazywana listą wierzchołków grafu i po
listach „poziomych” będących listami sąsiedztwa. W
pierwszej pętli „for” (wiersze 22 37) tworzona jest
lista wierzchołków grafu. Kod umieszczony w tej pętli
jest podobny do kodu procedury tworzącej kolejne
elementy kolejki. W wierszach 38 39 do zmiennej
„h” przepisywany jest adres pierwszego elementu listy
wierzchołków, który jest pamiętany w zmiennej „s”, a
następnie ten sam adres jest przepisywany do zmiennej
„v”. W dwóch zagnieżdżonych pętlach „for” (wiersze
40
52) przeglądana jest macierz sąsiedztwa. Pętla
zewnętrzna odpowiedzialna jest za przeglądanie
wierszy, natomiast pętla wewnętrzna za przeglądanie
kolumn. Jeśli element należący do wiersza ma wartość
„1”, to tworzony jest nowy element listy sąsiedztwa i
dodawany na koniec tej listy. Adres pierwszego
elementu listy sąsiedztwa zapamiętywany jest w polu
„next”
elementu
listy
wierzchołków,
który
reprezentuje wierzchołek, którego sąsiedzi zostali
umieszczeni na rozważanej liście sąsiedztwa. Po
zakończeniu przeglądania bieżącego wiersza procedura
przechodzi do następnego wiersza przechodząc
jednocześnie do elementu listy wierzchołków, który
reprezentuje wierzchołek związany z tym wierszem
macierzy (wiersze 50 i 51).
Procedura „show” wypisuje na ekran zawartość list
sąsiedztwa. Do tej procedury przekazywany jest adres
pierwszego elementu na liście wierzchołków. Zmienne
lokalne „h” i „v” służą tak, jak w procedurze „convert”
do „poruszania się” odpowiednio po liście
wierzchołków i po listach sąsiedztwa. Pętla zewnętrzna
„while” (wiersz 60) przegląda listę wierzchołków i
wypisuje ich numery na ekran. Jeśli wskaźnik „next”
któregoś elementu tej listy jest różny od „nil”, to
oznacza to że dla tego elementu istnieje lista
sąsiedztwa i w pętli wewnętrznej „while” (wiersze 64
67) i kolejne wierzchołki sąsiednie z wierzchołkiem
wskazywanym przez „h” są wypisywane na ekran. Na
podobnej zasadzie działa procedura „remove”
usuwająca reprezentację listową grafu z pamięci. W tej
procedurze najpierw niszczone są elementu listy
sąsiedztwa danego wierzchołka (wiersze 79 83), a
następnie element reprezentujący ten wierzchołek jest
usuwany z listy wierzchołków (wiersze 84
86).
Czynności te są powtarzane do momentu usunięcia
wszystkich elementów reprezentacji listowej grafu z
pamięci. W programie głównym wszystkie opisane
wyżej procedury są wywoływane i dodatkowo
następuje sprawdzenie, czy pamięć została zwolniona
prawidłowo.
Podstawy Programowania
47
v^.next:=nowy;
48
v:=v^.next;
49
end;
50
h:=h^.down;
51
v:=h;
52
end;
53 end;
54
55 procedure show(s:wskaznik);
56 var
57 v,h:wskaznik;
58 begin
59 h:=s;
60 while h<>nil do begin
61
writeln;
62
write(h^.dana:3,':');
63
v:=h^.next;
64
while v<> nil do begin
65
write(v^.dana:3);
66
v:=v^.next;
67
end;
68
h:=h^.down;
69 end;
70 writeln(#10#13);
71 end;
72
73 procedure remove(var s:wskaznik);
74 var
75 tmpv,tmps,v:wskaznik;
76 begin
77 while s<>nil do begin
78
v:=s^.next;
79
while v<> nil do begin
80
tmpv:=v^.next;
81
dispose(v);
82
v:=tmpv;
83
end;
84
tmps:=s^.down;
85
dispose(s);
86
s:=tmps;
87 end;
88 end;
89
90 begin
91 clrscr;
92 ma:=memavail;
4
semestr drugi
Podstawy Programowania
semestr drugi
93 convert(state,matrix);
94 show(state);
95 remove(state);
96 m:=ma-memavail;
97 if m<>0 then writeln('Błąd zwalniania pamięci: ',m);
98 readln;
99 end.
3.
Podsumowanie
W reprezentacji listowej lista wierzchołków grafu może zostać zastąpiona tablicą, jeśli znamy maksymalną liczbę wierzchołków
reprezentowanych w programie grafów i jeśli nie chcemy oszczędzać na ich reprezentację pamięci. Grafy pozwalają modelować wiele
problemów. Są stosowane między innymi w problemach związanych z optymalizacją (np.: wyszukanie najkrótszej trasy między węzłami sieci
komputerowej, projektowanie układów VLSI, itd.) i do rozwiązywania problemów związanych z dyscypliną sztucznej inteligencji.
5