Sari la conținut
EduCamp
Definitivat 2018 Teorie 15 puncte ✓ GRATUIT

Subiectul I, cerința 1

Arborii binari de căutare

Definiția arborelui binar de căutare, inserarea, căutarea, ștergerea și parcurgerea, descrise și exemplificate pe un arbore cu opt noduri, și o problemă rezolvată în C++ cu alocare dinamică.

Definiția

Fiecare nod al arborelui conține o cheie, adică o valoare din informația nodului care identifică nodul în mod unic.

Un arbore binar de căutare este un arbore binar care are următoarea proprietate: pentru orice nod, cheile din subarborele stâng sunt mai mici decât cheia nodului, iar cheile din subarborele drept sunt mai mari decât ea. Din definiție rezultă că toate cheile unui arbore binar de căutare sunt distincte.

Arborele folosit în exemple

Arborele alăturat are opt noduri și se obține prin inserarea, în această ordine, a cheilor 50 30 80 20 40 65 90 35. Rădăcina are cheia 50; subarborele ei stâng conține cheile 20, 30, 35 și 40, toate mai mici decât 50, iar subarborele drept conține cheile 65, 80 și 90, toate mai mari decât 50.

Nodurile se alocă dinamic. Un nod are un câmp pentru cheie și două câmpuri de legătură, cu adresele fiului stâng și fiului drept; un fiu care lipsește are adresa NULL.

struct nod {
    int cheie;
    nod *st, *dr;
};
Arbore binar de căutare cu rădăcina 50; 50 are fiii 30 și 80; 30 are fiii 20 și 40; 80 are fiii 65 și 90; 40 are fiul stâng 35 50 30 80 20 40 65 90 35
Arborele binar de căutare cu opt noduri.

Inserarea unei chei

Descrierea. Noul nod se adaugă întotdeauna ca nod terminal. Se pornește din rădăcină și se compară cheia de inserat cu cheia nodului curent: dacă este mai mică, se coboară în subarborele stâng, iar dacă este mai mare, în subarborele drept. Coborârea continuă până la un subarbore vid, în locul căruia se alocă noul nod. Dacă pe drum se întâlnește un nod cu aceeași cheie, inserarea nu se mai face, deoarece cheia există deja.

Parametrul r se transmite prin referință. Astfel, atribuirea r = new nod modifică chiar câmpul st sau dr al părintelui, iar noul nod rămâne legat de arbore.

void inserare(nod *&r, int k) {
    if (r == NULL) {
        r = new nod;
        r->cheie = k;
        r->st = NULL;
        r->dr = NULL;
    }
    else if (k < r->cheie)
        inserare(r->st, k);
    else if (k > r->cheie)
        inserare(r->dr, k);
}

Exemplu. Se inserează cheia 70:

  • 70 > 50, deci se coboară spre fiul drept, 80;
  • 70 < 80, deci se coboară spre fiul stâng, 65;
  • 70 > 65, iar fiul drept al lui 65 lipsește.

Nodul cu cheia 70 se alocă și devine fiul drept al lui 65. Arborele are acum nouă noduri.

Arborele după inserarea cheii 70: drumul parcurs de la rădăcină este 50, 80, 65, iar 70 devine fiul drept al lui 65 50 30 80 20 40 65 90 35 70
Drumul parcurs la inserarea cheii 70.

Căutarea unei chei

Descrierea. Se pornește din rădăcină. Cât timp nodul curent există și cheia lui este diferită de cheia căutată, se trece în subarborele stâng, dacă cheia căutată este mai mică, sau în subarborele drept, dacă este mai mare. Căutarea se oprește la nodul care conține cheia sau la un subarbore vid; în al doilea caz, cheia nu există în arbore.

După fiecare comparație se continuă într-un singur subarbore, iar celălalt nu mai este cercetat.

nod *cautare(nod *r, int k) {
    while (r != NULL && r->cheie != k)
        if (k < r->cheie)
            r = r->st;
        else
            r = r->dr;
    return r;
}

