Biblioteca
Tutora
BibliotecaInformatică și TIC › clasa a XII-a

Grafuri

Studiul structurilor de tip graf, al reprezentărilor și algoritmilor fundamentali pe grafuri.

Bacalaureat

Grafuri neorientate — definiții și proprietăți de bază

Un graf neorientat G = (V, U) este format dintr-o mulțime V de vârfuri (noduri) și o mulțime U de muchii — perechi neordonate de vârfuri distincte. Muchia [x, y] este totuna cu [y, x], iar x și y se numesc extremități și sunt vârfuri adiacente; muchia este incidentă cu fiecare dintre ele.

Gradul unui vârf, d(x), este numărul de muchii incidente cu el. Formula care apare aproape la fiecare sesiune: suma gradelor tuturor vârfurilor este dublul numărului de muchii — fiecare muchie contribuie cu 1 la gradul fiecărei extremități. Consecință directă: numărul vârfurilor de grad impar este întotdeauna par.

Vocabular care se punctează exact:

Graful complet Kn are toate muchiile posibile: n(n-1)/2. Un graf este bipartit dacă vârfurile se pot împărți în două mulțimi astfel încât orice muchie să lege un vârf din prima mulțime cu unul din a doua. Graful hamiltonian conține un ciclu elementar care trece prin toate vârfurile; graful eulerian conține un ciclu care folosește fiecare muchie exact o dată — condiția clasică: graful este conex și toate vârfurile au grad par.

Grafuri orientate — arce, grade, drumuri

Într-un graf orientat (digraf), legăturile sunt arce — perechi ordonate (x, y): arcul iese din x (extremitatea inițială) și intră în y (extremitatea finală). Arcul (x, y) NU este același lucru cu arcul (y, x) — exact aici pierd puncte candidații care tratează matricea de adiacență ca fiind simetrică.

Fiecare vârf are două grade:

Suma tuturor gradelor exterioare = suma tuturor gradelor interioare = numărul de arce.

Corespondentul lanțului este drumul (succesiune de vârfuri legate prin arce parcurse în sensul lor), iar corespondentul ciclului este circuitul. Un digraf cu n vârfuri are cel mult n(n-1) arce (fără bucle).

La conexitate apar două noțiuni distincte — o pereche cerută des la teorie:

O componentă tare conexă este un subgraf tare conex maximal. Orice graf tare conex este conex, dar reciproca este falsă — un contraexemplu minimal: două vârfuri și un singur arc între ele.

Reprezentarea grafurilor în memorie

Matricea de adiacență a este o matrice n x n cu a[i][j] = 1 dacă există muchia/arcul de la i la j și 0 altfel. Proprietăți care se cer:

Matricea de adiacență ocupă O(n²) memorie indiferent câte muchii există și răspunde în O(1) la întrebarea „există muchia (i, j)?”.

Listele de adiacență rețin, pentru fiecare vârf, lista vecinilor săi (succesorilor, la digrafuri). Ocupă O(n + m) memorie și sunt eficiente pentru grafuri rare (cu puține muchii); parcurgerea vecinilor unui vârf durează cât gradul lui, nu O(n).

Lista de muchii reține perechile (extremitate, extremitate) — utilă de exemplu la Kruskal, unde muchiile trebuie sortate după cost.

Pentru grafuri ponderate se folosește matricea costurilor: c[i][j] = costul muchiei/arcului, 0 pe diagonală și o valoare convențională „infinit” unde nu există legătură directă. Greșeala frecventă la probleme: a pune 0 în loc de infinit pentru perechile fără muchie, ceea ce transformă absența legăturii într-o legătură gratuită.

Parcurgerea grafurilor: BFS și DFS

Parcurgerea în lățime (BFS — Breadth First Search) vizitează mai întâi vârful de start, apoi toți vecinii lui, apoi vecinii nevizitați ai acestora, în valuri. Structura de date suport este coada (FIFO): se scoate un vârf din coadă, se adaugă la coadă toți vecinii lui nevizitați, marcându-i vizitați la introducere. Proprietatea de aur, cerută la probleme: în graf neponderat, BFS găsește drumul cu număr minim de muchii de la sursă la orice alt vârf.

Parcurgerea în adâncime (DFS — Depth First Search) merge pe o ramură cât de departe se poate, apoi revine și explorează alternativele. Se implementează natural recursiv sau iterativ cu o stivă (LIFO). Perechea de asociat obligatoriu la teorie: BFS — coadă, DFS — stivă; inversarea lor este una dintre cele mai frecvente erori de grilă.

Ambele parcurgeri au complexitatea O(n + m) cu liste de adiacență și O(n²) cu matrice de adiacență, și ambele vizitează exact componenta conexă a vârfului de start.

De aici algoritmul standard pentru numărarea componentelor conexe: se parcurg vârfurile în ordine și, pentru fiecare vârf încă nevizitat, se lansează o parcurgere (BFS sau DFS) și se incrementează contorul. Numărul de lansări = numărul componentelor conexe.

