Sari la conținut
EduCamp

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.
Arborele cu rădăcina 1 și 7 noduri, așezat pe niveluri: nodul 1 are fiii 2 și 3, nodul 2 are fiii 4 și 5, nodul 3 are fiul 6, nodul 5 are fiul 7 nivelul 0 nivelul 1 nivelul 2 nivelul 3 1 2 3 4 5 6 7
Arbore cu rădăcina 1

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.

Același arbore, cu rădăcina 5: nodul 5 are fiii 2 și 7, nodul 2 are fiii 1 și 4, nodul 1 are fiul 3, nodul 3 are fiul 6; înălțimea este 4 5 2 7 1 4 3 6
Același arbore, cu rădăcina 5

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 i1234567
t[i] (referințe ascendente)0112235
fiii (referințe descendente)2, 34, 56—7——

Din vectorul de tați se obțin:

  • rădăcina — nodul i cu t[i] = 0;
  • tatăl nodului x — valoarea t[x], într-un singur pas;
  • fiii nodului x — nodurile i cu t[i] = x, printr-o parcurgere a vectorului;
  • frunzele — nodurile care nu apar ca valori în vectorul t;
  • frații nodului x — nodurile i ≠ x cu t[i] = t[x];
  • nivelul nodului x — numărul de pași x ← 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.

Drumurile de la nodurile 4 și 7 spre rădăcină se întâlnesc prima dată în nodul 2, primul strămoș comun 1 2 3 4 5 6 7
Primul strămoș comun al nodurilor 4 și 7
#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ăcinaVectorul de tațiPreordinePostordinePe niveluri
10 1 1 2 2 3 51 2 4 5 7 3 64 7 5 2 6 3 11 2 3 4 5 6 7
52 5 1 2 0 3 55 2 1 3 6 4 76 3 1 4 2 7 55 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.

Arbore binar cu 7 noduri: 1 are fiul stâng 2 și fiul drept 3; 2 are fiii 4 și 5; 3 are numai fiul drept 6; 6 are numai fiul stâng 7 1 2 3 4 5 6 7
Arbore binar cu 7 noduri

Tipuri speciale de arbori binari

Se definesc următoarele tipuri de arbori binari:

Arbore binar strict: fiecare nod are 0 sau 2 fii 1 2 3 4 5
Strict
Arbore binar complet: strict, cu toate frunzele pe nivelul 2 1 2 3 4 5 6 7
Complet
Arbore binar aproape complet: nivelurile 0 și 1 sunt complete, iar ultimul nivel este completat de la stânga 1 2 3 4 5 6
Aproape complet
Arbore binar degenerat: fiecare nod are cel mult un fiu, iar înălțimea este n − 1 1 2 3 4
Degenerat

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 k al unui arbore binar se află cel mult 2ᵏ noduri, fiindcă fiecare nod de pe nivelul k − 1 are cel mult doi fii.
  • Un arbore binar cu înălțimea h are cel mult 1 + 2 + … + 2ʰ = 2ʰ⁺¹ − 1 noduri. Prin urmare, un arbore binar cu n noduri are înălțimea cel puțin [log₂ n] și cel mult n − 1.
  • Un arbore binar complet cu înălțimea h are 2ʰ frunze și 2ʰ⁺¹ − 1 noduri.
  • Î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 și n₁ + 2n₂, câte o muchie spre fiecare fiu. Rezultă n₀ = n₂ + 1.
  • Un arbore binar strict cu f frunze are f − 1 noduri interne, deci 2f − 1 noduri, 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 și dr — st[i] și dr[i] sunt etichetele fiului stâng și fiului drept ale nodului i, sau 0 dacă fiul lipsește; informația din nod se reține în vectorul info;
  • vectorii tata și fiu — tata[i] este tatăl nodului i, iar fiu[i] este −1 dacă i este 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 i are fiul stâng pe poziția 2i, fiul drept pe poziția 2i + 1 și tatăl pe poziția [i/2].

Pentru arborele binar din desen:

Nodul i1234567
st[i]2400070
dr[i]3560000
tata[i]0112236
fiu[i]0−11−111−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:

ParcurgereaOrdineaPentru arborele din desen
RSD (preordine)rădăcina, subarborele stâng, subarborele drept1 2 4 5 3 6 7
SRD (inordine)subarborele stâng, rădăcina, subarborele drept4 2 5 1 3 7 6
SDR (postordine)subarborele stâng, subarborele drept, rădăcina4 5 2 7 6 3 1
pe nivelurinivelul 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.

Ce cuprinde subcapitolul

  1. Noțiuni preliminare
  2. Arborele cu rădăcină
  3. Arborele binar
  4. Arborele binar de căutare
  5. Ansamblul heap
  6. Probleme care se rezolvă cu arbori
  7. Arborii cu rădăcină și arborii binari la examen
  8. Greșeli frecvente
  9. Verificare rapidă
  10. 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.

Înscrie-te Alte varianteCum plătești Login

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
Actualizat: 20 octombrie 2026