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

Structuri de date avansate

Studiul structurilor de date dinamice și al modalităților de reprezentare și prelucrare a acestora.

Bacalaureat

Pointeri și alocare dinamică

Un pointer este o variabilă care memorează adresa altei variabile, nu valoarea ei. În C++, `int p;` declară un pointer către int, `&x` este adresa variabilei x, iar `p` (operatorul de dereferențiere) este valoarea de la adresa memorată în p. Confuzia care costă puncte: p este adresa, *p este valoarea — a le amesteca schimbă complet sensul unei atribuiri.

Memoria unui program are două zone cu comportament diferit:

Ce se alocă cu `new` trebuie eliberat explicit cu `delete p;` respectiv `delete[] p;`. Neeliberarea produce scurgeri de memorie (memory leak): memoria rămâne ocupată deși nu mai e folosită. Alte două erori clasice: pointerul invalid (dangling) — folosirea unui pointer după ce zona lui a fost eliberată — și dereferențierea unui pointer nul (`NULL` sau `nullptr`), care oprește programul.

Alocarea dinamică este fundamentul tuturor structurilor din acest capitol: liste, stive, cozi și arbori cresc și scad în timpul execuției exact pentru că nodurile lor se creează cu new, pe măsură ce e nevoie.

Liste liniare înlănțuite

O listă simplu înlănțuită este o înșiruire de noduri alocate dinamic, fiecare nod conținând o informație utilă și un pointer către nodul următor. Primul nod se numește cap (prim), iar câmpul următor al ultimului nod este NULL — semnul de sfârșit.

Comparația cu vectorul, cerută frecvent:

Operațiile de bază și capcanele lor:

Lista dublu înlănțuită adaugă fiecărui nod un pointer către predecesor, permițând parcurgerea în ambele sensuri și ștergerea unui nod fără căutarea predecesorului — cu prețul unei legături suplimentare de întreținut la fiecare operație. Lista circulară leagă ultimul nod înapoi la primul; la parcurgere, condiția de oprire nu mai este NULL, ci revenirea la nodul de start.

Stiva

Stiva (stack) este o structură de date de tip LIFO — Last In, First Out: ultimul element introdus este primul extras. Modelul intuitiv: un teanc de farfurii — pui și iei doar de deasupra.

Operațiile permise, singurele:

Esența restricției: nu ai acces decât la vârf. Nu poți citi sau scoate un element din mijlocul stivei fără să scoți mai întâi tot ce e deasupra lui. Grilele exploatează exact această regulă: dacă în stivă s-au introdus, în ordine, 1, 2, 3, extragerea le produce în ordinea 3, 2, 1.

Implementare: pe vector (cu o variabilă care reține indicele vârfului) sau pe listă înlănțuită (push și pop la începutul listei, ambele O(1)).

Aplicații de reținut pentru examen: inversarea unei secvențe, verificarea parantezărilor corecte (fiecare paranteză deschisă se pune pe stivă, fiecare închisă trebuie să se potrivească cu vârful), evaluarea expresiilor și, esențial, stiva de execuție a apelurilor recursive — motivul pentru care recursivitatea și stiva sunt două fețe ale aceluiași mecanism.

Coada

Coada (queue) este structura de tip FIFO — First In, First Out: primul element introdus este primul extras, exact ca la o coadă la casă. Este imaginea în oglindă a stivei, iar perechea LIFO/FIFO este verificată aproape garantat în teste.

Operațiile specifice:

Dacă în coadă intră, în ordine, 1, 2, 3, ele ies în ordinea 1, 2, 3 — ordinea se păstrează, spre deosebire de stivă, care o inversează.

Implementarea pe vector are o subtilitate: extragerile de la început lasă spațiu nefolosit în față. Soluția standard este coada circulară, în care indicii de început și sfârșit înaintează modulo dimensiunea vectorului, reciclând pozițiile eliberate. Implementarea pe listă înlănțuită ține doi pointeri, către primul și către ultimul nod, ambele operații fiind O(1).

Aplicații: parcurgerea în lățime (BFS) a grafurilor și arborilor — coada este chiar inima algoritmului —, planificarea proceselor în sistemele de operare, tampoanele (buffer) de tipărire, simulările de fire de așteptare. Regula de alegere între cele două structuri: ai nevoie de revenire în ordine inversă → stivă; ai nevoie de servire în ordinea sosirii → coadă.

Arbori binari