Alte aplicații de examen: verificarea conexității (o singură parcurgere atinge toate vârfurile?), verificarea existenței unui lanț între două vârfuri, determinarea vârfurilor accesibile dintr-un vârf dat într-un digraf.

Arbori parțiali de cost minim: Kruskal și Prim

Un arbore parțial al unui graf conex cu n vârfuri este un graf parțial care este arbore: conex, fără cicluri, cu exact n-1 muchii. Într-un graf ponderat, arborele parțial de cost minim (APM) este arborele parțial cu suma costurilor muchiilor minimă. Ambii algoritmi clasici sunt aplicații ale metodei Greedy.

Kruskal lucrează cu muchiile: le sortează crescător după cost și le parcurge în această ordine, adăugând fiecare muchie la soluție doar dacă nu formează ciclu cu muchiile deja alese (adică extremitățile ei sunt în componente diferite). Se oprește după ce a acceptat n-1 muchii. În timpul rulării, soluția parțială poate fi o pădure — bucăți deocamdată nelegate între ele.

Prim lucrează cu vârfurile: pornește dintr-un vârf oarecare și crește un singur arbore, adăugând la fiecare pas muchia de cost minim care leagă un vârf din arbore de un vârf din afara lui. Soluția parțială este mereu conexă — exact invers decât la Kruskal; această diferență de comportament este întrebare de grilă recurentă.

Observații care aduc puncte:

Capcană: la Kruskal NU se sare o muchie doar pentru că are cost mare — se sare doar dacă ar închide un ciclu.

Drumuri de cost minim: Dijkstra și Bellman-Ford

Problema: într-un graf ponderat, să se afle costul minim al drumului de la un vârf sursă la celelalte vârfuri. Atenție la distincție: drumul de cost minim nu coincide neapărat cu drumul cu cele mai puține muchii — un ocol cu multe muchii ieftine poate bate legătura directă scumpă.

Dijkstra menține pentru fiecare vârf o distanță estimată d[x] (inițial 0 pentru sursă, infinit pentru rest) și o mulțime de vârfuri finalizate. La fiecare pas selectează vârful nefinalizat cu distanța estimată minimă — alegere de tip Greedy — îl marchează finalizat și relaxează muchiile care ies din el: dacă d[x] + c[x][y] < d[y], atunci d[y] se actualizează. Odată finalizat, un vârf nu se mai atinge.

Limita esențială, cerută explicit la teorie: Dijkstra funcționează corect doar cu costuri nenegative. Un cost negativ poate face ca un vârf deja finalizat să primească ulterior un drum mai bun, iar algoritmul nu mai revine asupra lui.

Bellman-Ford rezolvă și cazul costurilor negative: relaxează toate arcele, repetat, de n-1 ori (un drum elementar are cel mult n-1 arce). Dacă la o a n-a trecere vreo distanță încă scade, graful conține un circuit de cost negativ accesibil din sursă — și atunci noțiunea de drum minim nu mai are sens, pentru că circuitul poate fi parcurs oricât pentru a scădea costul. Prețul generalității: complexitate O(n·m), mai mare decât la Dijkstra.

Pentru distanțele minime între toate perechile de vârfuri se folosește algoritmul Roy-Floyd (Floyd-Warshall), cu trei cicluri imbricate și complexitate O(n³), în care vârful intermediar k se iterează în ciclul exterior.

De reținut

graf neorientat
pereche G = (V, U) formată din mulțimea vârfurilor și mulțimea muchiilor — perechi neordonate de vârfuri distincte
gradul unui vârf
numărul muchiilor incidente cu vârful; suma gradelor tuturor vârfurilor este egală cu dublul numărului de muchii
graf parțial / subgraf
graful parțial se obține eliminând doar muchii (păstrând toate vârfurile); subgraful se obține eliminând vârfuri împreună cu muchiile incidente lor
graf tare conex
graf orientat în care pentru oricare două vârfuri x și y există drum atât de la x la y, cât și de la y la x
matrice de adiacență
matrice n x n cu 1 pe poziția (i, j) dacă există muchia sau arcul de la i la j și 0 altfel; simetrică pentru grafuri neorientate
parcurgere BFS
parcurgere în lățime, pe niveluri, folosind o coadă; în grafuri neponderate găsește drumul cu număr minim de muchii de la sursă
parcurgere DFS
parcurgere în adâncime, care avansează pe o ramură cât mai departe și revine la nevoie; se implementează recursiv sau cu stivă
arbore parțial de cost minim
graf parțial conex și fără cicluri, cu n-1 muchii, care are suma costurilor muchiilor minimă; se determină cu algoritmii Kruskal sau Prim
algoritmul lui Dijkstra
algoritm Greedy care determină costul minim al drumurilor de la o sursă la toate celelalte vârfuri, corect doar pentru costuri nenegative
graf hamiltonian / graf eulerian
graful hamiltonian conține un ciclu elementar care trece prin toate vârfurile; graful eulerian conține un ciclu care folosește fiecare muchie exact o dată (conex, toate gradele pare)

Greșeli frecvente

