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:
- un arbore este un graf neorientat conex și fără cicluri; un arbore cu
nnoduri aren − 1muchii; - între oricare două noduri ale unui arbore există un singur lanț elementar;
- un nod terminal are gradul 1.
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
Un arbore cu rădăcină este un arbore în care s-a ales un nod, numit rădăcină. Î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:
- ordinul unui nod este numărul fiilor lui; nodul 2 are ordinul 2, nodul 3 ordinul 1;
- nivelul unui nod este numărul muchiilor de pe lanțul de la rădăcină la el; rădăcina este pe nivelul 0, iar nodul 7 pe nivelul 3;
- înălțimea arborelui este cel mai mare nivel al unui nod; arborele din desen are înălțimea 3;
- subarborele unui nod este format din nodul respectiv și din toți descendenții lui; subarborele nodului 2 are nodurile 2, 4, 5 și 7.
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:
- reprezentarea cu referințe ascendente (vectorul de tați): pentru fiecare nod
ise reține înt[i]eticheta tatălui lui; pentru rădăcinăt[i] = 0; - reprezentarea cu referințe descendente: pentru fiecare nod se rețin fiii lui, de exemplu într-o listă a fiilor.
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 urcă spre rădăcină, iar listele de fii pentru cele care coboară spre frunze. Manualul EDP folosește denumirile invers: numește vectorul tată „implementare prin referințe descendente”. Programa școlară din 2009, citată în subiectul de Titularizare 2026, și rezolvarea de pe site folosesc 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, urcând
din tată în tată, apoi se urcă din y 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 urcand 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 urca din y
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 urcă din nod în nod folosind vectorul 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 urcă din y. 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
Un arbore binar este un arbore cu rădăcină ale cărui noduri au fiecare cel mult doi fii: fiul stâng și fiul drept. 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:
- arborele binar strict — fiecare nod are 0 sau 2 fii;
- arborele binar complet — este strict și are toate frunzele pe același nivel; fiecare nivel
k, până la ultimul, are2ᵏnoduri; - arborele binar aproape complet — toate nivelurile, cu excepția ultimului, sunt complete, iar nodurile de pe ultimul nivel sunt așezate cât mai la stânga;
- arborele binar degenerat — fiecare nod are cel mult un fiu, iar înălțimea este
n − 1.
Manualele nu folosesc aceleași denumiri. Manualul EDP definește tipurile ca mai sus. Manualul Niculescu numește „arbore binar complet” arborele în care fiecare nod intern are exact doi fii, adică 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.
Prima săptămână este gratuită, fără plată și fără card. Dacă vrei să vezi mai întâi cum este prezentată materia, poți reveni la primul subcapitol al capitolului.
100 RON / lună, pentru o disciplină
Ce cuprinde:
- Două întâlniri de câte două ore, în fiecare lună
- Tot suportul de curs publicat până acum la disciplina aleasă
- Capitole noi în fiecare săptămână, cuprinse în luna plătită, fără costuri suplimentare
- Material organizat după structura programei de examen
- Acces de pe orice dispozitiv, folosind același cont
- Prima săptămână gratuită, fără card și fără reînnoire automată
Începe săptămâna gratuită Sunt cursant — login
Află când publicăm materiale noi
Materia este publicată treptat, capitol cu capitol. Înscrie-te pentru a primi un e-mail atunci când apare un capitol nou de informatică.
Nu am putut înregistra adresa. Verifică e-mailul și materia aleasă, apoi încearcă din nou.
Surse
- „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