Un arbore binar este o structură ierarhică formată din noduri, în care fiecare nod are cel mult doi descendenți: un fiu stâng și un fiu drept. Nodul din vârf, fără părinte, este rădăcina; nodurile fără niciun fiu sunt frunze. Înălțimea arborelui este numărul de niveluri (sau, în alte convenții, numărul de muchii pe cel mai lung drum de la rădăcină la o frunză — la examen se lucrează cu convenția din enunț).

Cele trei parcurgeri în adâncime, definite recursiv prin poziția rădăcinii:

Trucul de memorare: prefixul spune unde e Rădăcina — pre = prima, in = la mijloc, post = ultima. Subarborele stâng vine mereu înaintea celui drept.

Arborele binar de căutare (ABC) adaugă regula de ordine: în subarborele stâng al oricărui nod stau doar valori mai mici, în cel drept doar valori mai mari. Consecința de aur, cerută la examen: parcurgerea în inordine a unui ABC produce valorile în ordine crescătoare. Căutarea unei valori coboară din rădăcină alegând stânga sau dreapta după comparație — în un arbore echilibrat, O(log n) pași; degenerat în „listă” (inserții în ordine crescătoare), O(n).

Reprezentarea în memorie folosește noduri alocate dinamic cu doi pointeri (stâng, drept), iar parcurgerile se implementează natural recursiv.

Grafuri: reprezentare și parcurgere

Un graf neorientat G = (V, E) este format dintr-o mulțime de vârfuri (noduri) și o mulțime de muchii — perechi neordonate de vârfuri. La graful orientat, legăturile sunt arce — perechi ordonate, cu sens. Gradul unui vârf este numărul de muchii incidente lui; suma gradelor tuturor vârfurilor este dublul numărului de muchii — identitate cerută des în grile.

Reprezentări:

Un lanț (drum) este o succesiune de vârfuri legate prin muchii; un ciclu este un lanț care se întoarce în vârful de plecare. Un graf este conex dacă între oricare două vârfuri există un lanț; porțiunile conexe maximale se numesc componente conexe. Un arbore este exact un graf conex fără cicluri, cu n - 1 muchii.

Parcurgeri:

Ambele vizitează fiecare vârf o singură dată, cu ajutorul unui vector de marcaje vizitat; fără el, parcurgerea ciclează la nesfârșit pe grafuri cu cicluri. Asocierea de reținut: BFS ↔ coadă, DFS ↔ stivă — inversarea lor este greșeala standard.

De reținut

pointer
variabilă care memorează adresa altei variabile; operatorul & furnizează adresa, iar * (dereferențierea) accesează valoarea de la adresa memorată
alocare dinamică
rezervarea de memorie în heap în timpul execuției, cu operatorul new; memoria astfel obținută trebuie eliberată explicit cu delete
listă simplu înlănțuită
structură dinamică formată din noduri alocate dinamic, fiecare conținând informația utilă și un pointer către nodul următor; ultimul nod are legătura NULL
stivă (LIFO)
structură de date în care inserarea (push) și extragerea (pop) se fac la același capăt, vârful; ultimul element introdus este primul extras
coadă (FIFO)
structură de date în care adăugarea se face la sfârșit și extragerea de la început; primul element introdus este primul extras
arbore binar de căutare
arbore binar în care, pentru orice nod, subarborele stâng conține doar valori mai mici, iar cel drept doar valori mai mari; inordinea îl parcurge crescător
parcurgere în inordine
parcurgerea arborelui binar în ordinea: subarbore stâng, rădăcină, subarbore drept; aplicată unui arbore binar de căutare, produce valorile sortate crescător
matrice de adiacență
matrice n×n asociată unui graf, cu 1 pe poziția (i, j) dacă există muchie între vârfurile i și j; simetrică pentru grafurile neorientate
graf conex
graf neorientat în care între oricare două vârfuri există cel puțin un lanț; porțiunile conexe maximale ale unui graf se numesc componente conexe
BFS (parcurgere în lățime)
parcurgere a grafului care folosește o coadă și vizitează vârfurile în ordinea crescătoare a distanței față de vârful de start

Greșeli frecvente

