čo sa budeme dnes učiť
- základné najjednoduchšie algoritmy
na prechádzanie vrcholov v grafe
- zistenie počtu komponentov grafu a tým
aj súvislosť grafu
- nájdenie najkratšej cesty v grafe
pomocou algoritmu do hĺbky
Prehľadávanie grafov
- Budeme riešiť úlohy, v ktorých budeme
postupne prechádzať všetky vrcholy grafu
- prehľadávanie sa začne z nejakého (ľubovoľného)
vrcholu
- každý vrchol navštívime maximálne
raz a možno práve raz (závisí od
aplikácie)
- Postupujeme dvoma spôsobmi:
- do hĺbky - podobne ako preorder v stromoch
- do šírky - ako po úrovniach v stromoch
- Zadefinujeme pomocné pole na prehľadávanie,
v ktorom sa pamätá sa, či bol vrchol už
navštívený - často to býva stavová
premenná triedy TGraf:
- var visited:array[vrcholy] of boolean;
- môže sa robiť aj ako
- var visited:set of vrcholy;
- Procedúra spracuj(vrchol) - spracuje daný
vrchol (napr. vypíše info-položku)
Algoritmus do hĺbky
všeobecne:
|
procedure dohlbky(v1);
begin
visited:=visited + [v1];
spracuj(v1);
for w := všetkých susedov v1 do
if not (w in visited) then dohlbky(w)
end;
|
Budeme predpokladať, že máme definované
metódy na zistenie hrany, zafarbenie vrcholu
a hrany
napr. pre graf reprezentovaný poľom
množín:
|
type
TGraf = class
g:array of record
x,y:integer;
m:set of byte;
end;
visited:set of byte; // množina navštívených vrcholov
...
end;
function TGraf.jeHrana(v1,v2:integer):boolean;
begin
Result:=v2 in g[v1].m;
end;
procedure TGraf.zafarbiVrchol(v1:integer; f:TColor);
begin
c.Pen.Color:=f;
with g[v1] do c.Ellipse(x-r,y-r,x+r,y+r);
end;
procedure TGraf.zafarbiHranu(v1,v2:integer; f:TColor);
begin
c.Pen.Color:=f;
with g[v1] do c.MoveTo(x,y);
with g[v2] do c.LineTo(x,y);
end;
|
ale aj pre dynamické pole dynamických
polí:
|
type
THrana = class
cislo:integer;
constructor Create(v:integer);
end;
TVrchol = class
x,y:integer;
sus:array of THrana;
end;
TGraf = class
g:array of TVrchol;
visited:set of byte;
...
end;
function TGraf.jeHrana(v1,v2:integer):boolean;
var
i:integer;
begin
i:=high(g[v1].sus); while (i>=0) and (g[v1].sus[i].cislo<>v2) do dec(i);
Result:=i>=0;
end;
...
|
zafarbuje navštívené vrcholy
aj hrany, po ktorých prechádza:
|
procedure TGraf.dohlbky(v1:integer);
var
i:integer;
begin
visited:=visited+[v1];
zafarbiVrchol(v1,clRed); // spracuj vrchol
for i:=0 to high(g) do
if not (i in visited) and jeHrana(v1,i) then begin
zafarbiHranu(v1,i,clRed); // spracuj hranu
dohlbky(i);
end;
end;
|


