grafi in algoritmi na njih

40
Gra in algoritmi na njih Janez Brank

Upload: gordon

Post on 15-Jan-2016

69 views

Category:

Documents


0 download

DESCRIPTION

Grafi in algoritmi na njih. Janez Brank. Definicija. G = ( V , E ) je graf V je množica točk (vertices) ali vozlišč (nodes) E je množica povezav (edges) Povezave so lahko: neusmerjene ( u : v )  neusmerjen graf - PowerPoint PPT Presentation

TRANSCRIPT

Page 1: Grafi in algoritmi na njih

Grafi in algoritmi na njih

Janez Brank

Page 2: Grafi in algoritmi na njih

Definicija• G = (V, E) je graf

– V je množica točk (vertices) ali vozlišč (nodes)– E je množica povezav (edges)

• Povezave so lahko:– neusmerjene (u:v) neusmerjen graf– usmerjene (u, v) usmerjen graf (directed graph,

digraph)– u = začetno krajišče, v = končno krajišče– povezava (u, v) oz. (u:v) je incidenčna na u in v

• Povezave imajo včasih še kakšno dodatno lastnost(dolžina, kapaciteta, ipd.)

• Ponavadi točke predstavljajo neke stvari,povezave pa neko relacijo nad temi stvarmi– Mnoge zanimive probleme lahko rešimo tako,

da v problemu “opazimo” nek graf in prevedemo našproblem na nek problem na grafu

– Obstaja veliko algoritmov za razne probleme na grafih

Page 3: Grafi in algoritmi na njih

Še nekaj definicij• Če obstajajo povezave (u0, u1), (u1, u2), …, (uk–1, uk),

je (u0, u1, …, uk) sprehod (dolžine k).– Če so u0, u1, …, uk same različne točke, je to pot.

• Če je še uk = u0, je to obhod (če so u1, …, uk različne, je to cikel).– Graf, v katerem ni nobenega cikla, je acikličen.

Usmerjen acikličen graf = DAG (directed acyclic graph).• Nekateri namesto “sprehod” rečejo “pot”,

namesto “pot” rečejo “preprosta pot”,namesto “obhod” rečejo “cikel”,namesto “cikel” rečejo “preprost cikel”.

• Če obstaja pot od u0 do uk, je uk dosegljiva iz u0. u0 in uk sta (šibko) povezani.Če obstaja hkrati še pot od uk do u0, sta u0 in uk krepko povezani.

• Če obstaja povezava (u, v), je u predhodnik v-ja, v je naslednik u-ja, u in v sta soseda.

• Vhodna stopnja (in-degree) točke je število predhodnikov,izhodna stopnja (out-degree) je število naslednikov,stopnja je število vseh sosedov.

• Povezava (u, u) je zanka (loop). Ponavadi predpostavimo, da graf nima zank.

a

b

c

d

e f

g

h

j

i

k

Page 4: Grafi in algoritmi na njih

Še nekaj definicij

• Graf na n točkah (torej |V| = n) ima lahko največ toliko povezav:– n(n – 1), če je usmerjen in ne dovolimo zank– n2, če je usmerjen in dovolimo zanke– n(n – 1)/2, če je neusmerjen in ne dovolimo zank– n(n + 1)/2, če je neusmerjen in dovolimo zanke.

• Če je število povezav bliže O(n) kot O(n2), pravimo, da je graf redek (sparse), sicer je gost (dense).– Mnogi zanimivi grafi so precej redki:

• V državi je 1000 krajev, a iz vsakega kraja vodi le 5 cest.• V Sloveniji je 2 milijona ljudi, a vsakdo ima le nekaj 10

ali 100 znancev.

Page 5: Grafi in algoritmi na njih

Predstavitev grafa v računalniku

• Kako predstaviti graf v pomnilniku našega računalnika, da bo lahko naš program delal z njim?

• Dva glavna načina:– Matrika sosednosti– Seznami sosedov

• Pri vsakem je še več različic• Kaj izberemo, je zelo odvisno od tega,

– s kakšnimi grafi bomo delali in– kakšne operacije hočemo izvajati na teh grafih.

Page 6: Grafi in algoritmi na njih

Matrika sosednosti• Naj bo V = {u0, u1, …, un–1}.• Graf lahko predstavimo z matriko

(dvodimenzionalno tabelo) velikosti n × n.– a[i][j] = true, če v grafu obstaja povezava (ui , uj),

sicer a[i][j] = false.• Prednosti:

– Preprosta implementacija– V času O(1) preverimo, ali neka povezava obstaja

(ali pa jo dodamo/pobrišemo)• Slabosti:

– Vedno požre O(|V|2) pomnilnika, tudi če je graf redek– Za pregled vseh predhodnikov/naslednikov neke točke

porabimo vedno O(|V|) časa [zanka po enem stolpcu/vrstici tabele a]

– Za pregled vseh povezav porabimo vedno O(|V|2) časa

Page 7: Grafi in algoritmi na njih

Matrika sosednosti – različice

• Nekaj različic in potencialnih izboljšav:– Namesto tabele intov ali boolov lahko tabelo

zbijemo skupaj tako, da bo vsak par (i, j) zasedel le en bit.

