Sunteți pe pagina 1din 14

CAPITOLUL 4.

Arbori binari de căutare

4.1 Definiţia unui arbore binar de căutare

Arborii binari au fost descoperiţi în mod independent de mai mulţi


autori la sfârşitul anii 50. O primă prezentare pertinentă a acestora care
a constituit referinţa pentru foarte multe lucrări ulterioare se găseşte în
[14] .
Un arbore binar de căutare este un arbore binar în care avem
îndeplinite următoarele proprietăţi pentru fiecare nod:
 cheia din nodul părinte este strict mai mare decât orice cheie din
subarborele stâng subordonat nodului respectiv (când acest
subarbore există);
 cheia din nodul părinte este strict mai mică decât orice cheie din
subarborele drept subordonat nodului respectiv (când acest
subarbore există).
Aceste proprietăţi formează “criteriul de integritate” al unui arbore binar
de căutare. Din ele rezultă că într-un arbore binar valorile sunt
distincte.

Aşa cum rezultă şi din denumire, rolul acestor structuri de date


este de a asigura căutarea rapidă a informaţiilor. Operaţiunile
fundamentale pe care le vom implementa pentru arborii binari sunt
inserarea unei chei într-un nod frunză, eliminarea unei chei şi căutarea
unei chei. Trebuie menţionat că în implementările pe care le vom
prezenta în continuare, inserarea şi eliminarea vor include şi o etapă în
care se efectuează paşii specifici căutării.
Crearea unei astfel de structuri putem să o realizăm prin
apelarea în mod repetat a inserării.

69
4.2 Operaţii fundamentale în arborii binari de căutare

4.2.1. Inserarea unei chei într-un arbore binar de căutare

Această operaţiune se face într-un mod foarte asemănător celui


descris în subcapitolul 3.3.C. Diferenţa faţă de funcţia de inserare
prezentată acolo este că acum există un criteriu foarte clar care ne
spune în ce direcţie urmează să apelăm recursiv funcţia de inserare:
 dacă cheia pe care urmează să o inserăm este strict mai mică
decât cheia nodului curent, apelul se va face pentru subarborele
stâng;
 dacă cheia pe care urmează să o inserăm este mai mare decât
cheia nodului curent, apelul se va face pentru subarborele drept;
 dacă cheia pe care urmează să o inserăm este egală cu cheia
nodului curent, nu se va face nicio inserare (având în vedere
definiţia arborilor binari de căutare).
Vom ilustra acest algoritm de inserare în figura 4.1. Vom presupune
că a fost creat un arbore binar de căutare cu şapte noduri care reţin
chei formate din câte un număr natural. Acest arbore este ilustrat în
prima dintre figurile următoare. Vom urmări paşii algoritmului care
inserează în arbore o cheie cu valoarea 30.

Valoarea 30 este comparată cu


21 <
30 valoarea din rădăcină. Cum
21<30, se va apela inserarea
18 41 valorii 30 în subarborele drept
subordonat rădăcinii.
7 32
25 37

70
Valoarea 30 este comparată cu
21 41. Cum 41>30, valoarea 30 va fi
inserată în subarborele stâng
>> subordonat nodului care reţine
18 41 30
valoarea 41.
7 32
25 37

Valoarea 30 este comparată cu


21 32. 32>30, deci valoarea 30 va fi
inserată în subarborele stâng
18 41 subordonat nodului care reţine
valoarea 32.
32 >
7 30
25 37

Valoarea 30 este comparată cu


21 25. 25>30, deci valoarea 30 va fi
inserată în subarborele drept
18 41 subordonat nodului care reţine
valoarea 25. Cum acest
subarbore nu există, rezultă că
7 32
aceasta este poziţia unde va fi
> inserat un nod de tip frunză care
30 25 37
va reţine valoarea 30.

Arborele rezultat în urma inserării


21 valorii 30.

18 41

7 32
25 37
30
Fig. 4.1 Ilustrarea algoritmului de inserare a unei chei într-un arbore
binar de căutare

Prezentăm în continuare funcţia care implementează acest algoritm.