Greșit: Se confundă graful parțial cu subgraful
Corect: Graful parțial păstrează toate vârfurile și elimină doar muchii; subgraful elimină vârfuri împreună cu muchiile incidente lor
Greșit: La graful orientat se calculează gradul exterior adunând elementele de pe coloana vârfului în matricea de adiacență
Corect: Suma pe linia i dă gradul exterior (arcele care ies din i); suma pe coloana i dă gradul interior (arcele care intră în i)
Greșit: Se asociază BFS cu stiva și DFS cu coada
Corect: Exact invers: BFS folosește o coadă (FIFO, vizitare pe niveluri), DFS folosește stiva (LIFO) sau recursivitatea
Greșit: Se aplică Dijkstra pe grafuri cu costuri negative
Corect: Dijkstra garantează corectitudinea doar pentru costuri nenegative; la costuri negative se folosește Bellman-Ford, care detectează și circuitele de cost negativ
Greșit: La Kruskal se resping muchiile cu cost mare sau se presupune că soluția parțială e mereu conexă
Corect: La Kruskal o muchie se respinge doar dacă ar forma ciclu, indiferent de cost, iar soluția parțială poate fi o pădure; Prim este cel care menține mereu un singur arbore conex

Test — 6 întrebări ca la examen

1. Un graf neorientat are 6 vârfuri cu gradele 1, 2, 2, 3, 3, 3. Numărul de muchii este:
  1. 14
  2. 7
  3. 6
  4. nu poate exista un astfel de graf
Vezi răspunsul
7. Suma gradelor este 14, iar numărul de muchii este jumătatea ei, adică 7, pentru că fiecare muchie contribuie la gradul ambelor extremități. Răspunsul 14 este capcana pentru cine uită împărțirea la 2.
2. Numărul maxim de muchii ale unui graf neorientat cu 8 vârfuri este:
  1. 64
  2. 56
  3. 28
  4. 16
Vezi răspunsul
28. Graful complet K8 are n(n-1)/2 = 8·7/2 = 28 de muchii. Varianta 56 corespunde formulei n(n-1) fără împărțirea la 2 — aceea este valabilă pentru numărul maxim de arce ale unui graf orientat, nu pentru muchii.
3. Care structură de date se folosește la implementarea parcurgerii în lățime (BFS)?
  1. stiva
  2. coada
  3. arborele binar de căutare
  4. tabela de dispersie
Vezi răspunsul
coada. BFS vizitează vârfurile pe niveluri, în ordinea descoperirii, deci are nevoie de o structură FIFO — coada. Stiva este distractorul clasic, dar ea corespunde parcurgerii în adâncime (DFS).
4. Într-un graf neorientat cu n vârfuri, orice arbore parțial are:
  1. exact n muchii
  2. exact n-1 muchii
  3. cel puțin n muchii
  4. un număr de muchii egal cu numărul componentelor conexe
Vezi răspunsul
exact n-1 muchii. Un arbore cu n vârfuri are prin definiție n-1 muchii: este conex și fără cicluri, iar adăugarea oricărei muchii suplimentare ar închide un ciclu. Varianta cu n muchii descrie un graf conex cu exact un ciclu, nu un arbore.
5. Algoritmul lui Kruskal, aplicat unui graf conex ponderat, adaugă la soluție o muchie doar dacă:
  1. are costul mai mic decât media costurilor
  2. leagă un vârf din arborele curent de unul din afara lui
  3. nu formează ciclu cu muchiile deja selectate
  4. este incidentă cu vârful de pornire
Vezi răspunsul
nu formează ciclu cu muchiile deja selectate. Kruskal parcurge muchiile în ordinea crescătoare a costurilor și acceptă orice muchie care nu închide un ciclu, până adună n-1 muchii. Varianta a doua descrie criteriul lui Prim — distractorul standard, pentru că ambii algoritmi construiesc același tip de soluție, dar prin strategii diferite.
6. Într-un graf orientat cu costuri pe arce, dintre care unele sunt negative dar fără circuite de cost negativ, drumul de cost minim de la o sursă la celelalte vârfuri se determină corect cu:
  1. algoritmul lui Dijkstra, pentru că este cel mai rapid
  2. algoritmul Bellman-Ford, care admite costuri negative
  3. o parcurgere BFS, pentru că ea găsește drumurile minime
  4. algoritmul lui Prim, aplicat din vârful sursă
Vezi răspunsul
algoritmul Bellman-Ford, care admite costuri negative. Bellman-Ford relaxează toate arcele de n-1 ori și rămâne corect la costuri negative, cât timp nu există circuite negative. Dijkstra este distractorul tentant fiind mai cunoscut, dar strategia lui Greedy finalizează vârfurile definitiv și poate greși când un cost negativ ar îmbunătăți ulterior o distanță; BFS minimizează numărul de muchii, nu costul, iar Prim rezolvă cu totul altă problemă (APM).
Deschide varianta interactivă — cu AI care îți explică
← Tehnici de programareArbori →
BiologieChimieEconomieFilosofieFizicăGeografieInformatică și TICIstorieLogică și argumentareMatematicăPsihologieLimba și literatura română