• Poraba pomnilnika se zmanjša za nek konstanten faktor,poraba časa pa se poveča za nek konstanten faktor.

– Če je graf neusmerjen, je tabela simetrična.Torej, če smo dovolj obupani, lahko prihranimo 50% pomnilnika, vendar pridobimo bolj zapleteno indeksiranje:

– Če ima vsaka povezava neko dolžino ali kapaciteto ali kaj podobnega, lahko v tabeli a hranimo tudi to

Page 8: Grafi in algoritmi na njih

• Za vsako točko imamo seznam (linked list) naslednikov in seznam predhodnikov.– Oz. en sam seznam sosedov, če je graf neusmerjen.

• Prednosti:– Porabimo le O(|V|+|E|) pomnilnika.– Za pregled vseh sosedov neke točke u porabimo le

O(deg(u)) časa.– Za pregled vseh povezav porabimo le O(|V|+|E|) časa.

• Slabosti:– Preverjanje, ali je neka povezava (u, v) prisotna, lahko

porabi O(deg(u)) časa = O(|V|) v najslabšem primeru (pregled celega seznama sosedov za eno od krajišč).

– Enako tudi dodajanje/brisanje povezave (ker jo moramo najprej najti).

Seznami sosedovb c

d

abcde c

a e

aabcde c

a b

c

de

a e

aabcde c

i o u a e

aabcde c

i o u

Page 9: Grafi in algoritmi na njih

Seznami sosedov – različice• Različice in izboljšave:

– Vsaka povezava (u, v) se pravzaprav pojavi dvakrat: v je naslednik u-ja in u je predhodnik v-ja.

• Pogosto je koristno, če ta dva zapisa kažeta drug na drugega.• Lahko pa imamo celo samo en zapis,

ki je hkrati vključen v dva različna seznama.• To je vse lepo in prav, le pri implementaciji moramo biti

previdni, da se ne zmotimo pri prevezovanju kazalcev.

– Povezave lahko shranimo tudi v hash tabelo, v kateri je ključ par (u, v).

• Tako bomo lahko v O(1) preverili, ali povezava obstaja ali ne.[če to seveda potrebujemo]

• Zapisi hash tabele lahko kažejo na zapise iz seznamov sosedov (in obratno) ali pa celo kar tiste zapise zdaj povežemo še v verige, ki jih zahteva hash tabela.

– Če hočemo v O(1) zbrisati neko povezavo iz vseh verig, je koristno, če so verige dvojno povezane (doubly linked lists).

Page 10: Grafi in algoritmi na njih

Seznami

sosedov –

