This is an old revision of the document!
În urma parcurgerii acestui laborator, studentul va fi capabil să:
Grafurile sunt utile pentru a modela diverse probleme şi se regăsesc implementaţi în multiple aplicaţii practice:
Se numeşte componentă conexă a unui graf neorientat G = (V, E)
un subgraf G1 = (V1, E1)
în care pentru orice pereche de noduri (A, B)
din V1 există un lanţ de la A
la B
, implicit şi de la B
la A
.
Observaţie Nu există un alt subgraf al lui G
, G2 = (V2, E2)
care să îndeplinească această condiţie şi care să îl conţină pe G1
. În acest caz, G2
va fi componenta conexă, iar G1
nu.
BFS
, cât şi una DFS
, pornind dintr-un nod A, va determina componenta conexa din care face parte A
.G = (V, E)
, se vor parcurge nodurile din V
.BFS
sau DFS
.// inițializări pentru fiecare nod u din V { stare[u] = nevizitat } componente_conexe = 0 // funcţie de vizitare a nodului vizitare(nod) { stare[nod] = vizitat printeaza nod } // parcurgerea în adâncime DFS(nod) { stiva s viziteaza nod s.introdu(nod) cât timp stiva s nu este goală { nodTop = nodul din vârful stivei vecin = află primul vecin nevizitat al lui nodTop. dacă vecin există { viziteaza v s.introdu(v) } altfel { s.scoate(nodTop) } } } // parcurgerea nodurilor din V pentru fiecare nod u din V { dacă stare[u] == nevizitat { componente_componente = componente_conexe + 1 DFS(u) } }
Dacă toate muchiile au același cost, putem afla distanța minimă între două noduri A
și B
efectuând o parcurgere BFS
din nodul A
și oprindu-ne atunci când nodul B
a fost descoperit. Reamintindu-ne că nivelul unui nod este analog distanței, în muchii, față de sursă, și că BFS
descoperă un nod de pe nivelul N
numai după ce toate nodurile de pe nivele inferioare au fost descoperite, este ușor de văzut că nivelul nodului B
în parcurgere corespunde distanței minime între A
și B
.
Pentru a reține distanța și drumul exact de la A
la B
, se vor reține pentru fiecare nod d[x]
(distanța de la sursă
la x
) și p[x]
(părintele lui x
în drumul de la sursă spre x
). În momentul descoperirii unui nod y
al cărui părinte este x
, se vor face următoarele atribuiri:
d[y] = d[x] + 1 p[y] = x
sursa având d[A] = 0
și p[A] = NULL
.
Observații:
// inițializări pentru fiecare nod u din V { stare[u] = nevizitat d[u] = infinit p[u] = null } // distanța între sursă și destinație distanța(sursă, destinație) { stare[sursă] = vizitat d[sursă] = 0 enqueue(Q,sursă) // punem nodul sursă în coada Q // BFS cât timp coada Q nu este vidă { v = dequeue(Q) // extragem nodul v din coadă pentru fiecare u dintre vecinii lui v dacă stare[u] == nevizitat { stare[u] = vizitat p[u] = v d[u] = d[v] + 1 enqueue(Q,u) // adăugăm nodul u în coadă } } return d[destinație] // dacă este infinit, nu există drum }
Se dă un graf orientat aciclic. Orientarea muchiilor corespunde unei relații de ordine de la nodul sursă către cel destinație. O sortare topologică a unui astfel de graf este o ordonare liniară a vârfurilor sale astfel încât, dacă (u,v)
este una dintre muchiile grafului, u
trebuie să apară înaintea lui v
în înșiruire. Dacă graful ar fi ciclic, nu ar putea exista o astfel de înșiruire (nu se poate stabili o ordine între nodurile care alcătuiesc un ciclu).
Sortarea topologică poate fi văzută și ca plasarea nodurilor de-a lungul unei linii orizontale astfel încât toate muchiile să fie direcționate de la stânga la dreapta (să nu existe nici o muchie înapoi, spre părinte).
// inițializări pentru fiecare nod u din V { stare[u] = nevizitat p[u] = NULL tDesc[u] = 0 tFin[u] = 0 } contor_timp = 0 // funcţie de vizitare a nodului vizitare(nod) { contor_timp = contor_timp + 1 tDesc[nod] = contor_timp stare[nod] = vizitat printeaza nod } // parcurgere în adâncime DFS(nod) { stiva s viziteaza nod s.introdu(nod) cât timp stiva s nu este goală { nodTop = nodul din vârful stivei vecin = află primul vecin nevizitat al lui nodTop. dacă vecin există { p[v] = nodTop viziteaza v s.introdu(v) } altfel { contor_timp = contor_timp + 1 tFin[nodTop] = contor_timp s.scoate(nodTop) } } } // parcurgere noduri și calculare tDesc și tFin pentru fiecare nod pentru fiecare nod u din V { dacă u nu a fost vizitat { DFS(u) } } // sortare topologica sortează nodurile din V descrescător în funcție de tFin[nod]
Profesorul Bumstead își sortează topologic hainele înainte de a se îmbrăca.
(u, v
) înseamna că obiectul de îmbrăcăminte u
trebuie îmbrăcat înaintea obiectului de îmbrăcaminte v
. Timpii de descoperire (tDesc)
și de finalizare (tFin)
obținuți în urma parcurgerii DFS sunt notați lângă noduri.tFin
. Observați că toate muchiile sunt orientate de la stânga la dreapta. Acum profesorul Bumstead se poate îmbrăca liniștit. Așa cum se observă din imagine, sortarea topologică constă în sortarea nodurilor descrescător după timpii de finalizare. Demonstrația acestei afirmații se face simplu, arătând că nodul care se termină mai târziu trebuie să fie efectuat înaintea celorlalte noduri finalizate.
Se numește graf bipartit un graf G = (V, E)
în care mulțimea nodurilor poate fi împărțită în două mulțimi disjuncte A
și B
astfel încât V = A U B
şi E este inclus în A x B
(orice muchie leagă un nod din A
cu un nod din B
).
BFS
și atribuirea de etichete nodurilor conform cu paritatea nivelului acestora în parcurgere (A
pentru nodurile de pe nivel par, B
pentru nodurile de pe nivel impar). BFS
fără a apărea această situație), graful este bipartit și nodurile sunt etichetate cu mulțimea din care fac parte.sursa = un nod ales aleator din V nivel[sursa] = par enqueue(Q, sursa) // punem nodul sursă în coada Q // BFS cât timp coada Q nu este vidă { v = dequeue(Q) // extragem nodul v din coadă pentru fiecare u dintre vecinii lui v dacă nivel[u] nedefinit { nivel[u] = (nivel[v] == par) ? impar : par enqueue(Q, u) // adăugăm nodul u în coadă } altfel dacă nivel[u] == nivel[v] { // două noduri consecutive au acelaşi nivel // graful nu este bipartit return false } } // s-a terminat parcurgerea BFS fără să apară două noduri consecutive pe acelaşi nivel // graful este bipartit return true
Un lanţ hamiltonian într-un graf orientat sau neorientat G = (V, E)
, este o cale ce trece prin fiecare nod din V
o singură dată. Dacă nodul de început şi cel de sfârşit coincid (este vizitat de două ori) vom spune că lanţul formează un ciclu hamiltonian.
Un graf ce conţine un ciclu hamiltonian se numeşte graf hamiltonian.
În cadrul acestui laborator, vom folosi metoda backtracking pentru găsirea unui ciclu hamiltonian. Pentru contruirea soluţiei, se menţine o listă în care sunt adăugate nodurile parcurse:
n
(numărul de noduri din graf), se verifică dacă primul şi ultimul nod din listă sunt adiacente. În caz contrar, s-a găsit un lanţ hamiltonian, dar nu şi un ciclu hamiltonian.// inițializări număr_noduri = număr de noduri din V // verifica dacă un nod este nou în lanţ nouÎnLanţ(nod, lanţ) { return !lanţ.conţine(nod) } // construieste lanţul hamiltonian construireLanţ(lanţ, lungime_lanţ) { nod_curent = ultimul element din lanţ dacă lungime_lanţ == număr_noduri { început = lanţ[0] sfârşit = nod_curent dacă muchie(început, sfârşit) // există muchie între cele 2 noduri { // lanţul este ciclu afişează ciclul return } } altfel { pentru orice nod u vecin al nodului curent { dacă nouÎnLanţ(u, lanţ) { addLast(lanţ, u) // adaugă u la lanţ construireLanţ(lanţ, lungime_lanţ + 1) } } } } // apelează construirea lanţului hamiltonian cicluHamiltonian { // din moment ce ar trebui să formeze un ciclu, lanţul poate incepe cu orice nod sursă = alegem un nod aleator din V addLast(lanţ, sursă) construireLanţ(lanţ, 1) }
1) [5p] Se dau n și m, numărul de noduri, respectiv muchii dintr-un graf neorientat. În continuare, se citesc cele m muchii.
Se cere determinarea numărului de componente conexe din acest graf și afișarea acestor componente.
Exemplu
12 10 0 1 0 2 1 2 2 3 4 5 4 6 5 6 4 7 7 8 9 10
4 0 1 2 3 4 5 6 7 8 9 10 11
2) [5p] Un curier, care se află într-un oraș A, trebuie să livreze un pachet într-un oraș B.
Pe hartă se află n
orașe, conectate prin m
străzi bidirecționale. Se știe faptul că fiecare dintre aceste străzi este parcursă într-un timp constant t
.
Se citesc n
, m
, cele m
străzi și orașele A
și B
.
Determinați ruta cea mai scurtă pe care poate ajunge curierul în orașul B, în cazul în care aceasta există, iar în caz contrar afișați mesajul “Impossible”.
Exemplu
7 10 0 1 0 4 1 2 1 3 1 4 2 4 3 5 3 6 4 5 4 6 0 6
0 4 6
3) [5p] În primii ani de studiu, toți studenții de la Facultatea de Automatică și Calculatoare studiază un număr de N materii obligatorii. Dându-se un set de relații între acestea, cu semnificația că materia din stânga trebuie studiată într-un semestru anterior (nu neapărat din același an), celei din partea dreaptă, găsiti și implementați un algoritm care propune o ordine corectă de studiere a materiilor universitare, care să respecte restricțiile impuse.
Exemplu
6 4 Programarea_Calculatoarelor Structuri_de_Date Structuri_de_Date Programare_Orientata_pe_Obiecte Matematica1 Fizica Matematica2 Fizica
Matematica2 Matematica1 Fizica Programarea_Calculatoarelor Structuri_de_Date Programare_Orientata_pe_Obiecte
4) [5p] Dându-se n
noduri și m
muchii ale unui graf neorientat, determinați dacă acest graf este bipartit și afișați cele două mulțimi care îl formează.
Exemplu
9 8 0 1 0 6 1 2 2 7 3 6 4 7 4 8 5 8
0 2 3 4 5 1 6 7 8
5) [5p] Un curier trebuie să livreze pachete în n
orașe. Orașele sunt codificate prin numere de la 0
la n-1
. Se cunosc m străzi bidirecționale, legături între orașe.
Sediul curieratului se află în orașul 0
. Determinați dacă există o rută pe care curierul să o urmeze astfel încât acesta să efectueze toate livrările și să se întoarcă la sediu, astfel încât el va trece prin fiecare destinație o singură data. În cazul în care există, se va afișa această rută, iar în caz contrar, mesajul “Impossible”.
Se citesc n
, m
și cele m
străzi bidirecționale.
Exemplu
5 7 0 1 1 2 0 3 1 3 1 4 2 4 3 4
0 1 2 4 3 0
[1] - Componente conexe
[2] - Distanţa minimă
[3] - Sortare topologică
[4] - Graf bipartit
[5] - Lanţ hamiltonian si ciclu hamiltonian
[6] - Dijkstra
[7] - Bellman-Ford
[8] - Floyd-Warshall
[9] - A*