Exemplu, pe arborele cu nouă noduri, obținut după inserarea cheii 70:

  • cheia 35: 35 < 50, se trece la 30; 35 > 30, se trece la 40; 35 < 40, se trece la 35. Cheia este găsită după patru comparații, iar funcția întoarce adresa nodului;
  • cheia 68: 68 > 50, se trece la 80; 68 < 80, se trece la 65; 68 > 65, se trece la 70; 68 < 70, iar fiul stâng al lui 70 lipsește. Funcția întoarce NULL, deci cheia 68 nu există în arbore.

Ștergerea unei chei

Descrierea. Se caută nodul care conține cheia. Modul de ștergere depinde de numărul fiilor acestui nod:

  • nodul nu are fii: legătura părintelui către el primește valoarea NULL, iar memoria nodului se eliberează;
  • nodul are un singur fiu: legătura părintelui către el primește adresa acestui fiu, iar memoria nodului se eliberează;
  • nodul are doi fii: se determină cea mai mare cheie din subarborele stâng, care se află în nodul la care se ajunge trecând o dată la fiul stâng și apoi numai la fiii drepți. Cheia acestui nod se copiază în nodul de șters, apoi nodul găsit se șterge din subarborele stâng. Nodul găsit nu are fiu drept, deci ștergerea lui se face ca în unul dintre primele două cazuri.

În al treilea caz, cheia copiată este mai mare decât toate celelalte chei din subarborele stâng și mai mică decât toate cheile din subarborele drept, deci arborele rămâne arbore binar de căutare.

Exemplu. Pe arborele cu nouă noduri se șterg, una după alta, cheile 20, 40 și 50:

  1. Nodul 20 nu are fii: legătura st a lui 30 primește valoarea NULL.
  2. Nodul 40 are numai fiul stâng, 35: legătura dr a lui 30 primește adresa nodului 35.
  3. Nodul 50 are doi fii. În subarborele stâng se trece la 30, apoi la fiul drept al acestuia, 35, care nu are fiu drept, deci 35 este cea mai mare cheie din subarborele stâng. Cheia 35 se copiază în rădăcină, iar nodul terminal 35 se șterge.

Arborele obținut are rădăcina 35, cu fiii 30 și 80.

void stergere(nod *&r, int k) {
    nod *q;
    if (r == NULL)
        return;
    if (k < r->cheie)
        stergere(r->st, k);
    else if (k > r->cheie)
        stergere(r->dr, k);
    else if (r->st == NULL) {
        q = r;
        r = r->dr;
        delete q;
    }
    else if (r->dr == NULL) {
        q = r;
        r = r->st;
        delete q;
    }
    else {
        q = r->st;
        while (q->dr != NULL)
            q = q->dr;
        r->cheie = q->cheie;
        stergere(r->st, q->cheie);
    }
}
Arborele după ștergerea cheilor 20, 40 și 50: rădăcina 35 are fiii 30 și 80; 80 are fiii 65 și 90; 65 are fiul drept 70 35 30 80 65 90 70
Arborele după ștergerea cheilor 20, 40 și 50.

În subprogram, cazul nodului fără fii este tratat pe ramura r->st == NULL: fiul drept este și el NULL, deci legătura părintelui primește valoarea NULL.

Parcurgerea arborelui

Descrierea. Arborele binar de căutare se parcurge cu algoritmii de parcurgere în adâncime ai arborilor binari. Ei se deosebesc prin momentul în care se prelucrează rădăcina fiecărui subarbore:

  • RSD (preordine): rădăcina, subarborele stâng, subarborele drept;
  • SRD (inordine): subarborele stâng, rădăcina, subarborele drept;
  • SDR (postordine): subarborele stâng, subarborele drept, rădăcina.

Într-un arbore binar de căutare, parcurgerea SRD furnizează cheile în ordine crescătoare, deoarece cheile din subarborele stâng al oricărui nod se afișează înaintea cheii nodului, iar cele din subarborele drept după ea.

void srd(nod *r) {
    if (r != NULL) {
        srd(r->st);
        cout << r->cheie << " ";
        srd(r->dr);
    }
}

Exemplu, pe arborele cu nouă noduri, obținut după inserarea cheii 70:

ParcurgereaOrdinea cheilor
RSD50 30 20 40 35 80 65 70 90
SRD20 30 35 40 50 65 70 80 90
SDR20 35 40 30 70 65 90 80 50

Problema rezolvată