primer• Konkreten primer:typedef struct edge { int u, v; // krajišči int dolzina, kapaciteta, itd.; // če potrebujemo kaj od tega struct edge *prev_u_succ, *next_u_succ; // veriga u-jevih naslednikov struct edge *prev_v_pred, *next_v_pred; // veriga v-jevih predhodnikov struct edge *prev_in_hash, *next_in_hash; // veriga povezav, ki se v hash tabeli // preslikajo v isto hash kodo kot naša povezava (u, v)};int n, hash_modulo;struct edge **pred, **succ, **hash;

• Če vnaprej poznamo neko razumno zgornjo mejo za število povezav, lahko vse zapise edge hranimo v neki tabeli in namesto s kazalci nanje v bodoče delamo z indeksi v to tabelo.

a, ba b

c

de

a, c

e, c c, d

abcde

abcde

0123 hash_code(u, v) = (n· u + v) %

hash_modulohash_modulo = 4

hash

pred

succ

Page 11: Grafi in algoritmi na njih

Seznami sosedov – bolj kompaktno

• Življenje je preprostejše, če nam ni treba podpirati vseh možnih operacij (= mnoge ACM naloge):– Včasih potrebujemo le sezname naslednikov

ali pa le sezname predhodnikov.– Včasih nam ne bo treba za poljubne pare točk (u, v)

preverjati,ali povezava obstaja ali ne

– Včasih grafa sploh ne bomo spreminjali(ali pa bomo le brisali povezave) in ga bomo le na začetku prebrali iz neke datoteke.

– Takrat lahko vse sezname naslednikov zbijemo skupaj v eno samo tabelo (nasledniki v spodnjem primeru).

a b c d e0 2 2 3 3

a b c d e2 0 1 0 1

b c d c

izhodna stopnja

prvi naslednik

nasledniki0 1 2 3

a b

c

de

Page 12: Grafi in algoritmi na njih

Seznami sosedov – bolj kompaktno

• int n, m; // n = število točk, m = število povezavfscanf("%d %d", &n, &m); // preberemo število točk in povezavint *edgeFrom = new int[m], *edgeTo = new int[m]; // alokacija tabelint *inDeg = new int[n], *outDeg = new int[n];for (int u = 0; u < n; u++) inDeg[u] = outDeg[u] = 0;

• // Preberemo povezave, izračunamo stopnje vseh točk. for (int i = 0; i < m; i++) { int u, v; fscanf("%d %d", &u, &v); edgeFrom[i] = u; edgeTo[i] = v; inDeg[v]++; outDeg[u]++; }

• // Izračunamo, kje se v tabeli succ začnejo nasledniki posamezne točke u (namreč pri firstSucc[u]). int *firstSucc = new int[n], *succ = new int[m];firstSucc[0] = 0;for (int u = 1; u < n; u++) firstSucc[u] = firstSucc[u – 1] + outDeg[u – 1];

• // Vpišemo naslednike vsake točke na pravo mesto v tabelo succ. for (int u = 0; u < n; u++) outDeg[u] = 0;for (int i = 0; i < m; i++) { int u = edgeFrom[i], v = edgeTo[i]; succ[firstSucc[u] + outDeg[u]] = v; outDeg[u]++; }delete[] edgeFrom; delete[] edgeTo;

Page 13: Grafi in algoritmi na njih

Implicitna predstavitev grafa

• Včasih ima graf tako regularno zgradbo, da ga sploh ni treba predstaviti eksplicitno:– Če imamo neko pravilo, ki nam za poljubni dve točki

pove, ali obstaja med njima povezava ali ne– Če imamo nek postopek, ki nam za poljubno točko

našteje vse njene predhodnice/naslednice/sosede• To je pogost primer pri grafih, ki predstavljajo

“prostor stanj” nekega sistema:– Vsaka točka je eno od stanj sistema– Povezava od u do v pomeni, da se lahko stanje

sistema v enem koraku spremeni iz u v v (npr. zaradi nekega dogodka, dejanja, premika ipd.)

Page 14: Grafi in algoritmi na njih

Algoritmi na grafih• Nekaj problemov, ki pogosto pridejo prav:

– Topološko urejanje: iščemo vrstni red točk, da bo začetno krajišče vsake povezave v vrstnem redu pred končnim

– Iskanje v širino: sistematično pregledati vse, kar je dosegljivo iz neke začetne točke

– Iskanje v globino: kot iskanje v širino, a v drugačnem vrstnem redu– Iskanje najkrajših poti: vsaka povezava ima dolžino, iščemo

najkrajše poti med točkami– Iskanje (šibko) povezanih komponent: skupine točk, za katere je vsaka

točka skupine dosegljiva iz vsake druge (v neusmerjenem grafu)• Še drugi zanimivi problemi (za katere danes ne bo časa):

– Minimalno vpeto drevo: iščemo množico povezav, tako da bo vsota njihovih dolžin čim manjša in da bo vsaka točka incidenčna na vsaj eno od njih

– Barvanje grafa: vsaki točki hočemo pripisati neko barvo, tako da sosednji točki nimata nikoli iste barve in da porabimo čim manj barv

– Maksimalni pretok po grafu: vsaka povezava ima kapaciteto, hočemo čim večji pretok od izvora do ponora, tovor se ne sme nikjer izgubljati ali kopičiti

– Iskanje krepko povezanih komponent: skupine točk, za katere je vsaka točka skupine dosegljiva iz vsake druge (v usmerjenem grafu)

– Tranzitivna redukcija, minimalni ekvivalentni podgraf:hočemo obdržati čim manj povezav, ne da bi bila prizadeta dosegljivost

– Izomorfizem grafov: hočemo preslikati en graf v drugega, pri tem pa spoštovati sosednost: u je soseda v f(u) je soseda f(v)

Page 15: Grafi in algoritmi na njih

Topološko urejanje

• Dan je usmerjen graf• Iščemo “topološki vrstni red”:to je tak vrstni red

točk, v katerem za vsako povezavo (u, v) E velja, da je u v našem vrstnem redu pred v– Če točke narišemo v tem vrstnem redu od leve proti desni,

kažejo vse povezave v desno– Če obstaja cikel (u0, u1, …, uk–1, u0),

bi moral biti u0 pred u1, ta pred u2, …, ta pred uk–1, ta pred u0

• Torej bi moral biti u0 pred samim sabo• Torej grafa s ciklom ne moremo topološko urediti

– S topološkim urejanjem lahko torej preverjamo, če ima graf kak cikel

– Lahko tudi iščemo najkrajše poti po njem (tudi če imajo povezave različno dolžino)

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

Page 16: Grafi in algoritmi na njih

Topološko urejanje• Ideja:

– Prva točka v topološkem vrstnem redu mora imeti vhodno stopnjo 0.– Vzemimo torej poljubno tako točko, jo postavimo na začetek topološkega

vrstnega reda;odslej nas njene povezave ne bodo več motile, zato v mislih to točko in njene povezave pobrišimo iz grafa.

– Graf ima zdaj eno točko manj, ponovimo isti postopek, itd.• V praksi:

– Povezav ni treba zares brisati – dovolj je, če si zapomnimo, kolikšno vhodno stopnjo bi imela neka točka, če bi povezave res brisali.

– V neki vrsti Q bomo hranili točke, za katere že vemo, da imajo vhodno stopnjo 0, nismo pa jih še (v mislih) pobrisali iz grafa.

• Postopek:

for each v V: inDeg[v] := 0; for each (u, v) V: inDeg[v] := inDeg[v] + 1;Q := prazna vrsta;for each v V: if inDeg[v] = 0 then Enqueue(Q, v); while Q ni prazna: u := Dequeue(Q); print u; for vsako u-jevo naslednico v: inDeg[v] := inDeg[v] – 1; if inDeg[v] = 0 then Enqueue(Q, v);

• Za vrsto Q lahko uporabimo kar tabelo ter dva indeksa head, tail– Na koncu iz te tabele kar odčitamo enega od možnih topoloških vrstnih redov– Če je v grafu kak cikel, se bo postopek končal, še preden bo dodal v vrsto vse

točke

Page 17: Grafi in algoritmi na njih

Primer topološkega urejanja

Enqueue a, e

a

b

c

d

e

f

g

h

a0 1 2 3 4 5 6 7

head = 0, tail = 1

eDequeue aEnqueue b

ahead = 1, tail = 2

e bDequeue eEnqueue f, ga head = 2, tail

= 4

e b f gDequeue bEnqueue c, ha head = 3, tail

= 6

e b f g c hDequeue fEnqueue d

a head = 4, tail = 7

e b f g c h d

Dequeue g

a head = 5, tail = 7

e b f g c h d

a head = 6, tail = 7

e b f g c h d

Dequeue c

a head = tail = 7

e b f g c h d

Dequeue h

a head = 8, tail = 7

e b f g c h d

Dequeue d

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

a

b

c

d

e

f

g

h

Page 18: Grafi in algoritmi na njih

Primer uporabe topološkega urejanja

• acm.timus.ru, #1337– Imamo n uradnikov.

Uradnik i dela le en dan v tednu – na dan ai. Preden obiščemo uradnika i, moramo obiskati vse uradnike iz množice Pi.Teden ima L dni. Danes je dan k. Radi bi čim prej obiskali vse uradnike iz množice M. Koliko dni bo trajalo?

– Rešitev: definirajmo usmerjen graf: V = {1, …, n},E = {(u, v) : u Pv}

– Če zdaj pregledujemo uradnike v topološkem vrstnem redu, lahko za vsakega uradnika u brez težav določimo najzgodnejši možni datum obiska, ker takrat že vemo najzgodnejši datum obiska za vse, ki jih moramo obiskati pred njim (Pu)

– Na koncu vrnemo najkasnejši dan po vseh uradnikih iz M

Page 19: Grafi in algoritmi na njih

Iskanje v širino (breadth-first search, BFS)

• Je način, kako sistematično pregledati vse točke, ki so dosegljive iz neke začetne točke s.

• Postopek:

Q := prazna vrsta;for each v V: seen[v] := false;Q.Enqueue(s); seen[s] := true;while Q ni prazna: u := Q.Dequeue(s); print u; for vsako u-jevo naslednico v: if not seen[v]: Q.Enqueue(v); seen[v] := true;

• Najprej izpiše s, nato vse s-jeve naslednice, nato vse točke, ki so dosegljive iz s v dveh korakih, ne pa v enem,nato vse točke, ki so dosegljive iz s v treh korakih, ne pa v dveh ali manj,itd.

• Vrsto lahko implementiramo z linked listo, še lažje pa je s tabelo(dolga mora biti največ |V| elementov, za glavo in rep vrste vodimo dva indeksa).

Page 20: Grafi in algoritmi na njih

Primer iskanja v širino

ab

c

d

e

f

g

h

i

j

k

l

m n

ehead = 1, tail = 2

b c

ehead = 2, tail = 2

b c

e head = 3, tail = 4

b c g d

e head = 4, tail = 8

b c g h k mld

e head = 5, tail = 9

b c g h k mld f

e head = 6, tail = 10

b c g h k mld f i

e head = 7, tail = 11

b c g h k mld f i n

e0 1 2 3 4 5 6 7 8 9 10 11

head = tail = 0

12 13

e head = 8, tail = 11

b c g h k mld f i n

e head = 9, tail = 11

b c g h k mld f i n

e head = 10, tail = 11

b c g h k mld f i n

e head = tail = 11

b c g h k mld f i n

e head = 12, tail = 11

b c g h k mld f i n

Enqueue e

Dequeue eEnqueue b, c

Dequeue b

Dequeue cEnqueue g, d

Dequeue gEnqueue h, k, l, mDequeue dEnqueue f

Dequeue hEnqueue i

Dequeue kEnqueue n

Dequeue l

Dequeue m

Dequeue f

Dequeue i

Dequeue n

0 1 2 3 4 oddaljenost od s

• Lahko bi v neki tabeli tudi hranili dolžino najkrajše poti od s do vsake točke

• Lahko bi si tudi zapomnili, od kod smo v neko točko prišli– Na koncu bi iz tega

rekonstruirali potek najkrajših poti od s do vseh ostalih točk

– Npr. v i smo prišli iz h, v h iz g, v g iz c, v c iz e

• Časovna zahtevnost: O(|V|+|E|)

Page 21: Grafi in algoritmi na njih

Primer uporabe iskanja v širino

• acm.uva.es, #310:– Dani so nizi s, t, u, sestavljeni le iz črk a in b.– Naj bo f(w) niz, ki ga dobimo tako, da v w-ju vsak a

zamenjamo z nizom t, vsak b pa z nizom u.– Vprašanje: ali je dani niz x podniz kakšnega od nizov

s, f(s), f(f(s)), f(f(f(s))), …? x je dolg največ 15 znakov.

– Rešitev: definirajmo usmerjen graf:V = {vsi nizi iz črk a in b, dolgi največ toliko kot x}E = {(u, v) : v je podniz niza f(u)}

– Naloga se prevede na vprašanje, ali je x v tem grafu dosegljiv iz s, kar lahko preverimo z iskanjem v širino.

Page 22: Grafi in algoritmi na njih

Še en primer uporabe iskanja v širino

• acm.uva.es, #321– Imamo hišo z n 10 sobami. – Znani so pari sob, za katere sta sobi v paru neposredno povezane

z vrati. – V nekaterih sobah so tudi stikala,

i-to stikalo je v sobi si in prižiga/ugaša luč v sobi ti.– 1 korak = da stopiš iz ene sobe v drugo (skozi vrata)

ali pa prižgeš/ugasneš luč z enim od stikal v trenutni sobi– V sobo lahko stopiš le, če v njej gori luč– Znano je, v kateri sobi si na začetku in kakšno je stanje vseh luči.

Znano je tudi, v katero sobo bi rad prišel in kakšno naj bo takrat stanje vseh luči.Dosezi to v čim manj korakih.

– Rešitev: V = {1, …, n}{0,1}n – vsaka točka grafa predstavlja eno možno stanje sistema (naš položaj in stanje vseh stikal)

– Povezava od enega stanja do drugega obstaja, če lahko mi pridemo iz enega v drugo stanje z enim korakom.

– Ostane le še iskanje najkrajše poti, npr. z iskanjem v širino.

Page 23: Grafi in algoritmi na njih

Povezane komponente• V neusmerjenem grafu:

– Točki u in v sta povezani ntk. obstaja med njima neka pot.• V usmerjenem grafu:

– Točki u in v sta krepko povezani ntk. obstaja neka pot od u do v in še neka pot od v do u (ob upoštevanju smeri povezav).

– Točki u in v sta šibko povezani ntk. obstaja neka pot med u in v, če zanemarimo smer povezav (torej če se delamo, da je graf neusmerjen).

• (Krepko, šibko) povezana komponenta:– Je množica točk, ki so vse med sabo paroma (krepko, šibko)

povezane – in v katero ne moremo dodati nobene nove točke, brez da bi

prejšnji pogoj prenehal veljati.• Ogledali si bomo algoritem za povezane komponente v

neusmerjenem grafu– Lahko ga uporabimo tudi za šibko povezane komponente

v usmerjenem grafu– Če hočemo učinkovit algoritem za krepko povezane komponente,

je stvar malo bolj zapletena

Page 24: Grafi in algoritmi na njih

Povezane komponente• Postopek je preprost:

– Začnemo pri poljubni točki in z iskanjem v širino (ali pa v globino, saj je vseeno) obiščemo vse, kar je dosegljivo iz nje.

– To je ena povezana komponenta.– Zdaj začnemo pri poljubni točki, ki ni iz te komponente, in na enak način

dobimo drugo povezano komponento, itd.• Postopek:

ŠtKomponent := 0;for each v V: komponenta[v] := –1;for each s V: if komponenta[s] = –1: Q := prazna vrsta; Enqueue(Q, s); komponenta[s] := ŠtKomponent; while Q ni prazna: u := Dequeue(Q); for vsako u-jevo naslednico v: if komponenta[v] = –1: Enqueue(Q, v); komponenta[v] := ŠtKomponent; ŠtKomponent := ŠtKomponent + 1;

• Časovna zahtevnost: O(|V| + |E|)

Page 25: Grafi in algoritmi na njih

Primer naloge s povezanimi komponentami

• acm.uva.es, #10583– Imamo n ljudi.

Za nekatere pare ljudi vemo, da sta človeka iste vere.

– Ne vemo pa točno, katere vere je kdo – še tega ne vemo, koliko različnih ver sploh je. Ugotovi največje možno število različnih ver.

– Rešitev: definirajmo neusmerjen graf V = {1, …, n}, E = {(u:v) : za u in v vemo, da sta iste vere}.

– Vsi ljudje iz neke povezane komponente tega grafa morajo biti iste vere. Različnih ver je torej največ toliko, kolikor je povezanih komponent v tem grafu.

Page 26: Grafi in algoritmi na njih

Iskanje v globino (depth-first search, DFS)

• Podobno kot iskanje v širino, le da obiskuje točke v drugačnem vrstnem redu.– Če ima u dva naslednika, v in w, bomo šli najprej v v in nato

obiskali vse točke, dosegljive iz njega, preden se bomo lotili w-ja.• Postopek:

inicializacija: for each v V: barva[v] := bela;

algoritem DFS(u): barva[u] := siva; print u; for vsako u-jevo naslednico v: if barva[v] = bela: DFS(v); barva[u] := črna;

glavni klic: DFS(s);

• Namesto rekurzije imamo lahko tudiiteracijo, točke pa odlagamo na sklad(pri vsaki si tudi zapomnimo, do katerenaslednice smo pri njej že prišli)

• Vedno velja (za vsako točko u):– če je u bela, je še nismo

izpisali;– če je u siva, smo jo že izpisali;– če je u črna, smo izpisali že njo

in vse, kar je dosegljivo iz nje.

Page 27: Grafi in algoritmi na njih

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

ab

c

d

e

f

g

h

i

j

k

l

m n

Primer iskanja v globino

ab

c

d

e

f

g

h

i

j

k

l

m n

a

bc

e

g

h

ij

m

l

k

n

d

f

• e, b, c, g, h, i, m, l, k, n, d, f• Dobili smo tudi “DFS drevo”• Za vsako povezavo (u, v) E velja eno od

naslednjega:– ena od u in v je prednica druge v DFS

drevesu– [le pri usmerjenih grafih]

v (in celo njeno poddrevo) je bila obiskana prej kot u

• Z drugimi besedami: če povezava (u, v) kaže “naprej” po DFS vrstnem redu, je v potomka u (ne more biti iz nekega drugega poddrevesa)

Page 28: Grafi in algoritmi na njih

Iskanje v globino• Časovna zahtevnost je spet O(|V|+|E|),

enako kot pri iskanju v širino• Koristno za odkrivanje ciklov:

– Če pri pregledovanju u-jevih naslednic opazimo neko v, ki je že siva, pomeni, da je v grafu cikel (ki ga lahko kar odčitamo s sklada)

– Če v grafu obstaja kak cikel, dosegljiv iz s, se nam bo gornji primer med preiskovanjem gotovo zgodil

• Iskanje v globino se uporablja tudi pri:– odkrivanju krepko povezanih komponent– topološkem urejanju

[če uredimo točke po tem, kdaj je DFS končal s tisto točko in njenim poddrevesom, in na koncu ta vrstni red obrnemo]

– odkrivanju artikulacijskih točk (točk, pri katerih nam graf razpade na več nepovezanih delov, če tisto točko pobrišemo)

Page 29: Grafi in algoritmi na njih

Artikulacijske točke• Dan je neusmerjen, povezan graf

– Če v grafu pobrišemo neko točko u, se lahko zgodi, da razpade na več povezanih komponent

– Takšne u se imenujejo “artikulacijske točke”• Preprost algoritem za odkrivanje artikulacijskih točk:

– Za vsako u V: delajmo se, da smo u pobrisali iz grafa, in preverimo, če je še vedno povezan

– O(|V|(|V| + |E|))• Učinkovitejši algoritem: izvedimo DFS, oglejmo si DFS drevo

– Koren je artikulacijska točka ntk. ima več kot enega otroka– Za poljubno drugo točko v:

• Poglejmo vsakega od njenih otrok, w, v DFS drevesu• Če ni nobene povezave med kakšno točko iz w-jevega poddrevesa

in kakšno točko u v zunaj w-jevega poddrevesa, potem je v artikulacijska točka

• Če pa taka povezava obstaja, je u ena od prednic v-ja• Zato je mogoče takšne povezave učinkovito odkrivati,

če točke oštevilčimo v takem vrstnem redu,v kakšnem jih je DFS obiskal – prednike potem prepoznamo po manjših številkah

– To se da implementirati v O(|V|+|E|)

a

b

c d

e

f

g

i

hn

j

k

l

m

a

b

c

d

e

f

g

i

h

n

j

k

l

m

Page 30: Grafi in algoritmi na njih

Iskanje najkrajših poti• Recimo, da ima vsaka povezava (u, v) E neko

dolžino d(u, v)– Če (u, v) E, si mislimo d(u, v) = – Dolžina poti (u0, u1, …, uk) je i=1..k d(ui–1, ui)

• Problem najkrajših poti:– Za dano točko s nas zanimajo najkrajše poti od s do vseh

ostalih točk [single-source shortest paths]– Ali pa: za vsako točko s nas zanimajo najkrajše poti od s do

vseh ostalih točk [all-pairs shortest paths]

• Nekaj tega smo že videli:– Če so vse povezave enako dolge (d(u, v) = 1 za vsako (u, v)

E), lahko uporabimo iskanje v širino

– Če je graf acikličen, lahko uporabimo topološko urejanje– Videli pa bomo še nekaj splošnejših postopkov

Page 31: Grafi in algoritmi na njih

Iskanje najkrajših poti

• Če je = (u0, u1, …, uk–1, uk) najkrajša pot od u0 do uk, je (u0, u1, …, uk–1) najkrajša pot od u0 do uk–1– Res: če bi obstajala od u0 do uk–1 neka krajša pot ,

bi jo lahko podaljšali s korakom (uk–1, uk) in tako dobili neko pot od u0 do uk, ki bi bila krajša od (protislovje).

• Torej je vsaka najkrajša pot podaljšek neke druge najkrajše poti.– Najkrajše poti od s do vseh drugih točk torej

tvorijo “drevo najkrajših poti”.

– Vse, kar moramo storiti, je, da za vsako u najdemo njeno predhodnico na najkrajši poti od s do u.

u0

uk–

1

uk

Page 32: Grafi in algoritmi na njih

Iskanje najkrajših poti s topološkim urejanjem

• Recimo, da oštevilčimo točke v topološkem vrstnem redu: u0, u1, …, un

• Postopek:

for i := 0 to n – 1: // zdaj za u0, …, ui–1 že poznamo najkrajše poti (v tabeli d) p[ui] := nil; if ui = s then d[ui] := 0 else d[ui] := ; for vsako ui-jevo predhodnico uj: // gotovo je j < i (zaradi topološkega vrstnega reda) if d[uj] + d(uj, ui) < d[ui]: d[ui] := d[uj] + d(uj, ui); p[ui] := uj;

• Invarianta je, da na začetku i-te iteracije glavne zanke za vsak j < i že poznamo dolžino najkrajše poti od s do uj; ta dolžina je d[uj], predhodnica točke uj na tej poti pa je točka p[uj]

• V praksi ni treba najprej izvesti topološkega urejanja in nato zgornje zanke,ampak lahko počnemo oboje hkrati (med topološkim urejanjem iščemo še najkrajše poti)

Page 33: Grafi in algoritmi na njih

Dijkstrov algoritem• Graf si mislimo razdeljen na tri dele, črnega, sivega in belega.• Invarianta:

– Če je u črna, je d[u] dolžina najkrajše poti od s do u, p[u] pa je predhodnica u-ja na tej poti.Poleg tega so črne tudi vse točke v, za katere je najkrajša pot od s do v krajša kot d[u].

– Sive točke so vse tiste, ki niso črne, pač pa se da do njih priti v enem koraku iz kakšne črne točke.Za vsako sivo u je d[u] dolžina najkrajše take poti od s do u, ki gre ves čas po črnih točkah, le zadnji korak stopi iz črne v sivo; p[u] pa je predhodnica u-ja na tej poti.

– Ostale točke so bele.• Postopek:

for each v V: barva[v] := bela; d[v] := ; p[v] := nil;barva[s] := siva; d[v] := 0;while obstaja še kaj sivih točk: u := med vsemi sivimi točkami tista z najmanjšo d[u]; barva[u] := črna; (*) for vsako u-jevo naslednico v: če je v siva ali bela: če je d[u] + d(u, v) < d[v]: d[v] := d[u] + d(u, v); p[v] := u; barva[v] := siva; (*)

• Za večjo učinkovitost je dobro hraniti vse sive točke v prioritetni vrsti (npr. v kopici – heap).

– Če tega nimamo, bo moral biti dober tudi navaden seznam ali tabela• Pogoj za to, da se invarianta ohrani, ko v (*) spreminjamo barve točk,

je, da so dolžine vseh povezav ≥ 0.Drugače lahko vrne Dijkstrov algoritem napačne rezultate.

• Časovna zahtevnost: O((|V|+|E|) log |V|) s kopico, O(|V|2+|E|) = O(|V|2) brez nje

s

u

v

Page 34: Grafi in algoritmi na njih

Posplošitev problema najkrajših poti

• Za pot = (u0, u1, …, uk) definirajmo neko ceno J(). • Za dano točko s iščemo najcenejše poti od s do

vseh ostalih točk.• Če velja naslednje:

– J(u0, …, uk–1, uk) = f (J(u0, …, uk–1), uk–1, uk) za neko funkcijo f (t, u, v). ki jo poznamo

– J(u0, …, uk–1, uk) ≥ J(u0, …, uk–1)– če je t < t', je f (t, u, v) f (t', u, v)

