Structuri de date
Studiul tablourilor unidimensionale și bidimensionale ca modalități de organizare a datelor în memorie.
Bacalaureat
Tablouri unidimensionale (vectori)
Un tablou unidimensional (vector) este o colecție de elemente de același tip, memorate contiguu (una lângă alta) în memorie și identificate printr-un singur nume plus un indice. În C++ declarația int v[100]; rezervă loc pentru 100 de întregi, iar elementele se accesează cu v[0], v[1], ..., v[99].
Două lucruri trebuie fixate de la început, pentru că generează cele mai multe erori:
- indexarea începe de la 0 în C++: primul element este v[0], iar ultimul dintr-un vector cu n elemente este v[n-1], NU v[n]. Accesarea lui v[n] iese din tablou și produce comportament imprevizibil;
- dimensiunea declarată este capacitatea maximă, iar numărul de elemente efectiv folosite (n, citit de la tastatură) este de obicei mai mic. Subiectele de examen lucrează mereu cu n, nu cu capacitatea.
În pseudocodul de bacalaureat vectorii apar deseori indexați de la 1 la n — citește cu atenție enunțul, pentru că un raționament corect cu indexare greșită dă rezultat greșit.
Avantajul esențial al vectorului: accesul la orice element se face direct, în timp constant, cunoscând doar indicele — spre deosebire de datele citite una câte una, pe care nu le mai poți revizita.
Parcurgerea și prelucrările elementare
Parcurgerea înseamnă vizitarea elementelor unui vector, de regulă de la primul la ultimul, cu o structură pentru: pentru i ← 0, n-1 execută prelucrează v[i]. Pe acest schelet se construiesc prelucrările standard:
- suma elementelor: s ← 0, apoi la fiecare pas s ← s + v[i]; inițializarea cu 0 înainte de buclă este obligatorie;
- numărarea elementelor cu o proprietate (pare, pozitive, divizibile cu k): contor inițializat cu 0, incrementat doar când condiția e îndeplinită;
- minimul și maximul: se inițializează min cu primul element al vectorului, apoi fiecare element îl compară și îl înlocuiește dacă e mai mic. Inițializarea cu 0 este o greșeală clasică: pentru un vector cu toate elementele pozitive, minimul ar rămâne 0 — o valoare care nici nu există în vector;
- căutarea secvențială: parcurgi elementele până găsești valoarea căutată sau până se termină vectorul; rezultatul util e de obicei poziția, nu doar răspunsul da/nu.
O idee care apare des în subiecte: parcurgerea simultană de la ambele capete (i de la început, j de la sfârșit) — de exemplu pentru a verifica dacă vectorul e palindrom sau pentru a-l inversa fără vector suplimentar, interschimbând v[i] cu v[j] cât timp i < j.
Sortarea unui vector
A sorta înseamnă a rearanja elementele în ordine crescătoare sau descrescătoare. Doi algoritmi elementari sunt de știut cu pașii lor:
- Sortarea prin selecție: la pasul i se caută minimul din porțiunea nesortată (de la i la n-1) și se interschimbă cu elementul de pe poziția i. După pasul i, primele i+1 elemente sunt definitiv la locul lor. Face mereu același număr de comparații, indiferent de datele de intrare.
- Sortarea prin interschimbare (bubble sort): se compară perechi de elemente vecine și se interschimbă cele în ordine greșită; parcurgerile se repetă până când o trecere completă nu mai face nicio interschimbare. Pe un vector deja sortat se oprește după o singură trecere — de aceea pe date aproape ordonate e mai rapid decât selecția.
Operația de bază a ambilor este interschimbarea (swap) a două elemente, care cere o variabilă auxiliară: aux ← a; a ← b; b ← aux. Scrierea a ← b; b ← a pierde valoarea inițială a lui a — ambele variabile rămân cu valoarea lui b.
De reținut și utilitatea sortării: într-un vector sortat, minimul și maximul sunt la capete, elementele egale devin vecine (utile la eliminarea duplicatelor), iar căutarea unei valori poate folosi metode mult mai rapide decât parcurgerea completă.
Tablouri bidimensionale (matrice)
Un tablou bidimensional (matrice) organizează datele pe linii și coloane: int a[10][10]; declară o matrice, iar a[i][j] este elementul de pe linia i, coloana j — în această ordine, mereu. Inversarea indicilor (coloană, linie) este una dintre cele mai frecvente erori de punctaj.
Parcurgerea completă folosește două bucle imbricate: cea exterioară pentru linii, cea interioară pentru coloane. Cu bucla exterioară pe i și cea interioară pe j, matricea se parcurge pe linii; inversând buclele, se parcurge pe coloane.
Pentru o matrice pătratică (același număr n de linii și coloane) se definesc zonele clasice:
- diagonala principală: elementele a[i][i], adică cele cu i = j — din colțul stânga-sus în colțul dreapta-jos;
- diagonala secundară: elementele cu i + j = n - 1 (la indexare de la 0) — din colțul dreapta-sus în colțul stânga-jos;
- deasupra diagonalei principale: i < j; sub ea: i > j.
Prelucrări tipice de examen: suma elementelor de pe o linie dată, maximul de pe fiecare coloană, suma elementelor de pe diagonale, verificarea simetriei față de diagonala principală (a[i][j] = a[j][i] pentru orice i și j).
Subprograme care prelucrează tablouri
Prelucrările pe tablouri se împachetează natural în subprograme: o funcție care întoarce suma elementelor unui vector, o procedură care citește o matrice, o funcție care caută o valoare și întoarce poziția ei.
Particularitatea esențială în C++: tablourile se transmit prin adresă, nu prin valoare. Când dai un vector ca parametru unei funcții, funcția NU primește o copie, ci lucrează direct pe elementele originale — orice modificare făcută în subprogram rămâne după revenirea din apel. La variabilele simple (int, double) e invers: implicit se transmite o copie, iar modificările se pierd.
Consecințe practice:
- o funcție de sortare primită ca cerință modifică vectorul chiar dacă nu returnează nimic;
- pe lângă tablou se transmite separat și numărul de elemente n, pentru că din interiorul funcției dimensiunea utilă nu se poate deduce;
- antetul tipic arată astfel: void sortare(int v[], int n) — parantezele drepte goale semnalează că v este tablou.
Descompunerea în subprograme nu e doar eleganță: subiectul III de la bacalaureat cere explicit definirea unor subprograme cu antet impus, deci exersarea scrierii lor corecte (tip returnat, parametri, apel) aduce puncte direct.
De reținut
- tablou unidimensional (vector)
- structură de date omogenă în care elementele de același tip sunt memorate contiguu și accesate prin nume și indice
- indice
- numărul care identifică poziția unui element în tablou; în C++ primul element are indicele 0, iar ultimul dintre cele n are indicele n-1
- parcurgere
- vizitarea pe rând a elementelor unui tablou, de regulă cu o structură repetitivă cu contor, pentru a le prelucra
- căutare secvențială
- examinarea elementelor unui vector unul câte unul, până la găsirea valorii căutate sau până la epuizarea vectorului
- sortare prin selecție
- metodă de ordonare care la fiecare pas alege minimul (sau maximul) din zona nesortată și îl interschimbă cu primul element al acesteia
- interschimbare (swap)
- schimbarea valorilor a două variabile între ele folosind o a treia variabilă auxiliară: aux ← a; a ← b; b ← aux
- matrice
- tablou bidimensional organizat pe linii și coloane, în care a[i][j] este elementul de pe linia i și coloana j
- diagonala principală
- în matricea pătratică, mulțimea elementelor a[i][i], adică cele pentru care indicele liniei este egal cu indicele coloanei
- diagonala secundară
- în matricea pătratică cu n linii indexate de la 0, elementele pentru care suma indicilor este n-1
- transmiterea tablourilor ca parametri
- în C++ tablourile se transmit prin adresă: subprogramul lucrează pe elementele originale, iar modificările rămân după apel
Greșeli frecvente
Greșit: Ultimul element al unui vector cu n elemente ar fi v[n]
Corect: La indexare de la 0, ultimul element este v[n-1]; accesarea lui v[n] iese în afara tabloului — eroarea off by one care strică și buclele scrise cu <= în loc de <
Greșit: Minimul unui vector se inițializează cu 0
Corect: Minimul se inițializează cu primul element al vectorului (sau maximul cu primul element); inițializarea cu 0 dă rezultat fals pe vectori cu toate elementele pozitive, unde 0 nici nu apare
Greșit: Interschimbarea a două variabile s-ar face prin a ← b; b ← a
Corect: Fără variabilă auxiliară prima atribuire distruge valoarea lui a, iar ambele variabile ajung egale cu b; corect: aux ← a; a ← b; b ← aux
Greșit: a[i][j] ar fi elementul de pe coloana i și linia j
Corect: Primul indice este întotdeauna linia, al doilea coloana: a[i][j] = linia i, coloana j; inversarea lor face ca parcurgerea pe linii să devină pe coloane și invers
Greșit: Un vector transmis ca parametru unei funcții ar rămâne nemodificat, ca variabilele simple
Corect: Tablourile se transmit prin adresă: funcția modifică direct elementele originale; doar variabilele simple transmise prin valoare rămân neschimbate după apel
Test — 6 întrebări ca la examen
1. Un vector v cu 50 de elemente este indexat de la 0. Care este indicele ultimului element?
- 50
- 49
- 51
- 0
Vezi răspunsul
49. Elementele au indicii 0, 1, ..., 49 — în total 50. Varianta 50 este eroarea off by one: pare naturală pentru că vectorul are 50 de elemente, dar v[50] ar fi al 51-lea element, aflat în afara tabloului.
2. Pentru vectorul v = (3, 9, 2, 7), ce afișează secvența: m ← v[0]; pentru i ← 1, 3 execută dacă v[i] < m atunci m ← v[i]; scrie m?
- 9
- 3
- 2
- 7
Vezi răspunsul
2. Algoritmul determină minimul: m pornește de la 3, rămâne 3 la compararea cu 9, devine 2 la compararea cu v[2], iar 7 nu îl mai schimbă. Varianta 9 corespunde citirii greșite a semnului < ca > (adică maximul).
3. Într-o matrice pătratică cu 5 linii și 5 coloane, indexată de la 0, elementul a[1][3] se află pe:
- linia 3, coloana 1
- linia 1, coloana 3
- diagonala principală
- linia 2, coloana 4
Vezi răspunsul
linia 1, coloana 3. Primul indice este linia, al doilea coloana, deci a[1][3] e pe linia 1, coloana 3. Prima variantă inversează indicii — cea mai frecventă greșeală la matrice. Nu e pe diagonala principală pentru că 1 ≠ 3.
4. Într-o matrice pătratică cu n = 6 (indexare de la 0), un element a[i][j] este pe diagonala secundară dacă:
- i = j
- i + j = 6
- i + j = 5
- i - j = 5
Vezi răspunsul
i + j = 5. Pe diagonala secundară suma indicilor este n - 1 = 5: elementele a[0][5], a[1][4], ..., a[5][0]. Varianta i + j = 6 uită că indexarea începe de la 0 (ar fi corectă la indexarea de la 1), iar i = j descrie diagonala principală.
5. La sortarea prin selecție crescătoare a vectorului (8, 3, 5, 1), ce conține vectorul după PRIMUL pas complet?
- (3, 8, 5, 1)
- (1, 3, 5, 8)
- (3, 5, 1, 8)
- (1, 8, 5, 3)
Vezi răspunsul
(1, 8, 5, 3). Selecția caută minimul întregului vector (1, aflat pe ultima poziție) și îl interschimbă cu primul element: 8 ajunge unde era 1, deci rezultă (1, 8, 5, 3). Varianta (3, 8, 5, 1) descrie bubble sort, care interschimbă doar vecini — confuzia clasică între cele două metode.
6. O funcție void f(int v[], int n) dublează fiecare element al vectorului primit. Ce se întâmplă cu vectorul din programul principal după apelul f(a, n)?
- rămâne neschimbat, pentru că funcția lucrează pe o copie
- elementele lui sunt dublate, pentru că tablourile se transmit prin adresă
- se dublează doar primul element
- programul nu se compilează, pentru că funcția void nu poate modifica nimic
Vezi răspunsul
elementele lui sunt dublate, pentru că tablourile se transmit prin adresă. Tablourile se transmit prin adresă: funcția modifică direct elementele originale, deci după apel toate valorile din a sunt dublate. Prima variantă e tentantă pentru că descrie corect comportamentul variabilelor simple transmise prin valoare — dar tablourile sunt excepția.
Deschide varianta interactivă — cu AI care îți explică