Enunț. O bibliotecă identifică fiecare carte printr-un cod, număr natural nenul. Se citesc de la tastatură un număr natural n (n ≤ 1000), apoi n numere naturale distincte, reprezentând codurile cărților aflate în bibliotecă. Se citesc apoi un număr natural m (m ≤ 1000) și m coduri de cărți cerute de cititori. Pentru fiecare cod cerut, dacă în bibliotecă se află o carte cu acest cod, ea se împrumută, adică este eliminată din evidență, și se afișează mesajul DA; altfel se afișează mesajul NU. Mesajele se afișează pe câte un rând. La final se afișează, pe un rând, în ordine crescătoare, codurile cărților rămase în bibliotecă, separate prin câte un spațiu.

Exemplu: pentru n = 7, codurile 50 30 70 20 40 60 80, m = 3 și codurile cerute 30 65 50, se afișează:

DA
NU
DA
20 40 60 70 80

Descrierea soluției. Codurile cărților se memorează ca chei într-un arbore binar de căutare alocat dinamic. Arborele se construiește pornind de la arborele vid, prin inserarea, pe rând, a celor n coduri citite.

Pentru fiecare cod cerut, se caută în arbore nodul care îl conține. Dacă funcția de căutare întoarce o adresă diferită de NULL, cartea există: nodul se șterge din arbore și se afișează DA. Dacă funcția întoarce NULL, se afișează NU.

După prelucrarea tuturor cererilor, arborele se parcurge în inordine, iar cheile se afișează în ordine crescătoare, fără o sortare separată.

Implementarea soluției.

#include <iostream>
using namespace std;

struct nod {
    int cheie;
    nod *st, *dr;
};

void inserare(nod *&r, int k) {
    if (r == NULL) {
        r = new nod;
        r->cheie = k;
        r->st = NULL;
        r->dr = NULL;
    }
    else if (k < r->cheie)
        inserare(r->st, k);
    else if (k > r->cheie)
        inserare(r->dr, k);
}

nod *cautare(nod *r, int k) {
    while (r != NULL && r->cheie != k)
        if (k < r->cheie)
            r = r->st;
        else
            r = r->dr;
    return r;
}

void stergere(nod *&r, int k) {
    nod *q;
    if (r == NULL)
        return;
    if (k < r->cheie)
        stergere(r->st, k);
    else if (k > r->cheie)
        stergere(r->dr, k);
    else if (r->st == NULL) {
        q = r;
        r = r->dr;
        delete q;
    }
    else if (r->dr == NULL) {
        q = r;
        r = r->st;
        delete q;
    }
    else {
        q = r->st;
        while (q->dr != NULL)
            q = q->dr;
        r->cheie = q->cheie;
        stergere(r->st, q->cheie);
    }
}

void srd(nod *r) {
    if (r != NULL) {
        srd(r->st);
        cout << r->cheie << " ";
        srd(r->dr);
    }
}

int main() {
    nod *rad = NULL;
    int n, m, i, cod;

    cin >> n;
    for (i = 1; i <= n; i++) {
        cin >> cod;
        inserare(rad, cod);
    }

    cin >> m;
    for (i = 1; i <= m; i++) {
        cin >> cod;
        if (cautare(rad, cod) != NULL) {
            stergere(rad, cod);
            cout << "DA\n";
        }
        else
            cout << "NU\n";
    }

    srd(rad);
    return 0;
}

Verificarea pe exemplul din enunț. După inserarea codurilor, rădăcina are cheia 50, cu fiii 30 și 70; 30 are fiii 20 și 40, iar 70 are fiii 60 și 80.

Codul cerutRezultatul căutăriiOperațiaMesajul
30găsitnodul are doi fii: cheia 20, cea mai mare din subarborele stâng, se copiază în nod, iar nodul terminal 20 se ștergeDA
65negăsit: 65 > 50, 65 < 70, 65 > 60, iar fiul drept al lui 60 lipseșteNU
50găsitnodul are doi fii: în subarborele stâng se trece la 20, apoi la fiul lui drept, 40, care nu are fiu drept; cheia 40 se copiază în rădăcină, iar nodul terminal 40 se ștergeDA

Parcurgerea SRD a arborelui rămas afișează 20 40 60 70 80.

Rezolvarea se poate citi pe educamp.ro, la adresa de mai sus.

Actualizat: 15 septembrie 2026