[To nam zagotavlja, da če je najcenejša pot od s do v in je v-jeva predhodnica na neka točka u, potem je preostanek te poti najcenejša pot od s do u.]

• …lahko iščemo najboljše poti z Dijkstro.– Za običajne najkrajše poti je f (t, u, v) = t + d(u, v).

Page 35: Grafi in algoritmi na njih

Primer z iskanjem najkrajših poti• acm.uva.es, #10621

– Človeka A in B hodita po karirasti mreži 30 30, v vsakem koraku se vsakdo premakne iz trenutnega polja v eno od štirih sosednjih polj.

– Za vsakega je podan začetni in zahtevani končni položaj na mreži (zA, zB, kA, kB).

– Predlagaj jima tako pot, da:• Bosta obe poti enako dolgi (enako število korakov)• Če je d(t) razdalja med človekoma po t korakih, naj bo minimum d(t) po

vseh t čim večji.

– Rešitev: V = {(xA, yA, xB, yB) : vse koordinate med 1 in 30}E = {vsaka točka ima 44 sosede, ki ustrezajo možnim premikom A-ja in B-ja}

– Če je u = (xA, yA, xB, yB), definirajmo j(u) := ((xA – xB)2 + (yA – yB)2)– Definirajmo J(u0, …, uk–1, uk) = –min{j(u1), j(u2), …, j(uk)}.