|
- predpokladajme, že prehľadávanie štartujeme
vo vrchole 1
- tento vrchol sa zafarbí na červeno
- vo for-cykle sa nájde prvý ešte nenavštívený
sused vrcholu 1 - vrchol 2 - zafarbí sa hrana
a rekurzívne sa volá dohlbky pre 2
- aj tento vrchol 2 sa zafarbí na červeno a
nájde sa jeho sused - 4
- zafarbí sa hrana a opäť v rekurzívnom
volaní sa zafarbí aj 4
- prvý ešte nenavštívený sused
je 3 - zafarbí sa k nemu hrana a rekurzívne
sa volá dohlbky pre vrchol 3 - nakoľko 3 už nemá
nenavštívených susedov, toto volanie len
zafarbí 3 a končí
- pokračuje cyklus pre vrchol 4 - nájde sa sused
5 a zafarbí sa hrana
- algoritmus končí a teraz môžeme vidieť,
ktoré hrany sa zafarbili
|
Nerekurzívny algoritmus, ktorý iba
do množiny navštívených vrcholov pridá
všetky tie, ktoré sú dosiahnuteľné
z nejakého počiatočného vrcholu:
nerekurzívny algoritmus
do hĺbky:
|
procedure TGraf.dohlbky(v1:integer);
var
i:integer;
s:TStack;
begin
s:=TStack.Create; s.push(v1);
repeat
s.pop(v1);
visited:=visited+[v1];
for i:=0 to high(g) do
if not (i in visited) and jeHrana(v1,i) then
s.push(i);
until s.empty;
s.Free;
end;
|
Hoci tento algoritmus vyzerá v poriadku, ak
ho otestujeme na rovnakom jednoduchom grafe, zistíme,
že niektorý vrchol sa bude spracovávať
(zafarbovať) dvakrát a to môže byť v niektorých
situáciách neprijateľné:
- opäť začneme s vrcholom 1: najprv sa vloží
do prázdneho zásobníka, na začiatku
repeat-cyklu sa vyberie a spracuje - zaeviduje (a možno
aj zafarbí)
- vo for-cykle sa do teraz prázdneho zásobníka
vložia susedia 1, t.j. do zásobníka pribudnú
(2,3)
- na začiatku repeat-cyklu sa z vrchu zásobníka
vyberie 3, zaeviduje a všetky ešte nenavštívené
vrcholy, s ktorými susedí, sa pridajú
do zásobníka - do zásobníka
pribudne 4, t.j. je tam (2,4)
- z vrchu zásobníka sa vyberie 4, zaeviduje
sa a vložia sa na vrch zásobníka nenavštívení
susedia 4, t.j. v zásobníku teraz bude
(2,2,5) - vidíme, že do zásobníka
sa mohol dostať nenavštívený vrchol aj
viackrát, preto treba tento algoritmus opraviť
správne by mal nerekurzívny algoritmus
vyzerať takto:
|
procedure TGraf.dohlbky(v1:integer);
var
i:integer;
s:TStack;
begin
s:=TStack.Create; s.push(v1);
repeat
s.pop(v1);
if not (v1 in visited) then begin
visited:=visited+[v1];
for i:=0 to high(g) do
if not (i in visited) and jeHrana(v1,i) then
s.push(i);
end;
until s.empty;
s.Free;
end;
|
Ďalší variant prehľadávania do hĺbky
zafarbí danou farbou (parameter f) všetky vrcholy
aj všetky hrany nejakého komponentu:
farbenie všetkých hrán
a vrcholov:
|
procedure TGraf.dohlbky(v1:integer; f:TColor);
var
i:integer;
s:TStack;
begin
s:=TStack.Create; s.push(v1);
repeat
s.pop(v1);
if not (v1 in visited) then begin
visited:=visited+[v1];
zafarbiVrchol(v1,f);
for i:=0 to high(g) do
if jeHrana(v1,i) then begin
zafarbiHranu(v1,i,f);
if not (i in visited) then s.push(i);
end;
end;
until s.empty;
s.Free;
end;
|
Pozn.
- Zrejme pred každým volaním algoritmu
dohlbky by bolo treba nejako nastaviť množinu navštívených
vrcholov - visited na prázdnu
algoritmus do hĺbky môžeme použiť na:
- zistenie počtu komponentov grafu, resp. či je graf
súvislý
- nájdenie nejakej cesty medzi dvoma vrcholmi
- zistenie, či sú dva vrcholy v tom istom komponente
- zafarbovanie vrcholov a hrán
Nasledujúca metóda pomocou algoritmu
do hĺbky (rekurzívneho alebo nerekurzívneho)
zistí počet komponentov grafu - používa
preddefinované pole nejakých farieb -
príslušný prvok tohto poľa posiela do
metódy dohlbky, ktorá touto farbou zafarbí
daný podgraf (zrejme, môžeme použiť aj
metódu dohlbky bez zafarbovania vrcholov a hrán):
zafarbí všetky komponenty
a vráti ich počet:
|
const
f:array[0..max] of TColor = (...);
function TGraf.komponenty:integer;
var
i:integer;
begin
Result:=0; visited:=[];
for i:=0 to high(g) do
if not (i in visited) then begin
dohlbky(i,f[Result]);
inc(Result);
end;
end;
|
Algoritmus do šírky
Pri prehľadávaní stromu do šírky
(po úrovniach) sme potrebovali rad (front). Podobne
budeme postupovať aj pri algoritme pre graf:
najprv všeobecne:
|
procedure dosirky(v1);
var
q:TQueue;
w:integer;
begin
q:=TQueue.Create; q.append(v1);
repeat
q.serve(v1);
if not (v1 in visited) then begin
visited:=visited + [v1];
spracuj(v1);
for w := všetkých susedov v1 do
// for w:=0 to high(g) do if jeHrana(v1,w) then
if not (w in visited) then
q.append(w)
end;
until q.empty;
q.Free;
end;
|
Konkrétne použijeme algoritmus prehľadávania
do šírky na zafarbovanie vrcholov podľa toho,
ako ďaleko sú od počiatočného vrcholu
v1:
algoritmus do šírky:
|
procedure TGraf.dosirky(v1:integer);
var
i,u:integer;
q:TQueue; // vo fronte si pre každý vrchol pamätáme jeho úroveň - vzdialenosť
begin
q:=TQueue.Create; q.append(v1,0); visited:=[];
repeat
q.serve(v1,u);
if not (v1 in visited) then begin
visited:=visited+[v1];
zafarbiVrchol(v1,f[u]); // f je nejaké pole farieb
for i:=0 to high(g) do
if not (i in visited) and jeHrana(v1,i) then
q.append(i,u+1);
end;
until q.empty;
q.Free;
end;
|
Pozn.
- porovnajte tento algoritmus s nerekurzívnym
prehľadávaním do hĺbky
- používame rad, do ktorého vkladáme
aj vyberáme vždy dve celočíselné
hodnoty
- v súbore graf.zip
je jednoduchý ukážkový projekt
s algoritmom do šírky
zafarbí len hrany
grafu podľa toho, ako ďaleko sú do daného
vrcholu:
|
procedure TGraf.dosirky(v1:integer);
var
i,u:integer;
q:TQueue;
begin
q:=TQueue.Create; q.append(v1,0); visited:=[];
repeat
q.serve(v1,u);
if not (v1 in visited) then begin
visited:=visited+[v1];
for i:=0 to high(g) do
if not (i in visited) and jeHrana(v1,i) then begin
zafarbiHranu(v1,i,f[u]);
q.append(i,u+1);
end;
end;
until q.empty;
q.Free;
end;
|
- hrany, ktoré majú od daného
vrcholu rovnakú vzdialenosť (dĺžku cesty), sú
zafarbené rovnakou farbou
metóda počíta vzdialenosť dvoch vrcholov:
|
function TGraf.vzdialenost(v1,v2:integer):integer;
var
i,u:integer;
q:TQueue;
begin
Result:=-1; q:=TQueue.Create; q.append(v1,0); visited:=[];
try
repeat
q.serve(v1,u);
if not (v1 in visited) then begin
if v1=v2 then begin Result:=u; exit; end;
visited:=visited+[v1];
for i:=0 to high(g) do
if not (i in visited) and jeHrana(v1,i) then
q.append(i,u+1);
end;
until q.empty;
finally
q.Free;
end;
end;
|
- funkcia vráti -1, ak neexistuje cesta z v1
do v2
Rozšírime tento algoritmus nielen na zistenie
vzdialenosti dvoch vrcholov, ale aj na zapamätanie
tejto najkratšej cesty. Použijeme takúto ideu: vytvoríme
pomocné pole, v ktorom si budeme pre každý
vrchol pamätať jeho predchodcu v ceste, t.j. vždy,
keď si v rade zapamätáme vrchol (append),
ktorý plánujeme v budúcnosti preskúmať,
zapamätáme si s ním aj jeho predchodcu
(odkiaľ sme k nemu prišli). Keď vrchol z frontu vyberieme
(serve), tak vieme aj jeho predchodcu na ceste a môžeme
si ho zaevidovať v pomocnom poli. Na záver, keď
sme dorazili do cieľa, pomocou predchodcov uložených
v pomocnom poli sa vieme postupne vracať späť a
teda vieme skonštruovať výsledné dynamické
pole (hodnota -1 v poli znamená, že vrchol nemá
predchodcu - je prvý na ceste).
hľadanie najkratšej cesty v grafe:
|
type
postupnost = array of integer;
...
function TGraf.najkratsiaCesta(v1,v2:integer):postupnost;
var
i,v0:integer;
q:TQueue;
p:postupnost;
begin
Result:=nil; SetLength(p,Length(g)); visited:=[];
for i:=0 to high(p) do p[i]:=-1;
q:=TQueue.Create; q.append(v1,-1);
try
repeat
q.serve(v1,v0);
if not (v1 in visited) then begin
p[v1]:=v0;
if v1=v2 then begin
i:=0; repeat inc(i); v2:=p[v2] until v2<0; // zistíme dĺžku cesty
SetLength(Result,i); // pripravíme výsledné pole
repeat dec(i); Result[i]:=v1; v1:=p[v1] until v1<0; // prekopírujeme
exit; // try - finally zabezpečí, že sa vykoná q.Free;
end;
visited:=visited+[v1];
for i:=0 to high(g) do
if not (i in visited) and jeHrana(v1,i) then
q.append(i,v1);
end;
until q.empty;
finally
q.Free;
end;
end;
|
Pozn.
- high(najkratsiaCesta(v1,v2)) - vráti dĺžku
cesty
zhrňme použitie algoritmu do šírky:
- podobne ako do hĺbky, vieme zistiť vrcholy, ktoré
patria do jedného komponentu a teda aj zistiť
počet komponentov
- vieme zistiť (zafarbiť) vrcholy/hrany rovnako vzdialené
od daného vrcholu
- vieme zistiť vzdialenosť, resp. najkratšiu cestu
medzi dvoma vrcholmi v grafe
NDÚ:
- odlaďte tieto algoritmy v rôznych reprezentáciách
grafu
- zrealizujte prevody medzi rôznymi reprezentáciami
grafov, napr. graf reprezentovaný poľom množín
prekonvertujte do dynamického poľa dynamických
polí
- prevod binárneho/N-árneho/všeobecného
stromu do orientovaného/neorientovaného
grafu
- vygenerovať graf, napr. šachovnicu NxN:
- vrcholy sú susedné podľa pohybu kráľa/koňa/...
|