#include <iostream>
71
#define GENERIC template <class T>
using namespace std;
GENERIC struct nod
{ T info;
nod * st, * dr;};
GENERIC void inserare( nod <T>*&nodcrt, T cheie)
{ if (nodcrt == NULL)
{ nodcrt = new nod<T>;
nodcrt->st= nodcrt->dr= NULL;
nodcrt->info = cheie;
}
else
if (nodcrt->info < cheie)
inserare(nodcrt->dr,cheie);
else
if (nodcrt->info > cheie)
inserare(nodcrt->st,cheie);
else cout<<cheie<<" apare deja in arbore\n";
}
Pentru a testa corectitudinea funcţiei de inserare în main() sunt utile
următoarele funcţii:
GENERIC int inaltime(nod<T>*nodcrt)
{ if (nodcrt == NULL) return -1;
else
{ int rezst = inaltime(nodcrt->st);
int rezdr = inaltime(nodcrt->dr);
return 1 + max(rezst,rezdr); }
}
GENERIC void preordine(nod<T> *nodcrt)
{ if (nodcrt != NULL)
{ cout << nodcrt->info << ' ';
preordine(nodcrt->st);
preordine(nodcrt->dr); }
}
GENERIC void inordine(nod<T> *nodcrt)
{ if (nodcrt != NULL)
{ inordine(nodcrt->st);
cout << nodcrt->info<< ' ';
inordine(nodcrt->dr);
}
}
O funcţie main() care utilizează această funcţie pentru a crea un
arbore binar de căutare este următoarea:

int main() //program 4.1


72
{nod<int> * radacina = NULL;
ifstream fin("valori.in");
int x;
while (fin >> x)
inserare(radacina,x);
cout<<"inaltimea arb.="<<inaltime(radacina);
cout << "\ntraversare in inordine\n";
inordine(radacina);
cout << "\ntraversare in preordine\n";
preordine(radacina);
return 0;
}

Dacă fişierul valori.in conţine următorul şir de numere


21, 18, 41, 32, 37, 7, 18, 25, 30,
programul va afişa în fereastra consolă următoarele:
18 apare deja in arbore
inaltimea arb.=4
traversare in inordine
7 18 21 25 30 32 37 41
traversare in preordine
21 18 7 41 32 25 30 37

Valorile din fişierul de intrare au fost astfel alese încât programul să


construiască arborele de căutare reprezentat în figura 4.1. Remarcăm
că traversarea în inordine determină afişarea valorilor în ordine
crescătoare.

4.2.2. Căutarea unei chei

Algoritmul de căutare a unei chei într-un arbore binar de căutare este


asemănător algoritmului de căutare binară. Testele care se fac şi
operaţiunile determinate de aceste teste sunt următoarele:
 dacă arborele în care trebuie să căutăm cheia este vid este
evident că valoarea căutată nu se află în respectivul arbore;
 dacă arborele nu este vid, comparăm cheia pe care o căutăm cu
cheia memorată în nodul rădăcină. Sunt trei posibilităţi:
o cheia căutată este identică cu cheia reţinută de nodul
rădăcină; în acest caz căutarea se încheie cu succes;
o cheia căutată este mai mare decât cheia reţinută de
nodul rădăcină; în acest caz se caută cheia în
subarborele drept;

73
o cheia căutată este mai mică decât cheia reţinută de nodul
rădăcină; în acest caz se caută cheia în subarborele
stâng.
Următoarea funcţie implementează acest algoritm. Ea are doi
parametri: adresa nodului rădăcină arborelui în care se face căutarea şi
cheia căutată. Funcţia va returna adresa nodului care reţine cheia
căutată sau valoarea NULL în cazul în care cheia căutată nu apare în
arbore. Declaraţiile pentru tipurile folosite sunt cele din programul 4.1.

template <class T>


nod<T>* cauta(nod<T> *nodcrt, T cheie)
{
if (nodcrt == NULL)
return NULL;
else
if (nodcrt->info == cheie)
return nodcrt;
else
if (nodcrt->info < cheie)
return cauta(nodcrt->dr, cheie);
else
return cauta(nodcrt->st, cheie);
}

4.2.3. Eliminarea unei chei dintr-un arbore binar de căutare

Algoritmul de eliminare a unei chei dintr-un arbore binar de


căutare are două etape:
i. se caută nodul care reţine respectiva cheie. Evident dacă cheia
nu apare în arbore, nu se face nicio eliminare.
ii. se face eliminare propriu-zisă. Sunt trei cazuri în care se poate
afla nodul care urmează să fie eliminat pe care le ilustrăm în
figurile următoare:

1. Nodul care trebuie eliminat este frunză. În acest caz, se


elimină nodul respectiv iar în nodul părinte al acestuia se
marchează legătura cu NULL

74
21 21

18 41 18 41

7 32 7 32
25 37 37
39 39

Fig. 4.2 Eliminarea unui nod frunză dintr-un arbore binar

2. Nodul care trebuie eliminat are un singur subarbore


subordonat. În acest caz, se elimină nodul respectiv iar în
câmpul st sau dr din nodul părinte se face legătura cu
subarborele pe care îl are nodul care urmează să fie
eliminat.

21 21

18 41 18 32

7 32 7 25 37

25 37 39
39

Fig. 4.3 Eliminarea unui nod care are un singur subarbore subordonat
dintr-un arbore binar

3. Nodul care trebuie eliminat are ambii subarbori subordonaţi.


În acest caz valoarea cheii din nodul respectiv este înlocuită
cu valoarea unei chei astfel încât să nu se strice criteriul de
integritate şi se şterge nodul în care se afla respectiva cheie.
Cheile care pot înlocui cheia care urmează să dispară din
arbore sunt
- cea mai mică cheie din subarborele drept subordonat
nodului respectiv;
- cea mai mare cheie din subarborele stâng subordonat
nodului respecitv.
Un raţionament simplu ne permite să demonstrăm că nodurilor
care reţin aceste chei le lipseşte cel puţin unul dintre subarbori
(de ex. nodul cu cheia cea mai mică din subarborele drept cu
75
siguranţă nu are subarborele stâng – în caz contrar ar exista
chei mai mici!).

21 25

18 41 18 41

7 32 7 32

25 37 37
39 39

Fig. 4.4 Eliminarea unui nod care are ambii subarbori prin înlocuirea
cheii acestuia cu cea mai mică cheie din subarborele drept.

Algoritmul de eliminare dintr-un arbore binar de căutare


presupune operaţiunea de determinare a unei chei minime sau maxime
dintr-un subarbore. Din acest motiv vom prezenta o funcţie care
determină cheia minimă dintr-un subarbore. Algoritmul e foarte simplu:
se parcurge subarborele plecând de la rădăcină tot timpul spre stânga
până se ajunge la un nod care nu are subarbore stâng. Cheia reţinută
de acesta este cheia minimă din subarbore.
template <class T> T minimarb(nod<T> * nodcrt)
{
while (nodcrt->st != NULL)
nodcrt = nodcrt->st;
return nodcrt->info;
}

Funcţia de eliminare care implementează algoritmul descris este listată


în continuare:
template <class T>
void eliminarearb(nod<T> *&nodcrt, T cheie)
{ if (nodcrt==NULL)
{ cout << "valoarea nu este in arbore\n";
return;
}
if (nodcrt->info > cheie)
eliminarearb(nodcrt->st,cheie);
else
if (nodcrt->info < cheie)
eliminarearb(nodcrt->dr,cheie);
else //cheia apare in nodul curent
76
{ //cazul 1: frunza
if (nodcrt->st==NULL && nodcrt->dr==NULL)
{ delete nodcrt;
nodcrt = NULL;
return;
}
//cazul 2: lipseste subarborele stang
if (nodcrt->st==NULL && nodcrt->dr!=NULL)
{ nod<T> *aux = nodcrt->dr;
delete nodcrt;
nodcrt = aux;
return;
}
//cazul 2: lipseste subarborele drept
if (nodcrt->st!=NULL && nodcrt->dr==NULL)
{ nod<T> *aux = nodcrt->st;
delete nodcrt;
nodcrt = aux;
return;
}
//cazul 3: ambii subarbori prezenti
T auxcheie = minimarb(nodcrt->dr);
eliminarearb(nodcrt->dr,auxcheie);
nodcrt->info = auxcheie;
}
}
Dacă adăugăm aceste două funcţii la programul 4.1 iar în
main() următoarele instrucţiuni:

eliminarearb(radacina, radacina->info);
cout << "\ntraversare in inordine\n";
inordine(radacina);
cout << "\ntraversare in preordine\n";
preordine(radacina);
în fereastra consolă vor apare şi următoarele informaţii care
demonstrează eliminarea cheii din rădăcină (eliminarea valorii 21):