Naš problem se prevede na iskanje najcenejše poti od dane začetne do dane ciljne točke.

• Hitro se vidi, da J ustreza pogojem s prejšnje folije• Računamo jo s pomočjo

J(u0, …, uk–1, uk) = –min{j(u1), j(u2), …, j(uk)} = –min{min{j(u1), j(u2), …, j(uk–1)}, j(uk)} = max{–min{j(u1), j(u2), …, j(uk–1)}, –j(uk)} = max{J(u0, …, uk–1), –j(uk)},

torej uporabimo f (t, u, v) = max{t, –j(v)}

Page 36: Grafi in algoritmi na njih

Bellman-Fordov algoritem• Ideja: glejmo najkrajše poti, sestavljene iz največ k korakov

– Pri k = 0 je stvar trivialna – možna je le pot od s do s– Drugače pa je najkrajša pot od s do v s k koraki bodisi:

• Dolga manj kot k korakov• Dolga natanko k korakov in je zato podaljšek najkrajše poti od s do neke u s k – 1

koraki• Postopek:

for each v V: d[v] := ; p[v] := nil;d[s] := 0;for k := 1 to |V|: // Invarianta: d[v] je dolžina najkrajše poti od s do v z največ k – 1 koraki. // Izračunajmo najkrajše poti od s do vseh točk z največ k koraki. for each v V: d'[v] := d[v]; p'[v] := p[v]; for each (u, v) E: if d[u] + d(u, v) < d'[v]: d'[v] := d[u] + d(u, v); p'[v] := u; for each v V: d[v] := d'[v]; p[v] := p'[v];