Greșit: Confundarea pointerului p cu valoarea *p în atribuiri
Corect: p memorează o adresă, *p este valoarea de la acea adresă; p = q copiază adrese (ambii pointeri arată spre același loc), *p = *q copiază valori
Greșit: La inserarea în capul listei, mutarea capului înainte de a lega nodul nou
Corect: Întâi nodul nou primește legătura către vechiul cap (nou->urm = prim), abia apoi prim = nou; în ordine inversă se pierde accesul la restul listei
Greșit: Inversarea comportamentelor: stiva ar fi FIFO și coada LIFO
Corect: Stiva este LIFO (ultimul intrat, primul ieșit), coada este FIFO (primul intrat, primul ieșit); introduse 1, 2, 3, stiva le scoate 3, 2, 1, coada le scoate 1, 2, 3
Greșit: Parcurgerea unui graf cu cicluri fără vector de vizitare
Corect: Fără marcarea vârfurilor deja vizitate, BFS și DFS revin la nesfârșit în aceleași vârfuri; vectorul vizitat se verifică înainte de fiecare adăugare în coadă sau stivă
Greșit: Asocierea BFS cu stiva și DFS cu coada
Corect: Este exact invers: BFS folosește coada (explorare pe niveluri), DFS folosește stiva sau recursivitatea (explorare în adâncime pe o ramură)

Test — 6 întrebări ca la examen

1. Într-o stivă inițial vidă se execută: push(4), push(7), pop(), push(9), push(2), pop(). Elementul aflat în vârful stivei este:
  1. 2
  2. 4
  3. 7
  4. 9
Vezi răspunsul
9. După push(4), push(7) stiva e 4, 7; pop() scoate 7; push(9), push(2) o fac 4, 9, 2; ultimul pop() scoate 2, deci vârful rămâne 9. Răspunsul 2 e al celor care uită că ultimul pop l-a eliminat deja.
2. Într-o coadă inițial vidă intră, în ordine, elementele 5, 8, 1, 6. După două extrageri, primul element al cozii este:
  1. 1
  2. 6
  3. 8
  4. 5
Vezi răspunsul
1. Coada e FIFO: extragerile scot 5, apoi 8, deci în față rămâne 1. Răspunsul 6 ar corespunde unei stive (LIFO), unde s-ar scoate ultimele intrate — exact confuzia dintre cele două structuri.
3. Avantajul esențial al listei simplu înlănțuite față de vector este:
  1. accesul direct la al k-lea element în O(1)
  2. posibilitatea căutării binare în O(log n)
  3. inserarea și ștergerea la o poziție cunoscută fără deplasarea celorlalte elemente
  4. ocuparea unei zone mai mici de memorie pentru același număr de elemente
Vezi răspunsul
inserarea și ștergerea la o poziție cunoscută fără deplasarea celorlalte elemente. În listă, inserarea sau ștergerea înseamnă doar mutarea a două legături, pe când vectorul cere deplasarea tuturor elementelor următoare. Primele două variante sunt exact avantajele vectorului — lista nu are acces direct, deci nici căutare binară; iar memoria e mai mare, nu mai mică, din cauza pointerilor.
4. Parcurgerea în inordine a unui arbore binar de căutare produce valorile:
  1. în ordinea inserării lor
  2. în ordine crescătoare
  3. în ordine descrescătoare
  4. începând cu rădăcina
Vezi răspunsul
în ordine crescătoare. Inordinea vizitează întâi subarborele stâng (valori mai mici), apoi rădăcina, apoi subarborele drept (valori mai mari), deci într-un ABC rezultatul e sortat crescător. Varianta care începe cu rădăcina descrie preordinea — distractorul clasic dintre cele trei parcurgeri.
5. Un graf neorientat are 6 vârfuri cu gradele 2, 3, 1, 2, 3, 1. Numărul de muchii este:
  1. 12
  2. 6
  3. 5
  4. nu poate fi determinat doar din grade
Vezi răspunsul
6. Suma gradelor este 12, iar fiecare muchie contribuie cu exact 2 la această sumă, deci numărul de muchii este 12 / 2 = 6. Răspunsul 12 este al celor care uită împărțirea la 2 — cea mai frecventă scăpare la această identitate.
6. La parcurgerea BFS a unui graf conex pornind din vârful 1, vârfurile sunt vizitate:
  1. în ordinea crescătoare a numerelor lor, indiferent de muchii
  2. pe o singură ramură, cât mai adânc, apoi cu revenire
  3. în ordinea crescătoare a distanței (în muchii) față de vârful 1
  4. în ordinea descrescătoare a gradelor
Vezi răspunsul
în ordinea crescătoare a distanței (în muchii) față de vârful 1. BFS explorează pe niveluri, cu ajutorul cozii: întâi vecinii direcți ai startului, apoi vârfurile la distanța 2 și așa mai departe — de aceea găsește drumurile cu număr minim de muchii. Varianta cu înaintarea pe o ramură descrie DFS, perechea-capcană a acestei întrebări.
Deschide varianta interactivă — cu AI care îți explică
← Programare proceduralăBaze de date →
BiologieChimieEconomieFilosofieFizicăGeografieInformatică și TICIstorieLogică și argumentareMatematicăPsihologieLimba și literatura română