Tematică științifică · 5.9
Arbori cu rădăcină și arbori binari
Arborele cu rădăcină, terminologia și reprezentarea cu referințe ascendente și descendente; arborele binar, tipurile speciale (strict, complet, aproape complet), proprietăți, reprezentări și parcurgeri; arborele binar de căutare; ansamblul heap, cu inserarea, eliminarea cheii maxime și HeapSort; programe în C++ și subiectele date la examen.
Structura directoarelor de pe un disc, organigrama unei firme și arborele genealogic al unei familii au în comun o ierarhie: fiecare element, în afară de unul singur, este subordonat direct unui alt element. Modelul lor este arborele cu rădăcină. Când fiecare element are cel mult doi subordonați, ale căror poziții se deosebesc, modelul este arborele binar.
Lecția prezintă:
- arborele cu rădăcină, terminologia și reprezentările lui;
- arborele binar, tipurile speciale de arbori binari, proprietățile și parcurgerile lor;
- arborele binar de căutare;
- ansamblul heap și operațiile lui.
Implementarea dinamică a arborilor binari și a arborilor binari de căutare, cu toate operațiile, este prezentată în subcapitolul Structuri de date implementate dinamic. Aici se folosește implementarea statică, cu vectori.
Noțiuni preliminare
Arborii cu rădăcină se definesc pornind de la arbore:
Arborii și proprietățile lor sunt prezentați în subcapitolul Arbori. Arbori parțiali de cost minim.
Arborele cu rădăcină
Definiție și terminologie
Între rădăcină și fiecare alt nod există un singur lanț elementar, deci relațiile dintre noduri se stabilesc după poziția lor pe acest lanț.
Arborele alăturat are rădăcina 1. Pentru el:
- nodul 2 este tatăl (părintele) nodurilor 4 și 5, iar 4 și 5 sunt fiii lui 2;
- nodurile 4 și 5 sunt frați, fiindcă au același tată;
- nodurile 4, 6 și 7 nu au fii: sunt frunze (noduri terminale); celelalte sunt noduri interne;
- nodurile 1, 2 și 5 sunt ascendenții (strămoșii) lui 7, iar 7 este descendentul lor.
Se mai folosesc termenii:
Arborele cu rădăcină are și o definiție recursivă: un arbore cu rădăcină este format din rădăcină și din zero, unul sau mai mulți arbori cu rădăcină, numiți subarbori, legați de ea. Rădăcinile subarborilor sunt fiii rădăcinii. Doi subarbori ai aceluiași arbore fie nu au noduri comune, fie unul este inclus în celălalt.
Alegerea rădăcinii. Același arbore dă arbori cu rădăcină diferiți pentru rădăcini diferite. Cu rădăcina 5, arborele de mai sus are fiii 2 și 7 pe nivelul 1, iar înălțimea devine 4. Tatăl nodului 1 este acum 2, iar nodul 1, care era rădăcină, are un singur fiu, nodul 3.
Reprezentarea arborilor cu rădăcină
Arborele cu rădăcină se poate memora ca orice graf, cu matricea de adiacență, dar reprezentările specifice folosesc legătura dintre tată și fii:
Pentru arborele cu rădăcina 1:
Nodul i | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
t[i] (referințe ascendente) | 0 | 1 | 1 | 2 | 2 | 3 | 5 |
| fiii (referințe descendente) | 2, 3 | 4, 5 | 6 | — | 7 | — | — |
Din vectorul de tați se obțin:
- rădăcina — nodul
icut[i] = 0; - tatăl nodului
x— valoareat[x], într-un singur pas; - fiii nodului
x— nodurileicut[i] = x, printr-o parcurgere a vectorului; - frunzele — nodurile care nu apar ca valori în vectorul
t; - frații nodului
x— nodurilei ≠ xcut[i] = t[x]; - nivelul nodului
x— numărul de pașix ← t[x]până la rădăcină.
Vectorul de tați este potrivit pentru operațiile care se deplasează spre rădăcină, iar listele de fii pentru cele care se deplasează spre frunze. Denumirile se folosesc uneori invers: vectorul de tați este numit și „implementare prin referințe descendente”. Programa școlară din 2009, citată în subiectul de Titularizare 2026, folosește denumirile din tabelul de mai sus.
Operații cu vectorul de tați
Enunț. Se citesc numărul de noduri n și vectorul de tați al unui arbore cu rădăcină, apoi
două noduri x și y. Să se afișeze, pentru fiecare nod, tatăl, nivelul și fiii, înălțimea
arborelui și primul strămoș comun al nodurilor x și y.
Primul strămoș comun se determină astfel: se marchează strămoșii lui x, inclusiv x, trecând
din tată în tată, apoi se trece din y din tată în tată până la primul nod marcat. Pentru nodurile 4 și 7, cele
două drumuri spre rădăcină se întâlnesc prima dată în nodul 2.
#include <iostream>
using namespace std;
const int MAX = 101;
int t[MAX], stramos[MAX], n;
void citire() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> t[i];
}
/* Nivelul nodului x este numarul de pasi facuti din x,
din tata in tata, pana la radacina. */
int nivel(int x) {
int k = 0;
while (t[x] != 0) {
x = t[x];
k++;
}
return k;
}
/* Pentru fiecare nod: tatal, nivelul si fiii, adica nodurile i cu t[i]
egal cu nodul respectiv. Nodul fara fii este frunza. */
void afisare() {
int h = 0;
for (int x = 1; x <= n; x++) {
cout << "nodul " << x << ": tata " << t[x] << ", nivel " << nivel(x) << ", fii:";
int nrFii = 0;
for (int i = 1; i <= n; i++)
if (t[i] == x) {
cout << " " << i;
nrFii++;
}
if (nrFii == 0)
cout << " - (frunza)";
cout << "\n";
if (nivel(x) > h)
h = nivel(x);
}
cout << "Inaltimea arborelui: " << h << "\n";
}
/* Se marcheaza stramosii lui x, inclusiv x, apoi se trece din y
din tata in tata pana la primul nod marcat. */
int stramosComun(int x, int y) {
for (int k = x; k != 0; k = t[k])
stramos[k] = 1;
int k = y;
while (k != 0 && stramos[k] == 0)
k = t[k];
return k;
}
int main() {
int x, y;
citire();
afisare();
cin >> x >> y;
cout << "Primul stramos comun al nodurilor " << x << " si " << y << ": "
<< stramosComun(x, y) << "\n";
return 0;
}
Pentru datele 7, 0 1 1 2 2 3 5 și nodurile 4 7 programul afișează:
nodul 1: tata 0, nivel 0, fii: 2 3
nodul 2: tata 1, nivel 1, fii: 4 5
nodul 3: tata 1, nivel 1, fii: 6
nodul 4: tata 2, nivel 2, fii: - (frunza)
nodul 5: tata 2, nivel 2, fii: 7
nodul 6: tata 3, nivel 2, fii: - (frunza)
nodul 7: tata 5, nivel 3, fii: - (frunza)
Inaltimea arborelui: 3
Primul stramos comun al nodurilor 4 si 7: 2
Descrierea soluției. Subprogramul nivel trece din nod în tatăl lui cu ajutorul vectorului t
și numără pașii. Subprogramul afisare caută, pentru fiecare nod x, indicii i cu t[i] = x,
care sunt fiii lui, și reține cel mai mare nivel, adică înălțimea. Subprogramul stramosComun
marchează în vectorul stramos drumul de la x la rădăcină, apoi trece din y din tată în tată. Afișarea fiilor
tuturor nodurilor cere O(n²) pași, iar nivelul și strămoșul comun cer câte O(h) pași, unde
h este înălțimea.
Operațiile se pot urmări pas cu pas în simulatorul arborelui cu rădăcină🔒, pe același arbore.
Construirea vectorului de tați și parcurgerile
Un arbore dat prin muchii devine arbore cu rădăcină după alegerea rădăcinii r. Parcurgerea în
adâncime din r ajunge în fiecare nod j dintr-un nod x, iar x este tatăl lui j.
Arborele cu rădăcină se parcurge astfel:
- în preordine — se prelucrează rădăcina, apoi se parcurg, pe rând, subarborii fiilor ei;
- în postordine — se parcurg subarborii fiilor, apoi se prelucrează rădăcina;
- pe niveluri (în lățime) — se prelucrează rădăcina, apoi nodurile de pe nivelul 1, apoi cele de pe nivelul 2 și așa mai departe, cu ajutorul unei cozi.
Enunț. Se citesc numărul de noduri n, rădăcina r și cele n − 1 muchii ale unui arbore.
Să se construiască vectorul de tați și să se afișeze parcurgerile în preordine, în postordine și
pe niveluri. Fiii unui nod se iau în ordinea crescătoare a etichetelor.
#include <iostream>
using namespace std;
const int MAX = 101;
int a[MAX][MAX], t[MAX], viz[MAX], coada[MAX], n, r;
/* Se citesc n, radacina r si cele n - 1 muchii ale arborelui. */
void citire() {
int x, y;
cin >> n >> r;
for (int k = 1; k < n; k++) {
cin >> x >> y;
a[x][y] = a[y][x] = 1;
}
}
/* Parcurgerea in adancime din radacina: nodul j atins din nodul x
are tatal x. */
void construiesteTati(int x) {
viz[x] = 1;
for (int j = 1; j <= n; j++)
if (a[x][j] == 1 && viz[j] == 0) {
t[j] = x;
construiesteTati(j);
}
}
/* Nodul x, apoi subarborii fiilor lui, luati in ordinea etichetelor. */
void preordine(int x) {
cout << x << " ";
for (int j = 1; j <= n; j++)
if (t[j] == x)
preordine(j);
}
/* Subarborii fiilor lui x, apoi nodul x. */
void postordine(int x) {
for (int j = 1; j <= n; j++)
if (t[j] == x)
postordine(j);
cout << x << " ";
}
/* Pe niveluri: in coada intra, pe rand, fiii nodului din capul cozii. */
void niveluri() {
int p = 1, u = 1;
coada[1] = r;
while (p <= u) {
for (int j = 1; j <= n; j++)
if (t[j] == coada[p]) {
u++;
coada[u] = j;
}
p++;
}
for (int i = 1; i <= u; i++)
cout << coada[i] << " ";
}
int main() {
citire();
construiesteTati(r);
cout << "Vectorul de tati:";
for (int i = 1; i <= n; i++)
cout << " " << t[i];
cout << "\nPreordine: ";
preordine(r);
cout << "\nPostordine: ";
postordine(r);
cout << "\nPe niveluri: ";
niveluri();
cout << "\n";
return 0;
}
Pentru arborele din desen, cu muchiile [1,2], [1,3], [2,4], [2,5], [3,6], [5,7], programul
afișează:
| Rădăcina | Vectorul de tați | Preordine | Postordine | Pe niveluri |
|---|---|---|---|---|
| 1 | 0 1 1 2 2 3 5 | 1 2 4 5 7 3 6 | 4 7 5 2 6 3 1 | 1 2 3 4 5 6 7 |
| 5 | 2 5 1 2 0 3 5 | 5 2 1 3 6 4 7 | 6 3 1 4 2 7 5 | 5 2 7 1 4 3 6 |
Descrierea soluției. Subprogramul construiesteTati este parcurgerea în adâncime din
subcapitolul
Parcurgerea grafurilor,
în care nodul nevizitat j primește tatăl x. Subprogramele preordine și postordine se
autoapelează pentru fiecare fiu, adică pentru fiecare j cu t[j] = x, înainte sau după
afișarea nodului. Subprogramul niveluri pune în coadă fiii nodului din capul cozii. Căutarea
fiilor în vectorul de tați face ca fiecare parcurgere să ceară O(n²) pași.
Arborele binar
Definiție
Poziția fiului contează: un nod cu un singur fiu precizează dacă acesta este fiul stâng sau fiul drept.
Definiția recursivă: un arbore binar este fie arborele vid, fără noduri, fie un nod rădăcină căruia îi sunt atașați doi arbori binari, subarborele stâng și subarborele drept.
În arborele alăturat, nodul 3 are numai fiu drept, nodul 6 numai fiu stâng, iar frunzele sunt 4, 5 și 7.
Tipuri speciale de arbori binari
Se definesc următoarele tipuri de arbori binari:
Denumirile nu sunt aceleași la toți autorii. Denumirea „arbore binar complet” este folosită uneori pentru arborele în care fiecare nod intern are exact doi fii, adică pentru arborele strict de mai sus. La examen se scrie definiția folosită, nu doar denumirea.
Proprietăți ale arborilor binari
- Pe nivelul
kal unui arbore binar se află cel mult2ᵏnoduri, fiindcă fiecare nod de pe nivelulk − 1are cel mult doi fii. - Un arbore binar cu înălțimea
hare cel mult1 + 2 + … + 2ʰ = 2ʰ⁺¹ − 1noduri. Prin urmare, un arbore binar cunnoduri are înălțimea cel puțin[log₂ n]și cel multn − 1. - Un arbore binar complet cu înălțimea
hare2ʰfrunze și2ʰ⁺¹ − 1noduri. - Într-un arbore binar nevid, numărul frunzelor este cu 1 mai mare decât numărul nodurilor cu doi
fii. Dacă
n₀,n₁,n₂sunt numerele de noduri cu 0, 1 și 2 fii, numărul muchiilor se scrie în două feluri:n − 1 = n₀ + n₁ + n₂ − 1șin₁ + 2n₂, câte o muchie spre fiecare fiu. Rezultăn₀ = n₂ + 1. - Un arbore binar strict cu
ffrunze aref − 1noduri interne, deci2f − 1noduri, un număr impar.
Pentru arborele binar din desen, n₂ = 2 (nodurile 1 și 2) și n₀ = 3 (nodurile 4, 5, 7).
Reprezentarea arborilor binari
Arborele binar se reprezintă static prin:
- vectorii
stșidr—st[i]șidr[i]sunt etichetele fiului stâng și fiului drept ale noduluii, sau 0 dacă fiul lipsește; informația din nod se reține în vectorulinfo; - vectorii
tatașifiu—tata[i]este tatăl noduluii, iarfiu[i]este −1 dacăieste fiul stâng și 1 dacă este fiul drept; pentru rădăcină ambele valori sunt 0; - un singur vector, pentru arborii aproape compleți — nodurile se numerotează pe niveluri, de
la stânga la dreapta, începând cu 1; nodul de pe poziția
iare fiul stâng pe poziția2i, fiul drept pe poziția2i + 1și tatăl pe poziția[i/2].
Pentru arborele binar din desen:
Nodul i | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
|---|---|---|---|---|---|---|---|
st[i] | 2 | 4 | 0 | 0 | 0 | 7 | 0 |
dr[i] | 3 | 5 | 6 | 0 | 0 | 0 | 0 |
tata[i] | 0 | 1 | 1 | 2 | 2 | 3 | 6 |
fiu[i] | 0 | −1 | 1 | −1 | 1 | 1 | −1 |
Rădăcina este singurul nod care nu apare în vectorii st și dr, iar frunzele sunt nodurile cu
st[i] = dr[i] = 0. Implementarea dinamică, cu un câmp info și adresele celor doi subarbori,
este prezentată în subcapitolul despre structurile implementate dinamic.
Parcurgerea arborilor binari
Arborele binar se parcurge pe niveluri, ca orice arbore cu rădăcină, sau în adâncime. Parcurgerile în adâncime diferă prin momentul în care se prelucrează rădăcina; subarborele stâng se parcurge înaintea celui drept:
| Parcurgerea | Ordinea | Pentru arborele din desen |
|---|---|---|
| RSD (preordine) | rădăcina, subarborele stâng, subarborele drept | 1 2 4 5 3 6 7 |
| SRD (inordine) | subarborele stâng, rădăcina, subarborele drept | 4 2 5 1 3 7 6 |
| SDR (postordine) | subarborele stâng, subarborele drept, rădăcina | 4 5 2 7 6 3 1 |
| pe niveluri | nivelul 0, nivelul 1, … | 1 2 3 4 5 6 7 |
Parcurgerile se pot urmări pas cu pas, cu stiva apelurilor recursive, în simulatorul parcurgerilor de arbore binar🔒, pe același arbore.
Enunț. Se citește un arbore binar cu n noduri: pentru fiecare nod, informația, fiul stâng și
fiul drept. Să se afișeze rădăcina, parcurgerile RSD, SRD și SDR, înălțimea, numărul frunzelor și
dacă arborele este arbore binar de căutare.
#include <iostream>
using namespace std;
const int MAX = 101;
int info[MAX], st[MAX], dr[MAX], n, ultim, ordonat;
/* Pentru fiecare nod i se citesc informatia, fiul stang si fiul drept;
0 inseamna ca fiul lipseste. */
void citire() {
cin >> n;
for (int i = 1; i <= n; i++)
cin >> info[i] >> st[i] >> dr[i];
}
/* Radacina este singurul nod care nu este fiul niciunui nod. */
int radacina() {
int esteFiu[MAX] = {0};
for (int i = 1; i <= n; i++)
esteFiu[st[i]] = esteFiu[dr[i]] = 1;
for (int i = 1; i <= n; i++)
if (esteFiu[i] == 0)
return i;
return 0;
}
void Preordine(int i) {
cout << info[i] << " ";
if (st[i] != 0) Preordine(st[i]);
if (dr[i] != 0) Preordine(dr[i]);
}
void Inordine(int i) {
if (st[i] != 0) Inordine(st[i]);
cout << info[i] << " ";
if (dr[i] != 0) Inordine(dr[i]);
}
void Postordine(int i) {
if (st[i] != 0) Postordine(st[i]);
if (dr[i] != 0) Postordine(dr[i]);
cout << info[i] << " ";
}
/* Inaltimea subarborelui cu radacina i; subarborele vid are inaltimea -1. */
int inaltime(int i) {
if (i == 0)
return -1;
int hs = inaltime(st[i]), hd = inaltime(dr[i]);
return 1 + (hs > hd ? hs : hd);
}
int frunze(int i) {
if (i == 0)
return 0;
if (st[i] == 0 && dr[i] == 0)
return 1;
return frunze(st[i]) + frunze(dr[i]);
}
/* Parcurgerea SRD compara fiecare informatie cu cea afisata inainte:
arborele este de cautare daca sirul obtinut este strict crescator. */
void verificaCautare(int i) {
if (st[i] != 0) verificaCautare(st[i]);
if (ultim != 0 && info[i] <= info[ultim])
ordonat = 0;
ultim = i;
if (dr[i] != 0) verificaCautare(dr[i]);
}
int main() {
citire();
int r = radacina();
cout << "Radacina: " << info[r] << "\nRSD: ";
Preordine(r);
cout << "\nSRD: ";
Inordine(r);
cout << "\nSDR: ";
Postordine(r);
cout << "\nInaltimea: " << inaltime(r) << ", frunze: " << frunze(r) << "\n";
ordonat = 1;
verificaCautare(r);
cout << (ordonat ? "Este" : "Nu este") << " arbore binar de cautare\n";
return 0;
}
Materialul acesta se citește pe educamp.ro și nu se tipărește.
S-a încheiat minutul de citit liber.
Ce cuprinde subcapitolul
- Noțiuni preliminare
- Arborele cu rădăcină
- Arborele binar
- Arborele binar de căutare
- Ansamblul heap
- Probleme care se rezolvă cu arbori
- Arborii cu rădăcină și arborii binari la examen
- Greșeli frecvente
- Verificare rapidă
- Exerciții de fixare
Continuă lectura ca și cursant
Cel puțin un subcapitol din fiecare capitol este disponibil gratuit și integral. Pentru a citi toate celelalte subcapitole ale disciplinei, te înscrii la cursul de pregătire.
Te înscrii, alegi în cont când începe cursul și plătești prima lună prin transfer bancar. Pentru a vedea mai întâi cum este prezentată materia, poți reveni la primul subcapitol al capitolului.
Acces la suportul de curs
25 RON / lună, fără ședințe
Ce cuprinde:
- Acces la toate lecțiile disponibile
- Suport de curs pentru tematica științifică și metodică
- Exemple, explicații și exerciții
- Acces la materialele noi adăugate pe platformă
- Posibilitatea de a adresa întrebări
Poți alege și variante cu ședințe online, în grup sau individuale. Le vezi pe pagina cursului.
Ai întrebări sau o problemă? Scrie-mi pe WhatsApp, la 0745 874 576.
Surse (6)
- „Informatică. Manual pentru clasa a XI-a”, Editura Didactică și Pedagogică, București
- Oprescu, D., Bejan Ienulescu, L., „Informatică, varianta C++. Manual pentru clasa a XI-a”, Editura Niculescu, București, 2007
- Gheorghe, M. (coord.), Tătărâm, M., Achinca, C., Năstase, C., „Informatică. Manual pentru clasa a XI-a”, Editura Corint, București, 2008
- Programa pentru examenul de definitivare în învățământ, disciplina Informatică, cap. 5 „Teoria grafurilor”
- Programa pentru concursul de ocupare a posturilor didactice, disciplina Informatică, cap. 5 „Teoria grafurilor”
- Subiecte și bareme publicate, Definitivat și Titularizare, informatică, 2003–2026