• Časovna zahtevnost: O(|V||E|)

Page 37: Grafi in algoritmi na njih

Bellman-Fordov algoritem• Če na koncu neke iteracije glavne zanke opazimo,

da sta tabeli d in d' povsem enaki,– Pomeni, da s k koraki ni mogoče dobiti nobene krajše poti kot

s k – 1 koraki.– Torej se tudi v nadaljnjih iteracijah ne bo nič spremenilo– Lahko se kar takoj ustavimo.

• Če se nam to ne zgodi niti v zadnji iteraciji (ko je k = |V|):– Pomeni, da je neka pot od s do neke v z |V| koraki krajša

kot katerakoli pot od s do te v z manj kot |V| koraki– Toda pot z |V| koraki gotovo vsebuje nek cikel– Če ta cikel pobrišemo, ima pot manj kot |V| korakov, torej je

daljša– Nekaj smo pobrisali, pot pa je daljša?!– Da, ker imamo negativni cikel.

Večkrat ko gremo po njem, krajša bo pot. Najkrajša pot torej sploh ne obstaja.

– Bellman-Ford nam torej omogoča tudi opaziti, če je iz s dosegljiv kakšen negativni cikel.

s

v

8

–3 5

1 –4

20

Page 38: Grafi in algoritmi na njih