traversare in inordine
7 18 25 30 32 37 41
traversare in preordine
25 18 7 41 32 30 37

În ceea ce priveşte complexitatea celor trei operaţiuni


fundamentale (inserare, căutare, eliminare) efectuate în arborii binari
de căutare, aceasta este dat de înălţimea maximă a arborelui.
77
Deoarece în cazuri extreme (de exemplu când şirul de valori care sunt
inserate este sortat), arborii construiţi prin funcţia de inserare
prezentată pot fi degeneraţi, deci au înălţimea egală cu n-1 (unde n
este numărul de noduri din arbore), toate cele trei operaţiuni se
încadrează în categoria O(n).

78
4.3 Determinarea succesorului unei chei dintr-un arbore
binar de căutare

În acest subcapitol ne propunem să rezolvăm următoarea


problemă: se ştie că un arbore binar de căutare reţine un set de chei.
Se dă o cheie din acest set şi se cere să se determine cheia care
reprezintă succesorul cheii date (adică cheia care ar urma după cheia
dată, dacă şirul cheilor ar fi sortat).
Aceasta este o aplicaţie interesantă care ilustrează avantajele
dispunerii informaţiilor într-un arbore binar de căutare.
O primă observaţie este că enunţul garantează existenţa cheii al
cărei succesor în căutăm în arbore.
Algoritmul pe care îl vom prezenta în continuare are la bază
algoritmul de căutare a unei chei în arbore. Pentru o mai bună
înţelegere, vom exemplifica cazurile care apar pentru graful din figura
4.4. În momentul în care prin apeluri recursive am ajuns la nodul care
reţine cheia dată, apar două posibilităţi:
 nodul respectiv are subarborele drept. Atunci succesorul cheii
este cheia minimă din acest subarbore; de exemplu, dacă ne
interesează succesorul cheii 32 din arborele din figura 4.4,
deoarece nodul care reţine valoarea 32 are subarbore drept,
succesorul lui 32 este valoarea minimă care apare în acest
subarbore, deci 34.
 nodul respectiv nu are subarborele drept. În acest caz,
parcurgem în sens invers drumul pe care am ajuns la nodul care
conţine cheia (aceasta se realizează foarte uşor prin însuşi
mecanismul de revenire dintr-un apel recursive – evident dacă
folosim un algoritm recursiv de căutare) şi primul nod din acest
drum invers care are proprietatea că de la el ne-am deplasat pe
drumul direct prin apel recursiv către stânga este cel care reţine
cheia pe care o căutăm (fiind apel către stânga rezultă că el
reţine o cheie mai mare decât cheia dată!). Dacă nu există
niciun astfel de nod, cheia dată iniţial nu are succesor (este deci
cheia de valoare maximă din set). Să exemplificăm pe arborele
din figura 4.4: căutăm succesorul cheii 39: nodul care reţine
valoarea 39 nu are subarbore drept. Calea pe care am ajuns la
acest nod este formată din cheile care reţin valorile 21, 41, 32,
şi 37 Parcurgem calea pe care am ajuns la acest nod prin
algoritmul de căutare a cheii 39; primul nod în care am apelat
funcţia de căutare spre stânga este cel care reţine valoarea 41.
Acesta este succesorul valorii 39! Din nodurile care reţin valorile
21, 32 şi 37 am făcut apelul funcţiei de căutare spre dreapta
79
(aceasta deoarece cheile din aceste noduri sunt mai mici decât
cheia 39 şi deci evident succesorul lui 39 nu se află în aceste
noduri). Deci cheia 41 este succesorul cheii 39. Dacă aplicăm
algoritmul pentru determinare succesorului cheii 25: nodul care
reţine această cheie nu are subarbore drept; drumul urmat de la
rădăcină la respectivul nod este format din nodurile cu cheile 21,
41, 32, 25. Mergând în sens invers pe acest drum, primul nod
în care am făcut apel recursiv spre stânga este cel care reţine
cheia 32. Acesta este succesorul cehii 25! Se observă că am
făcut apel recursiv spre stânga şi din nodul cu cheia 41, dar
acest apel a fost făcut înaintea apelului făcut din nodul cu cheia
32 deoarece 41>32. Deci cheia 32 e între 41 şi 25, deci ea
reprezintă succesorul cheii 25.