Najkrajše poti med vsemi pari točk

• Lahko poženemo kakšnega od dosedanjih algoritmov |V|-krat, vsakič z drugo s

• Lahko prilagodimo Bellman-Forda:– Naj bo dk[u, v] dolžina najkrajše poti od u do v z največ k koraki,

pk[u, v] pa v-jeva predhodnica na tej poti.– Za d1[u, v] vemo, da je d1[u, v] = 0 pri u = v,

d1[u, v] = d(u, v) sicer.p1[u, v] = u.

– Za večje k ga lahko računamo takole:

algoritem Tralala(r, t): for each u V, for each v V: dr+t[u, v] := ; pr+t[u, v] := nil; for each w V: if dr[u, w] + dt[w, v] < ds+t[u, v]: dr+t[u, v] := dr[u, w] + dt[w, v]; pr+t[u, v] := pt[w, v];

– Z algoritmom Tralala lahko iz dr in dt v času O(|V|3) dobimo dr+t.Na začetku poznamo d1, iz tega lahko izračunamo d2, d4, d8, d16, d32, …Nas pa zanima d|V|, torej moramo le še pogledati, katere potence števila 2 moramo sešteti, da pride vsota |V|.

– Algoritem Tralala bomo poklicali največ O(log |V|)-krat. Skupaj torej O(|V|3 log |V|).