21

18 41

7 32 48
25 37
34 39

Fig. 4.5 Arbore binar de căutare pentru ilustrarea cazurilor


care apar în algoritmul de determinare a succesorului unei
chei
Funcţia care implementează acest algoritm este listată în continuare.
Se remarcă cele trei cazuri specifice algoritmului de căutare:
 cheia din nodul curent egală cu cheia dată: se testează dacă
există subarbore drept;
o dacă da, se returnează minimul din acel subarbore
o dacă nu, se returnează o valoare care să permită
identificarea acestei situaţii (s-a ales chiar valoarea cheii
pentru care se caută succesorul);
 cheia din nodul curent este mai mică decât cheia dată; în acest
caz atât cheia dată cât şi succesorul ei sunt în subarborele
drept, deci rezultatul va fi cel returnat de funcţie la apelul pentru
respectivul subarbore;
 cheia din nodul curent este mai mare decât cheia dată. În acest
caz, se apelează funcţia pentru subarborele stâng al nodului
curent şi:

80
o dacă valoarea returnată este chiar valoarea cheii căutate,
atunci valoarea din nodul curent succesorul;
o dacă valoarea este mai mare decât cheia căutată, ea
reprezintă soluţia problemei.
Dacă valoarea returnată de această funcţie în main() este valoarea
cheii date, atunci cheia dată este cea maximă şi deci nu are succesor.

GENERIC T var2(nod<T> * nodcrt, T cheie)


{ if (nodcrt != NULL)
{ if (nodcrt->info == cheie)
if (nodcrt->dr != NULL)
return minimarb(nodcrt->dr);
else
return cheie;
if (nodcrt->info > cheie)
{ T rez = var2(nodcrt->st, cheie);
if (rez == cheie)
return nodcrt->info;
else
return rez;
}
if (nodcrt->info < cheie)
return var2(nodcrt->dr, cheie);
}
}

81
4.4 Probleme propuse

Problema 1. Într-un arbore binar de căutare sunt inserate conform


algoritmului prezentat în acest capitol următoarele valori:
24, 40, 10, 5, 20, 15, 14, 16, 50, 80, 60, 55, 75,
95, 90, 100, 17, 6, 92
Din arborele binar de căutare rezultat sunt eliminate în această ordine
valorile 24 şi 60 cu algoritmul prezentat în acest capitol.
a. Scrieţi care este valoarea din nodul rădăcină în urma efectuării
acestor operaţii;
b. Scrieţi care sunt valorile din nodurile aflate pe nivelul 2 în urma
efectuării acestor operaţii. (Atenţie: rădăcina arborelui este pe
nivelul 0).
Problema 2. Fişierul valori.in conţine pe prima linie un număr
natural n (2≤n≤1000) iar pe a doua linie n numere naturale
reprezentând valorile afişate la traversarea în preordine a unui arbore
binar de căutare. Scrieţi un program care reconstituie arborele binar
corespunzător acestei parcurgeri.

Pentru problemele 3,...,5 se presupune că este valabilă următoarea


declaraţie pentru tipul nodurilor unui arbore binar:
template <class T> struct nod
{ T info;
nod * st, * dr;
};
Problema 3. Scrieţi o funcţie cu un parametru de tip pointer la un nod
dintr-un arbore binar care să returneze valoarea 1 dacă subarborele cu
rădăcina în nodul dat ca parametru este de căutare şi 0 în caz contrar.
Problema 4. Scrieţi o funcţie cu următorii parametri:
- nodcrt de tip pointer la un nod dintr-un arbore binar de căutare;
- cheie de tipul generic T;
Funcţia va returna predecesorul cheii date ca parametru al funcţiei din
subarborele cu rădăcina în nodcrt sau valoarea cheii date ca
parametru când aceasta este cheia cea mai mică din arbore.
Problema 5. Scrieţi o funcţie cu următorii parametri:
- nodcrt de tip pointer la un nod dintr-un arbore binar de căutare;
- limst şi limdr două valori de tipul generic T;
funcţia va returna numărul de chei din nodurile subarborelui cu
rădăcina în nodcrt care aparţin intervalului [limst;limdr].

82

S-ar putea să vă placă și