Page 39: Grafi in algoritmi na njih

Floyd-Warshallov algoritem• Za najkrajše poti med vsemi pari točk• Oštevilčimo točke: V = {u0, u1, …, un–1}• Naj bo dk[i, j] dolžina najkrajše poti od ui do uj,

ki med njima ne obiskuje točk uk, uk+1, …– Trivialni podproblemi: d0[i, j] = d(ui, uj)– Za k > 0 pa preverimo dve možnosti:

• Taka pot mogoče obišče uk–1 (prej in potem pa je to pot, ki ne obiskuje uk–1, uk, …)

• Ali pa je ne obišče• Postopek:

for i := 0 to n – 1, for j := 0 to n – 1: d0[i, j] := d(ui, uj);for k := 0 to n – 1: for i := 0 to n – 1, for j := 0 to n – 1: dk[i, j] := min{dk–1[i, j], dk–1[i, k] + dk–1[k, j]}; // zdaj lahko dk–1 že zavržemo, ker ga ne bomo več potrebovali

• Če se kdaj zgodi, da je dk[i, i] < 0, imamo negativni cikel.• Časovna zahtevnost je le O(|V|3).

Page 40: Grafi in algoritmi na njih

Pregled časovne zahtevnostialgoritmov za najkrajše poti

• Najkrajše poti Najkrajše poti Algoritem od ene do ostalih med vsemi pari točk

iskanje v širino O(V+E) O(V2 +VE)topološko urejanje O(V+E) O(V2 +VE) Dijkstra brez kopice O(V2+E) O(V3 +VE)Dijkstra s kopico O(E log V) O(VE log V)Bellman-Ford O(VE) O(V2E)Bellman-Ford za APSP O(V3 log V)Floyd-Warshall O(V3)

[Spomnimo se: E = O(V) za redke grafe, E = O(V2) za goste.]