Sunteți pe pagina 1din 57

CAPITOLUL 5.

Arbori de căutare echilibraţi

5.1 Generalităţi

Funcţia de inserare într-un arbore binar de căutare descrisă în


capitolul precedent are dezavantajul că poate conduce la crearea unui
arbore în care, între înălţimea subarborelui drept subordonat unui nod
şi înălţimea subarborelui stâng subordonat nodului respectiv este o
diferenţă considerabilă. În cel mai defavorabil caz ne putem imagina
situaţia când valorile pe care urmează să le introducem în arbore sunt
deja sortate. În acest caz, arborele binar creat va avea toate nodurile
într-o singură direcţie şi înălţimea n-1 (unde prin n am notat numărul
de noduri din arbore). Un astfel de arbore binar este de fapt o listă
liniară iar majoritatea operaţiilor fundamentale (căutare, inserare sau
eliminare) au complexitatea O(n).
Având în vederea dezavantajul unor astfel de situaţii, au fost
căutate soluţii pentru a crea arbori echilibraţi în care înălţimea să fie
proporţională cu log(n) sau cel puţin operaţiunile fundamentale să
fie, în medie executate cu o complexitate medie logaritmică. Vom
admite că aceasta este definiţia unui arbore echilibrat.
Cele mai cunoscute tipuri de arbori echilibraţi sunt:
- arborii AVL realizabili prin metoda descoperită de G. M.
Adelson-Velskii şi E. M. Landis în 1962 [1];
- arborii roşu-negru inventaţi de Rudolf Bayer în 1972 [3];
- arborii B inventaţi de Bayer-McCreight în 1972;
- arborii 2-3 şi arborii 2-3-4 (cazuri particulare ale arborilor B);
- arborii splay inventaţi de Daniel Dominic Sleator şi Robert
Endre Tarjan în 1985 [18];
- arborii scapegoat, inventaţi de Arne Andersson în 1962 [2].
Criteriile care conduc la proprietatea unui arbore de a fi echilibrat pot fi
deterministice sau probabilistice. Exemple de criterii deterministice:
 diferenţa între înălţimile celor doi subarbori subordonaţi fiecărui
nod să fie egală cu -1,0 sau +1 (la arborii AVL)
 raportul dintre lungimea celui mai lung lanţ care leagă un nod de
o frunză aflată în subarborii subordonaţi şi a celui mai scurt lanţ
care leagă acel nod de o frunză să fie cel mult 2 (la arborii roşu-
negru);
Arborii scapegoat sunt exemplul „clasic” de arbori echilibraţi care
rezultă prin folosirea unui criteriu probabilistic.
83
5.2 Rotaţii în arbori binari de căutare

Rotaţiile sunt operaţii care re-echilibrează un arbore binar de căutare


conservând criteriul de integritate. Ele se efectuează în raport cu un
vârf. Sunt de patru tipuri:
 simplă spre stânga;
 simplă spre dreapta;
 dublă spre stânga;
 dublă spre dreapta;

a) Rotaţia simplă spre dreapta

Considerăm că cel mai bun mod de a prezenta o rotaţie este să


prezentăm în mod grafic efectele acesteia. Figura 5.1 în care se
efectuează tranziţia notată a prezintă efectul unei rotaţii simple spre
dreapta în raport cu nodul aflat la adresa notată p care reţine cheia x.
O astfel de rotaţie se va aplica numai în situaţiile în care nodul de la
adresa x are fiul stâng! Vom presupune că în acest nod se reţine cheia
y. Efectele rotaţiei sunt următoarele:
- nodul care reţinea cheia x devine fiul stâng al nodului care
reţinea cheia y;
- subarborele drept al nodului cu cheia y devine subarbore stâng
al nodului cu cheia x (cel notat cu S2);
- nodul care reţine cheia y se va afla la adresa p.

x a
y

y p p x
S3
S1

S1 S2 S2 S3

Fig 5.1: Ilustrarea unei rotaţii simple

Este uşor de observat că această transformare conservă criteriul de


integritate al arborelui binar de căutare.
Complexitatea unei rotaţii simple este O(1) având în vedere că se fac
doar instrucţiuni de atribuire.

84
b) Rotaţia simplă spre stânga

Este inversa rotaţiei simple spre stânga; efectele ei sunt ilustrate


tot de figurile 5.1 în care se efectuează tranziţia notată cu b. Vom
presupune că rotaţia se efectuează în raport cu nodul care reţine cheia
y şi care are fiu drept în care este reţinută cheia x.
Efectele rotaţiei sunt următoarele:
- nodul care reţinea cheia y devine fiul stâng al nodului care
reţinea cheia x;
- subarborele stâng al nodului cu cheia x devine subarbore drept
al nodului cu cheia y;
- nodul care reţine cheia x se va afla la adresa p.
Şi în acest caz este uşor de observat că această transformare
conservă criteriul de integritate al arborelui binar de căutare.

c) Rotaţia dublă spre dreapta

O rotaţie dublă spre dreapta se efectuează numai în condiţiile în


care nodul în raport cu care aceasta se efectuează are fiul stâng şi
acest fiu are la rândul lui fiul drept. Vom nota cu:
- x cheia reţinută de nodul în raport cu care se efectuează rotaţia
dublă şi
- p pointerul care reţine adresa acestuia;
- y cheia reţinută de fiul stâng al nodului cu cheia x
- z cheia reţinută de fiul drept al nodului y.

O rotaţie dublă este alcătuită din două rotaţii simple şi anume:

- o rotaţie simplă spre stânga în raport cu nodul aflat în stânga


nodului x (care reţine cheia y);
- o rotaţie simplă spre dreapta în raport cu nodul care reţine cheia
x;

Prezentăm în mod grafic efectele unei rotaţii duble spre dreapta


în raport cu nodul aflat la adresa notată p care reţine valoarea x.

85
II
x

y I p Sdx

Ssy z

Ssz Sdz

p
z

y x

Ssy Ssz Sdz Sdx

Fig 5.2: Ilustrarea unei rotaţii duble spre dreapta


Efectele rotaţiei duble sunt următoarele:
- nodul care reţinea cheia x devine fiul drept al nodului care
reţinea cheia z;
- nodul care reţinea cheia y devine fiul stâng al nodului care
reţinea cheia z;
- subarborele drept al nodului cu cheia z devine subarbore stâng
al nodului cu cheia x;
- subarborele stâng al nodului cu cheia z devine subarbore drept
al nodului cu cheia y;
- nodul care reţine cheia z se va afla la adresa p.
Este uşor de observat că şi această transformare conservă criteriul de
integritate al arborelui binar de căutare. Complexitatea unei rotaţii
duble este tot O(1) (se execută doar instrucţiuni de atribuire).

d) Rotaţia dublă spre stânga

Este simetrica rotaţiei duble spre dreapta. Nu mai facem


prezentarea detaliată a acesteia, dar invităm cititorul să o reprezinte
printr-o figură asemănătoare cu figura 5.2.

86
5.3 Arbori AVL

Un arbore echilibrat de tip AVL are proprietatea că pentru


fiecare nod din arbore este îndeplinită următoarea proprietatea:
diferenţa dintre înălţimea subarborelui său stâng şi înălţimea
subarborelui său drept este -1, 0 sau 1. Pentru aceasta, de fiecare
dată când este modificată structura arborelui prin inserarea sau
eliminarea unui vârf, se verifică dacă proprietatea sus menţionată a fost
păstrată. În caz contrar pentru vârfurile la care proprietatea a fost
deteriorată se face o restaurare a proprietăţii (deci o re-echilibrare)
printr-o operaţie specifică denumită rotaţie. Pentru a efectua rapid
verificarea dacă arborele a fost dezechilibrat în urma unei operaţii de
inserare sau eliminare, pentru fiecare vârf se memorează o informaţie
suplimentară care poate fi:
- o valoare care reprezintă diferenţa dintre înălţimile subarborilor
stâng şi drept (deci una dintre valorile -1, 0 sau 1);
- o valoare care reprezintă înălţimea vârfului respectiv.
Autorii acestei metode au demonstrat că înălţimea unui arbore de
tip AVL cu n noduri, notată hAVL îndeplineşte relaţia:

hAVL ≤ 1,4404log2(n + 2)

5.3.1 Rotaţii în arbori binari de căutare de tip AVL

a) Rotaţia simplă spre stânga

Rotaţiile spre stânga se efectuează în momentul în care


înălţimea subarborelui drept al unui vârf oarecare pe care o să îl notăm
în continuare x este mai mare cu 2 decât înălţimea subarborelui stâng.
Să presupunem că avem configuraţia din figura 5.3 a subarborelui cu
rădăcina în x şi aşa cum am presupus mai înainte, că
Hdrx = Hstx + 2 (5.1)
Să mai presupunem că nodul y este echilibrat şi că Hdry  Hsty, deci
- fie Hdry= Hsty, (5.2.a)
- fie Hdry = Hsty + 1 (5.2.b)
Deoarece
Hdrx = max(Hdry , Hsty) + 1  Hdrx = Hdry + 1 
Hdry + 1 = Hstx + 2  Hdry = Hstx + 1  (5.3)
- fie Hsty = Hstx + 1 (5.4.a)
- fie Hsty = Hstx (5.4.b)

87
x

Hstx Hdr
Hsty Hdr x
Sstx y

Ssty Sdry

Fig.5.3: Subarbore dezechilibrat la nivelul vârfului x


Printr-o rotaţie în raport cu vârful x se ajunge la configuraţia
următoare:

x
H'sty Hdr
Hstx y
Hsty
Sdry

Sstx Ssty

Fig.5.4: Subarborele anterior după efectuarea rotaţiei spre stânga în


raport cu vârful x

Observăm pe configuraţia rezultată că:


- vârful x este echilibrat (conform oricăreia dintre formulele 5.4.a
sau 5.4.b);
- înălţimea subarborelui drept al vârfului y este Hdry iar cea a
noului subarbore stâng al vârfului y este H'sty
=1+max(Hstyy,Hstx) = 1+ Hsty.
Conform ipotezei Hdry  Hsty rezultă că diferenţa între
înălţimea noului subarbore stâng a lui y şi înălţimea subarbore
drept este 1 sau 0, deci şi vârful y este echilibrat.

În plus, se observă că prin această rotaţie a fost păstrat criteriul


de integritate (proprietatea fundamentală a arborilor binari de căutare -
„stânga < părinte < dreapta”) atât pentru vârful x cât şi pentru vârful y!

88
Funcţia care implementează rotaţia simplă spre stânga este
prezentată în continuare. Vom folosi varianta în care structura care
defineşte un nod reţine şi o valoare care reprezintă înălţimea vârfului
respectiv (câmp de tip short int denumit h):

#define GENERIC template <class T>


GENERIC struct nod
{ T info;
short int h;
nod * st, * dr;
};

Funcţia propriu-zisă realizează noile legături conform celor ilustrate în


figurile 5.1, 5.3 şi 5.4 şi calculează noile valori pentru câmpul h al
nodurilor x şi y.

GENERIC void rotsstAVL(nod<T> * &x)


{
nod<T> *y = x->dr;
x->dr = y->st;
y->st = x;
x->h = 1 + max(height(x->st), height(x->dr));
y->h = 1 + max(x->h,height(y->dr));
x = y;
}
În cele de mai sus este apelată funcţie height care are ca parametru
adresa unui nod şi returnează înălţimea respectivului nod. În mod
normal aceasta este dată de câmpul h, dar dacă apelul este pentru
adresa nulă, atunci dereferențierea pointerului dat ca parametru ar
genera o eroare. În acest caz, se returnează direct valoarea -1.
Funcţia, foarte simplă de altfel, are rolul de a simplifica codul pentru
rotaţii şi pentru funcţia de inserare care va fi prezentată în subcapitolul
următor.

GENERIC short int height(nod<T>*nodcrt)


{ if (nodcrt!=NULL) return nodcrt->h;
else return -1;
}

Ultima atribuire din funcţia de rotaţie are rolul de a lega subarborele


care a rezultat prin apelul funcţiei de rotaţie la nodul la care acesta este
subordonat (aceasta se realizează prin mecanismul parametrilor de tip
referinţă!
89
b) Rotaţia simplă spre dreapta

Este exact opusa rotaţiei spre stânga. Codul funcţiei care


implementează această rotaţie este următorul:

GENERIC void rotsdrAVL(nod<T> * &y)


{ nod<T> *x = y->st;
y->st = x->dr;
x->dr = y;
y->h = 1 + max(height(y->st),height(y->dr));
x->h = 1 + max(height(x->st),height(x->dr));
y = x;
}

b) Rotaţia dublă spre stânga

În explicaţiile aferente rotaţiei simple spre stânga am făcut o


ipoteză importantă referitoare la raportul dintre înălţimile
subarborilor subordonaţi vârfului y. În cazul în care aceasta nu este
validă şi deci Hdry ≤ Hsty mai exact Hdry = Hsty - 1, prin
efectuarea rotaţiei simple se va rezolva dezechilibrul de la vârful x,
dar va apare un dezechilibru la nivelul vârfului y: subarborele stâng
rezultat în urma rotaţiei va avea înălţimea Hsty + 1 ceea ce
reprezintă o valoare mai mare cu 2 decât înălţimea subarborelui
drept a lui y!
În acest caz, pentru echilibrarea arborelui şi la nivelul vârfului y,
este necesară o rotaţie suplimentară care să ne aducă în condiţiile
ipotezei iniţiale; astfel, efectuând pentru început o rotaţie simplă
spre dreapta în raport cu vârful y, înălţimea subarborelui drept a lui
y devine mai mare sau cel puţin egală cu înălţimea subarborelui
stâng a lui y. Aceasta va avea ca efect că rotaţia simplă spre
stânga în raport cu vârful x va duce la echilibrarea corectă a
subarborelui. În concluzie, o rotaţie dublă spre stânga este formată
din două rotaţii simple: prima spre dreapta aplicată în raport cu fiul
drept a vârfului unde a apărut un dezechilibru urmată de o rotaţie
simplă spre stânga în raport cu vârful analizat:

90
II
x
x
I y

Sstx

Ssty Sdry

Fig.5.5: rotaţie dublă spre dreapta în raport cu vârful x

Codul acestei funcţii este foarte simplu având în vedere că sunt


deja implementate funcţiile pentru rotaţii simple:
GENERIC void rotdstAVL(nod<T> *&x)
{ rotsdr(x->dr);
rotsst(x);
}

c) Rotaţia dublă spre dreapta

Este reprezentată schematic în figura 13.5:

II
y

x I

Sdry

Sstx Sdrx

Fig. 5.6: rotaţie dublă spre dreapta în raport cu vârful y

Codul funcţiei:
GENERIC void rotddrAVL(nod<T> *&y)
{ rotsst(y->st);
rotsdr(y);
}
91
În ceea ce priveşte complexitatea rotaţiilor, aceasta este în toate cele
patru cazuri O(1) deoarece toate instrucţiunile din funcţiile care
implementează aceste operaţii sunt fie atribuiri fie instrucţiuni de
decizie.

5.3.2 Inserarea unei chei într-un arbore AVL

Această operaţiune are la bază funcţia de inserare dintr-un


arbore binar de căutare neechilibrat (subcapitolul 4.2.1) cu deosebirea
că după inserarea vârfului (în program după apelul recursiv) se verifică
dacă a apărut un dezechilibru. Fiind după apelul recursiv, această
verificare se va face „de jos în sus” pe ramura din arbore pe care a
parcurs-o funcţia de inserare (deci de la părintele vârfului frunză inserat
spre rădăcină). Adelson-Velskii şi Landis au demonstrat că este
suficientă o rotaţie (simplă sau dublă) în raport cu un singur vârf pentru
re-echilibrare.
Vom ilustra în figura 5.6 modul în care este echilibrat un arbore
binar prin metoda AVL presupunând că urmează să fie inserate în
arbore chei numerice cu următoarele valori:
10, 20, 40, 25, 28, 30,35,51.
Reamintim că un subarbore format dintr-un singur nod are înălţime 0,
deci un subarbore inexistent are înălţimea -1.

Prima valoare din şir, 10, este


10 inserată în rădăcină. Nu se pune
problema apariţiei unui
dezechilibru.

A doua valoare din şir, 20, este


10 inserată în dreapta nodului
rădăcină. Nu există dezechilibre.
20

92
Valoarea 40 este inserată într-un
10 nod frunză după ce s-a parcurs
drumul format din nodurile 10 şi
20 20. Se verifică dacă a apărut un
dezechilibru la nodurile de pe
acest drum: prima dată la nodul
40 20, dar la nivelul acestui nod nu
apare vreun dezechilibru. Apoi la
nodul 10. Aici, subarborele stâng
e vid, deci are înălţime -1 iar
20 subarborele drept are înălţimea 1,
deci a apărut un dezechilibru. Este
necesară o rotaţie simplă spre
10 40 stânga în raport cu nodul rădăcină.

Se inserează valoarea 25. Nu


20 apar dezechilibre.

10 40

25

93
Se inserează valoarea 28, drumul
20 urmat fiind format din nodurile 20,
II 40 şi 25. Se verifică apariţia
10 40 dezechilibrelor în sens invers.
Primul nod la care se constată un
I dezechilibru este 40 (subarborele
25 stâng are înălţimea 1, cel drept -1.
Pentru echilibrare este necesară o
28 rotaţie dublă spre dreapta în raport
cu acest nod.

După prima rotaţie simplă spre


20
stânga în raport cu nodul 25
II
arborele va avea această
10 40 configuraţie.

28

25 După a doua rotaţie simplă spre


dreapta în raport cu nodul 40 se
obţine arborele echilibrat.
20

10 28

25 40

94
Se inserează valoarea 30 drumul
20 urmat fiind format din nodurile 20,
28 şi 40. Se verifică apariţia
10 28 dezechilibrelor în sens invers. La
nodurile care reţin valorile 40 şi 28
nu a apărut vreun dezechilibru. La
25 40 nodul rădăcină a apărut un
dezechilibru: subarborele stâng
are înălţimea 0 iar cel drept
30 înălţimea 2. Pentru echilibrare este
necesară o rotaţie simplă spre
stânga raport cu acest nod.
28

20 40

10 25 30

După inserarea valorii 35 apare la


28 nivelul nodului cu cheia 40:
II subarborele stâng are înălţimea 1,
20 40 cel drept -1. Deoarece subarborele
nodului cu cheia 30 care a creat
I dezechilibrul este cel drept,
10 25 30 este necesară o rotaţie dublă spre
dreapta în raport cu nodul cu cheia
40.
35

28

20 35

10 25 30 40

95
Inserarea valorii 51 nu produce
28 vreun dezechilibru.

20 35

10 25 30 40

51

Fig. 5.7 Inserarea într-un arbore de tip AVL

Este important de observat că după fiecare inserare şi eventuala


aplicare de rotaţii, arborele rezultat este echilibrat.
Funcţia care implementează inserarea într-un arbore AVL este
prezentată în continuare:

GENERIC void inserareAVL(nod <T>*&nodcrt, T cheie)


{if (nodcrt==NULL) /*1*/
{nodcrt = new nod<T>;
nodcrt->info = cheie;
nodcrt->st = nodcrt->dr = NULL;
nodcrt->h = 0;
return;
}
else
{if (nodcrt->info == cheie) /*2*/
return;
if (cheie < nodcrt->info) /*3*/
{inserareAVL(nodcrt->st,cheie);
if (height(nodcrt->st)>height(nodcrt->dr)+1)
if(height(nodcrt->st->st)>=height(nodcrt->st->dr))
rotsdrAVL(nodcrt);
else
rotddrAVL(nodcrt);
else
nodcrt->h = 1 +
max(height(nodcrt->st),height(nodcrt->dr));
}
else /*4*/
{inserareAVL(nodcrt->dr,cheie);
if (height(nodcrt->dr) > 1+height(nodcrt->st))
if(height(nodcrt->dr->dr)>=height(nodcrt->dr->st))
rotsstAVL(nodcrt);
96
else
rotdstAVL(nodcrt);
else
nodcrt->h = 1 +
max(height(nodcrt->st), height(nodcrt->dr));
}
}
}

Câteva explicaţii pentru secvenţele comentate:


/*1*/: alocarea de memorie pentru nodul care va reţine cheia inserată şi
completarea celorlalte câmpuri cu valori corespunzătoare;
/*2*/: cheia de inserat există deja în arbore, deci funcţia nu o inserează;
/*3*/: cheia de inserat e mai mică decât cheia nodului curent; cheia va fi
inserată în subarborele stâng. La revenirea din apelul recursiv:
 prin primul if se testează dacă a apărut sau nu un
dezechilibru în raport cu nodul curent; dacă a apărut
o prin al doilea if se testează care subarbore este
responsabil pentru acest dezechilibru; în funcţie de
aceasta se face o rotaţie simplă sau dublă;
 dacă nu a apărut un dezechilibru, trebuie recalculată
înălţimea subarborelui.
/*4*/: cheia de inserat e mai mare decât cheia nodului curent deci va fi
inserată în subarborele drept. Paşii sunt similari ca la cazul
precedent.
Programul care generează un arbore AVL prin apeluri ale funcţiei de
inserare este listat în continuare. Pe lângă funcţiile prezentate deja, el
mai conţine:
 o funcţie care traversează arborele în preordine afişând
pentru fiecare nod cheia reţinută şi înălţimea arborelui.
 funcţia main() în care se apelează în mod repetat funcţia de
inserare pentru valori de tip int citite dintr-un fişier text.

//program 5.1 inserare in arbori AVL


#include <iostream>
#include <fstream>
#define GENERIC template <class T>
using namespace std;
GENERIC struct nod
{ T info;
short int h;
nod * st, * dr;};
GENERIC short int height(nod<T>*nodcrt){...}
GENERIC void rotsstAVL(nod<T> * &x){...}
97
GENERIC void rotsdrAVL(nod<T> * &y){...}
GENERIC void rotdstAVL(nod<T> *&x) {...}
GENERIC void rotddrAVL(nod<T> *&y) {...}
GENERIC T minimarb(nod<T> * nodcrt) {...}
GENERIC void inserare(nod <T>*&nodcrt, T cheie)
{...}
GENERIC void preord(nod<T>*nodcrt)
{ if (nodcrt!=NULL)
{ cout << "cheia "<<nodcrt->info;
cout << ",inaltime "<<nodcrt->h<<endl;
preord(nodcrt->st);
preord(nodcrt->dr);
}
}
int main()
{
nod<int> *radacina = NULL;
int x;
ifstream fin("valori.in");
while (fin >> x)
{
inserare(radacina,x);
}
cout << "arborele rezultat dupa inserare\n";
preord(radacina);
return 0;
}
Dacă fişierul text valori.in conţine valorile folosite în figurile 5.6
10 20 40 25 28 30 35 51
programul va afişa în fereastra consolă următoarele:

arborele rezultat
cheia 28,inaltime 3
cheia 20,inaltime 1
cheia 10,inaltime 0
cheia 25,inaltime 0
cheia 35,inaltime 2
cheia 30,inaltime 0
cheia 40,inaltime 1
cheia 51,inaltime 0

98
5.3.3 Eliminarea unei chei dintr-un arbore AVL

Şi această operaţiune are la bază funcţia corespunzătoare


pentru arbore binar de căutare neechilibrat (vezi din nou capitolul
precedent!) şi este principial asemănătoare cu cea de inserare, în
sensul că după eliminare (după apelul recursiv) verifică tot „de jos în
sus” apariţia unor eventuale dezechilibre şi reface proprietatea de
arbore AVL prin rotaţii. Însă atunci când este eliminat un vârf dintr-un
arbore AVL este posibil să fie necesare rotaţii în raport cu toate
vârfurile de pe calea urmată de funcţia de eliminare (de la părintele
vârfului eliminat până la rădăcina arborelui!). Vom ilustra această
situaţie în figura 5.7:

28 În acest arbore nu există


dezechilibru la niciunul dintre
noduri. Să presupunem că
20 40
eliminăm nodul care reţine
cheia 30.
10 25 30 43

5 13 27 49

16

28 Calea urmată de funcţia de


eliminare pentru a ajunge la
nodul cu cheia 30 este
20 40 formată din nodurile 28 şi 40.
Rezultă arborele alăturat.
10 25 43 Se verifică nodul 40. Se
constată că a apărut un
dezechilibru: subarborele
5 13 27 49 stâng a acestuia are înălţimea
-1 iar cel drept 1. Se aplică o
16 rotaţie spre stânga în raport cu
acest nod.

Fig. 5.8.a Eliminarea dintr-un arbore de tip AVL

99
28 Se verifică nodul 28. Şi aici se
constată că a apărut un
dezechilibru: subarborele
20 43
stâng a acestuia are înălţimea
3 iar cel drept 1. Se aplică o
10 25 40 49 rotaţie spre dreapta în raport
cu acest nod. Se obţine
arborele echilibrat!
5 13 27

16

20

10 28

5 13 25 43

16 27 40 49

Fig. 5.8.b Eliminarea dintr-un arbore de tip AVL

Funcţia care implementează algoritmul de eliminare este listată în


continuare. Ea are la bază funcţia de eliminare dintr-un arbore binar de
căutare dată ca exemplu în subcapitolul 4.2.3. Faţă de aceasta, apar în
plus secvenţe identice cu cele din funcţia de inserare într-un arbore
AVL prin care pentru nodul curent, după fiecare apel recursiv, se
verifică dacă au apărut dezechilibre şi dacă este necesar se
efectuează rotaţiile de reechilibrare, dacă nu se recalculează înălţimea
nodului curent.

GENERIC void eliminareAVL(nod<T> *&nodcrt, T cheie)


{if (nodcrt==NULL)
{ cout << "valoarea nu este in arbore\n";
return;
}
if (nodcrt->info > cheie)
{eliminareAVL(nodcrt->st,cheie);
if (height(nodcrt->dr)>1+height(nodcrt->st))
if(height(nodcrt->dr->dr)>=height(nodcrt->dr->st))
rotsstAVL(nodcrt);
else
rotdstAVL(nodcrt);
100
else
nodcrt->h = 1 +
max(height(nodcrt->st), height(nodcrt->dr));
}
else
if (nodcrt->info < cheie)
{eliminareAVL(nodcrt->dr,cheie);
if (height(nodcrt->st)>height(nodcrt->dr)+1)
if(height(nodcrt->st->st)>=height(nodcrt->st->dr))
rotsdrAVL(nodcrt);
else
rotddrAVL(nodcrt);
else
nodcrt->h = 1 +
max(height(nodcrt->st), height(nodcrt->dr));
}
else //cheia apare in nodul curent
{//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);
nodcrt->info = auxcheie;
eliminareAVL(nodcrt->dr,auxcheie);
if (height(nodcrt->st)>height(nodcrt->dr)+1)
if (height(nodcrt->st->st)>=height(nodcrt->st->dr))
rotsdrAVL(nodcrt);
else
101
rotddrAVL(nodcrt);
else
nodcrt->h = 1 +
max(height(nodcrt->st), height(nodcrt->dr));
}
}
Adăugăm această funcţie la programul 5.1 şi modificăm funcţia
main() adăugând instrucţiunile:

eliminareAVL(radacina,30);
cout<<"arborele dupa eliminarea valorii 30\n";
preord(radacina);
Dacă fişierul text valori.in conţine valorile folosite pentru a construi
arborele din figura 5.8 şi anume
28 20 40 10 25 30 43 5 13 27 49 16
programul va afişa în fereastra consolă următoarele:
arborele rezultat dupa inserare
cheia 28,inaltime 4
cheia 20,inaltime 3
cheia 10,inaltime 2
cheia 5,inaltime 0
cheia 13,inaltime 1
cheia 16,inaltime 0
cheia 25,inaltime 1
cheia 27,inaltime 0
cheia 40,inaltime 2
cheia 30,inaltime 0
cheia 43,inaltime 1
cheia 49,inaltime 0
arborele rezultat dupa eliminarea valorii 30
cheia 20,inaltime 3
cheia 10,inaltime 2
cheia 5,inaltime 0
cheia 13,inaltime 1
cheia 16,inaltime 0
cheia 28,inaltime 2
cheia 25,inaltime 1
cheia 27,inaltime 0
cheia 43,inaltime 1
cheia 40,inaltime 0
cheia 49,inaltime 0

Se observă că rezultatele afişate corespund arborelui final din figura


5.8.

102
5.4 Arbori Roşu-Negru
5.4.1 Definiţie, proprietăţi

Acest tip de arbori binari a fost inventat de Rudolf Bayer în 1972


sub numele de „B-arbori binari simetrici”. Ulterior au fost denumiţi
„Roşu-Negru” de Guibas şi Sedgewick care i-au studiat în detaliu [10].
Arborii Roşu-Negru sunt arbori binari de căutare în care se
presupune că fiecare nod este colorat cu una dintre culorile roşu sau
negru.
Pentru o mai uşoară explicaţie a proprietăţilor acestor arbori, în
majoritatea lucrărilor în care aceştia sunt prezentaţi se introduc
santinele în locul legăturilor spre stânga sau dreapta care au valoarea
NULL (figura 5.9). Prin definiţie, aceste santinele se consideră că sunt
colorate în negru.
Din motive de economie de spaţiu de memorie, implementările care
folosesc această metodă nu declară decât un singur astfel de nod
santinelă. Adresa acestuia este atribuită legăturilor spre stânga şi
dreapta ale fiecărui nod nou creat (figura 5.9).

28 28

20 40 20 40

10 25 30 10 25 30

Fig. 5.9 Arbore Roşu-Negru cu mai Fig. 5.10 Arbore Roşu-Negru cu un


multe noduri frunză santinelă singur nod frunză santinelă (varianta
(varianta teoretică) practică)

Menţionăm că nu este obligatoriu ca în implementarea unui astfel de


arbore să se lucreze efectiv cu santinele – aceasta se va vedea chiar
în programele care vor urma. Însă din punct de vedere teoretic, este
util să considerăm că aceste santinele sunt prezente.

Cu aceste precizări putem da următoarea definiţie:


Un arbore Roşu-Negru (notat de acum RN) este un arbore binar de
căutare care respectă următoarele reguli:
1. fiecare nod are asociată una dintre culorile roşu sau negru;
2. nodul rădăcină are culoarea neagră;

103
3. orice nod colorat roşu are fiii coloraţi cu negru (sau orice
nod roşu are nodul tatăl colorat cu negru);
4. fiecare drum de la rădăcină la o frunză conţine acelaşi
număr de noduri negre (acest număr este denumit înălţime
neagră).

Observăm că existenţa nodurilor santinelă de tip frunză nu afectează în


niciun fel proprietăţile enumerate mai sus.
În figura 5.11 este prezentat un arbore Roşu-Negru corect.

28

20 40

10 25 30

Fig. 5.11 Arbore Roşu-Negru


corect
În schimb arborele din figura 5.12 nu respectă toate regulile enumerate
mai sus. Aceasta s-ar observa mai uşor dacă am ilustrăm şi nodurile
santinelă – vezi figura 5.13: se observă că drumul de la rădăcină la
frunza santinelă din stânga nodului 40 are două noduri negre. Restul
drumurilor de la rădăcină la frunze au trei noduri negre.

28 28

20 40 20 40

10 25 30 10 25 30

Fig. 5.12 Arbore Roşu-Negru Fig. 5.13 Acelaşi arbore incorect


incorect ilustrat şi cu santinele.

Având în vedere regulile pe care trebuie să le respecte un arbore RN


rezultă că lungimea celui mai lung drum de la o rădăcină la o frunză
este de cel mult de două ori mai mare lungimea celui mai scurt drum
de la rădăcină la o frunză. Aceasta are loc dacă pe cel mai lung drum
nodurile au culori alternante iar pe cel mai scurt drum toate nodurile
104
sunt negre. Aceasta proprietate are ca efect faptul că înălţimea unui
arbore RN cu n noduri notată hRN îndeplineşte proprietatea

hRN ≤ 2lg(n+1).

5.4.2 Inserarea unei chei într-un arbore RN

A. Algoritmul de inserare

Algoritmul de inserare într-un arbore RN are la bază algoritmul


de inserare într-un arbore binar de căutare prezentat în capitolul
precedent (4.2.1). Nodul inserat este colorat cu roşu. Astfel nu este
afectată în mod direct înălţimea neagră; în schimb s-ar putea ca
părintele nodului inserat să fie tot roşu şi astfel nodul inserat nu mai
respectă regula 3 (el este roşu şi are nodul tată tot roşu). Aşa că în
acest caz, după inserarea nodului trebuie refăcută proprietatea de
arbore RN prin recolorări şi rotaţii. Aceste operaţiuni pot afecta nodul
tată al nodului inserat, nodul bunic al nodului inserat şi nodul unchi al
nodului inserat (pentru clarificarea acestor trei noţiuni vezi figura 5.14).

nod bunic

nod unchi nod tată

nod inserat

Fig. 5.14 Ilustrarea noţiunilor de nod părinte,


nod bunic şi nod unchi relative la un nod inserat.

Vom lua în considerare cazul în care nodul a fost inserat în


dreapta nodului bunic. Aşa cum am menţionat mai înainte, situaţia care
necesită recolorare este când nodul tată este roşu! Evident că în acest
caz nodul bunic este negru (deoarece arborele existent înaintea
inserării era RN, deci un nod roşu avea obligatoriu nodul tată negru).
Apar trei configuraţii (sau cazuri) posibile :

i) Cazul I: nodul bunic negru, nodurile unchi şi tată roşii


Situaţia este ilustrată în figura 5.15.
Soluţia în acest caz este de a recolora nodul bunic în roşu şi
cei doi fii ai acestui nod, adică nodul tată şi nodul unchi, în
negru.
105
Prin aceasta, înălţimile negre nu vor fi afectate iar refacerea
proprietăţii de arbore RN este rezolvată local (adică pentru
subarborele cu rădăcina în nodul bunic), dar dacă tatăl nodului
bunic este roşu, atunci nodul bunic încalcă regula 3, dar
alterarea a fost mutată cu un nivel mai sus. Aceasta va
presupune aplicare unei corecţii la acest nivel. Dacă configuraţia
este similară pentru nodurile bunic, tată şi unchi ale nodului care
încalcă acum regula 3 (noul nod bunic este negru iar noile
noduri tată şi unchi sunt roşii) vom proceda analog. Astfel este
posibil ca deteriorarea regulii 3 să se propage în arbore în sus
pe drumul urmat de funcţia de inserare până la nivelul nodului
rădăcină. În această situaţie problema se va rezolva
asemănător: recolorăm rădăcina în roşu şi cei doi fii ai acesteia
în negru, apoi refacem şi regula 2, adică recolorăm rădăcina în
negru. Evident că această ultimă recolorare nu afectează
proprietăţile 4 sau 3!

Presupunem că în acest arbore


24 inserăm o cheie cu valoarea 41.

15 32

Arborele rezultat are un nod roşu


24 cu un fiu roşu, deci nu respectă
regula 3.
15 32

41
Configuraţia corespunde cazului I.
24 Se recolorează nodul tată şi nodul
unchi în negru iar nodul bunic în
roşu. Arborele rezultat are
15 32 rădăcina roşie deci nu respectă
regula 2. Se recolorează rădăcina
41 cu negru.

106
În final se obţine un arbore RN
24 corect!

15 32

41
Fig. 5.15 Rezolvarea încălcării regulii 3 apărută după inserarea unei
chei în cazul I.

Dacă nodul unchi al unui nod care încalcă proprietatea 3 este


negru, metoda recolorării celor trei noduri nu rezolvă problema.
Rezolvarea în acest caz se face prin rotaţii şi recolorări în funcţie
de următoarele cazuri:

ii) Cazul II: nodurile bunic şi unchi negre, nodul tată roşu, nodul tatăl
este fiul din dreapta al nodului bunic
Soluţia în acest caz este alcătuită din:
- o rotaţie simplă spre stânga în raport cu nodul bunic;
- interschimbarea culorilor nodului bunic şi nodului tată
(care în urma rotaţiei sunt la adresele unde iniţial erau nodul
unchi şi respectiv nodul bunic!).

Presupunem că în acest arbore


14 inserăm o cheie cu valoarea 50.

11 24

19 32

27 41

107
Nodul cu cheia 50 nu respectă
14 regula 3. Se aplică metoda
recolorării celor trei noduri (cele cu
cheile 32, 27 şi 41).
11 24

19 32

27 41

50
În configuraţia rezultată nodul cu
14 cheia 32 nu respectă regula 3. El
este fiu drept al nodului 24 care
este tot roşu. Nodurile bunic şi
11 24 unchi sunt negre. Se efectuează
rotaţia simplă spre stânga în
19 32 raport cu nodul cu cheia 14 (nodul
bunic a lui 32).
27 41

50
Apoi se interschimbă culorile
24 nodurilor cu cheile 24 şi 14 (iniţial
bunic şi tată).
14 32

11 19 27 41

50
Arborele RN rezultat este corect
24 iar înălţimile negre ale tuturor
ramurilor nu au fost modificate!
14 32

11 19 27 41

50
Fig. 5.16 Refacerea arborelui RN după inserare, cazul II.

108
iii) Cazul III: : nodurile bunic şi unchi negre, nodul tată roşu, nodul tatăl
este fiul din stânga al nodului bunic
În acest caz se efectuează:
- o rotaţie dublă spre stânga în raport cu nodul bunic;
- interschimbarea culorilor nodului bunic şi nodului care
încălca regula 3 (care în urma rotaţiei sunt la adresele unde
iniţial erau nodul unchi şi respectiv nodul bunic!).
Menţionăm că prin prima rotaţie simplă a rotaţiei duble (cea
simplă spre dreapta în raport cu nodul tată se ajunge la
configuraţia specifică cazului I, configuraţie care se rezolvă prin
rotaţia simplă spre stânga în raport cu nodul bunic – observaţi
analogia cu necesitatea aplicării rotaţiilor duble la arborii AVL!)

Presupunem că în acest arbore


14 inserăm o cheie cu valoarea 13.

11 24

19 32
41
16 21

Nodul cu cheia 13 nu respectă


14 regula 3. Se aplică metoda
recolorării celor trei noduri (cele
cu cheile 19, 21 şi 16).
11 24

19 32
41
16 21

13

109
II În configuraţia rezultată nodul cu
14 cheia 19 nu respectă regula 3. El
este fiu stâng al nodului 24 care
I este tot roşu. Nodurile bunic şi
11 24 unchi sunt negre. Se efectuează
rotaţia dublă spre stânga în raport
19 32 cu nodul cu cheia 14 (nodul bunic
41 a lui 19).
16 21 21

13

Apoi se interschimbă culorile


19 nodurilor cu cheile 19 şi 14.

14 24

11 16 21 32

13

Arborele RN rezultat este corect


19 iar înălţimile negre ale tuturor
ramurilor nu au fost modificate!
14 24

11 16 21 32

13

Fig. 5.17 Rezolvarea încălcării regulii 3 apărută după inserarea unei


chei pentru configuraţia specifică cazului II.

Se observă că în cazurile II şi III rădăcina arborelui a fost colorată în


negru. Mai mult, după cum am menţionat în figurile 5.16 şi 5.17,
arborele rezultat în urma rotaţiilor şi a recolorărilor efectuate are
aceeaşi înălţime neagră, deci este conservată şi regula 4 (înălţime
neagră constantă pe oricare drum de la rădăcină la o frunză). Rezultă
că dacă oricare dintre aceste două metode se aplică la nivelul unui
subarbore, situaţiile de încălcare a regulii 3 (nod roşu cu fiu roşu)
apărute prin inserarea unui nod sunt eliminate la nivelul întregului
arbore RN.

110
Cazurile în care nodul a fost inserat în stânga nodului bunic sunt
simetricele cazurilor descrise mai înainte şi nu le mai prezentăm. Ele
pot fi urmărite în funcţia de inserare listată în continuare.

B. Implementarea algoritmului de inserare

Pentru implementarea acestui algoritm trebuie în primul rând


particularizată metodă de reprezentare a unui arbore binar printr-o
structură (vezi cap. 3.2.2). Majoritatea lucrărilor includ în această
structură pentru un nod dat următoarele: informaţia propriu-zisă
(cheia), doi pointeri către fiii stâng şi drept, un câmp pentru culoarea
nodului şi un pointer spre nodul părinte. Implementarea pe care o
prezentăm nu foloseşte acest pointer către nodul părinte. Pentru
aceasta testele şi eventualele corecţii prin rotaţii şi/sau recolorări
descrise în algoritm se vor face considerând nodul bunic ca fiind nodul
curent. Aceasta deoarece având acces la câmpurile nodului descris ca
nod bunic în prezentare algoritmului putem accesa informaţiile
nodurilor fii ai acestuia!

Pentru culoare am definit un tip enumerare cu două valori denumite


negru şi roşu (sinonime pentru valorile 0 şi respectiv 1):

enum culoare {negru=0,rosu=1};


Cu aceasta, definiţia tipului pentru nodurile arborelui este următoarea:
GENERIC struct nod
{ T info;
culoare cul;
nod * st, * dr;
};

Pentru a simplifica corpul funcţiei de inserare am definit o funcţie care


are ca parametru adresa unui nod şi returnează culoarea nodului. Dacă
adresa pentru care a fost apelată funcţia este NULL, valoarea returnată
este negru (am presupus existenţa santinelelor fără însă a la
implementa efectiv!)

GENERIC culoare retculoare(nod<T>*nodcrt)


{ if (nodcrt!=NULL)
return nodcrt->cul;
else
return negru;
}

111
Funcţia de inserare pe care o prezentăm are la bază funcţia de
inserare într-un arbore binar de căutare (prezentată în subcapitolul
4.2.1). La fel ca la funcţia de inserare într-un arbore AVL, după apelul
recursiv trebuie verificat dacă nu a fost deteriorată structura arborelui
RN prin încălcarea regulii 3. Pentru aceste verificări trebuie să ştim
dacă a apărut vreo alterare a regulii 3 în subarborele unde a fost
inserată cheia precum şi ce culori au nodurile aflate pe drumul urmat
de funcţia de inserare şi aflate cu două niveluri mai jos faţă de nodul
curent (nodul fiu şi nodul nepot). Din acest motiv funcţia de inserare pe
care o prezentăm returnează o valoare de tip int care oferă informaţii
referitoare la culorile celor două noduri, şi anume:
- valoarea 0: nodul părinte este negru, deci nu este necesară
nicio corecţie;
- valoarea 1: nodul fiu este roşu, nodul nepot este negru;
- valoarea 21: nodul fiu este în stânga, este roşu şi la rândul lui
are un fiu roşu;
- valoarea 22: nodul fiu este în dreapta, este roşu şi la rândul lui
are un fiu roşu;
Funcţiile simple de rotaţie sunt similare cu cele de la arborii AVL din
care lipsesc instrucţiunile de recalculare a înălţimilor din câmpurile h:
GENERIC void rotsst(nod<T> * &x)
{ nod<T> *y = x->dr;
x->dr = y->st;
y->st = x;
x = y;
}
GENERIC void rotsdr(nod<T> * &y)
{ nod<T> *x = y->st;
y->st = x->dr;
x->dr = y;
y = x;
}

GENERIC int inserareRN(nod <T>*&nodcrt, T cheie)


{if (nodcrt==NULL) //se insereaza efectiv cheia
{ nodcrt = new nod<T>;
nodcrt->info = cheie;
nodcrt->st = nodcrt->dr = NULL;
nodcrt->cul = rosu;
return 1; //nodul inserat este rosu!
}
else
{ if (nodcrt->info == cheie) //cheie existenta!
return 0;

112
else
if (cheie < nodcrt->info)
{ int nrrosii = inserareRN(nodcrt->st,cheie);
//nrrosii = cate rosii consecutive sunt sub el
if (nrrosii==0) return 0;
if (retculoare(nodcrt)==negru)
{ if (nrrosii == 1)
return 0;
//nrrosii=21 sau 22, trebuie facute corectii
if (retculoare(nodcrt->dr)==rosu)
{//recolorarea celor trei noduri
nodcrt->cul = rosu;
nodcrt->st->cul = negru;
nodcrt->dr->cul = negru;
return 1;
}
if (nrrosii == 21) //cazul I
rotsdr(nodcrt);
if (nrrosii == 22) //cazul II
rotddr(nodcrt);
swap(nodcrt->cul, nodcrt->dr->cul);
return 0;
}
else // nodcrt->cul==rosu
{ if (nrrosii == 0)
return 1;
else //nodul crt e rosu,apelul spre stanga
return 21;
}
}
else //cheia inserata mai mare=>apel spre dreapta
{ int nrrosii = inserareRN(nodcrt->dr,cheie);
if (nrrosii==0) return 0;
if (retculoare(nodcrt)==negru)
{ if (nrrosii == 1)
return 0;
//nrrosii=21 sau 22, trebuie facute corectii
if (retculoare(nodcrt->st)==rosu)
{// recolorarea celor trei noduri
nodcrt->cul = rosu;
nodcrt->st->cul = negru;
nodcrt->dr->cul = negru;
return 1;
}
if (nrrosii == 22) //cazul I
113
rotsst(nodcrt);
if (nrrosii == 21) //cazul II
rotdst(nodcrt);
swap(nodcrt->cul,nodcrt->st->cul);
return 0;
}
else // nodcrt->cul==rosu
{ if (nrrosii == 0)
return 1;
else //nodul crt e rosu,apelul spre dreapta
return 22;
}
}
}
}
În funcţia main(), după fiecare apel al funcţiei de inserare este
necesară recolorarea rădăcinii cu negru.

Vom ilustra în figura 5.18 modul în care este echilibrat un arbore binar
prin metoda RN presupunând că urmează să fie inserate în arbore chei
numerice cu următoarele valori (acelea şir de valori ca în cazul
ilustrării metodei AVL):
10, 20, 40, 25, 28, 30,35,51.

Se inserează valoarea 10. Nodul


10 este colorat iniţial cu roşu şi
constituie rădăcina arborelui.
După apelarea funcţiei de
inserare rădăcina este colorată cu
10 negru.

Este inserată valoarea 20 într-un


10 nod colorat cu roşu. Nu avem
vreo alterare a regulilor arborilor
20 RN.

Este inserată valoarea 40 la


10 dreapta nodului cu cheia 20.
Apare alterarea regulii 3 (nod roşu
cu fiu roşu). Configuraţia
20
corespunde cazului II: bunic
negru, unchi negru (acesta nu
40 există, deci putem considera că
este frunza de tip santinelă), fiul
114
roşu este în dreapta. Este
20 necesară o rotaţie simplă în raport
cu nodul bunic, urmată de o
10 40 interschimbare de culoare între
nodurile aflate în poziţia nodului
bunic şi respectiv unchi!

20

10 40
Este inserată valoarea 40 la
20 stânga nodului cu cheia 40. Apare
alterarea regulii 3 (nod roşu cu fiu
10 40 roşu). Configuraţia corespunde
cazului I, deci se aplică
recolorarea nodurilor bunic, unchi
25
şi tată.

20

10 40

25
Aceasta este urmată de
recolorarea cu negru a nodului
rădăcină.
20

10 40

25

Este inserată valoarea 28 la


20 dreapta nodului cu cheia 25.
II Apare din nou alterarea regulii 3
10 40 (nod roşu cu fiu roşu).
Configuraţia corespunde cazului
25 I III (bunic şu unchi negri, tatăl
roşu, fiul în dreapta). Este
necesară o rotaţie dublă spre
28 stânga în raport cu nodul cu cheia
40.

115
20 Aceasta este urmată de o
interschimbare a culorilor
10 28 nodurilor aflate în poziţiile
nodurilor bunic şi respectiv unchi.
25 40

20

10 28

25 40

Este inserată valoarea 30 la


20 stânga nodului cu cheia 40.
Apare din nou alterarea regulii 3
10 28 (nod roşu cu fiu roşu).
Configuraţia corespunde cazului
25 40 I. Se recolorează cele trei noduri.

30

20

10 28

25 40

30

Este inserată valoarea 35 la


20 dreapta nodului cu cheia 30.
Apare din nou alterarea regulii 3
10 28 (nod roşu cu fiu roşu).
II Configuraţia corespunde cazului
25 40 III. Se aplică rotaţia dublă spre
dreapta în raport cu nodul 40,
I urmată de interschimbarea
30 culorilor între nodurile cu cheile
35 şi 40.
35

116
20

10 28

25 35

30 40

20

10 28

25 35

30 40

Este inserată valoarea 51 la


20 dreapta nodului cu cheia 40.
Apare din nou alterarea regulii 3
10 28 (nod roşu cu fiu roşu).
Configuraţia corespunde cazului
25 35 bunic negru, unchi şi tată roşii. Se
face recolorarea celor trei noduri.
30 40

51

20
A apărut o alterare a regulii 3
28 datorită nodurilor cu cheile 35 şi
10
28. Configuraţia corespunde
cazului II: bunic şi unchi negri
25 35 tatăl roşu, fiul roşu e în dreapta
tatălui. Se aplică o rotaţie simplă
30 40 spre stânga în raport cu nodul cu
cheia 20.
51

117
Rotaţia este urmată de
28 interschimbarea culorilor dintre
nodurile ajunse în poziţiile unde
20 35 se aflau nodul bunic şi nodul
unchi, deci între nodurile cu cheile
10 25 30 40 28 şi 20:

51

28 Arborele rezultat înedplineşte


toate criteriile impuse arborilor
20 35 roşu-negru.

10 25 30 40

51

Fig. 5.18 Inserarea într-un arbore de tip RN

Şi în cazul funcţiei de inserare într-un arbore de tip RN se


observă că după fiecare cheie nou inserată arborele rezultat prin
aplicarea funcţiei îndeplineşte toate criteriile cerute pentru acest tip de
arbori binari echilibraţi.

În concluzie, relativ la operaţiunea de inserare a unei chei dintr-un


arbore RN putem afirma următoarele:
- are la bază operaţiunea de inserare într-un arbore binar de
căutare neechlibrat;
- re-echilibrarea se face prin recolorări şi/sau rotaţii;
- în cel mai defavorabil caz se face o rotaţie (simplă sau dublă);
- complexitatea operaţiunii este dată de înălţimea arborelui
deoarece se parcurge „dus-întors” un drum de la rădăcină până
la un nod de tip frunză sau căruia îi lipseşte unul dintre fii, la
care urmează să fie ataşat noul nod. Deci complexitatea este
O(log n).

118
5.4.3 Eliminarea unei chei dintr-un arbore RN

Ca şi în cazul arborilor de tip AVL, această operaţiune are la bază


funcţia de eliminare dintr-un arbore binar de căutare neechilibrat (vezi
din nou capitolul 4.2.3!). şi este principial asemănătoare cu cea de
inserare, în sensul că după eliminare (după apelul recursiv) verifică tot
„de jos în sus” apariţia unor eventuale alterări ale regulilor RN şi reface
proprietăţile de arbore RN prin rotaţii şi/sau recolorări.

Algoritmul de eliminare a unei chei dintr-un arbore RN

Este un algoritm destul de complex datorită numărului destul de


mare de cazuri (configuraţii) care apar. Este important să reamintim
faptul că din arborele binar sunt eliminate efectiv numai noduri care au
cel mult un fiu!
Vom prezenta cazul în care nodul eliminat este fiul din stânga al
unui nod (cazul în care nodul eliminat este fiul din dreapta este simetric
acestuia).

Cu această ipoteză, cazurile care apar sunt următoarele:

A) dacă nodul eliminat este roşu, atunci nu apare vreo alterare a


unei reguli RN, deci nu trebuie efectuate corecţii!

B) dacă nodul eliminat este negru, atunci este alterată regula 4


deoarece scade înălţimea neagră pentru întreg subarborele cu
rădăcina în nodul eliminat! Şi în aceasta situaţie există două
posibilităţi:
a. unicul fiu al nodului eliminat este roşu: în acest caz
corecţia este foarte simplă: se colorează acest fiu în
negru, ceea ce reface înălţimea neagră în subarborele
respectiv!
b. unicul fiul al nodului eliminat este negru (aceasta are loc
şi în cazul în care nodul eliminat nu are fii). În acest caz
apar mai multe cazuri care au în vedere culorile nodului
tată, şi dacă există un nod frate, culoarea acestuia şi
culorile nodurilor nepoţi din stânga şi din drepta (fiii
nodului frate) pe care îi are nodul eliminat. Pentru
ilustrarea acestor 4 noţiuni vezi figura 5.19:

119
nod tată

nod eliminat nod frate

nod nepot nod nepot


din stânga din dreapta

Fig. 5.19 Ilustrarea noţiunilor de nod tată, nod


frate, nod nepot din dreapta şi nod nepot din
stânga relative la un nod eliminat.

Sunt 11 cazuri posibile; din „fericire” unele dintre ele au aceeaşi


rezolvare, ceea ce face să existe şapte cazuri de tratat. În primele două
am presupus că nu există nodul frate:
 Cazul I: nodul tată colorat cu roşu şi nodul frate inexistent.
Refacerea proprietăţii de arbore RN se face colorând nodul
tată cu negru.

 Cazul II: nodul tată colorat cu negru şi nodul frate inexistent.


În acest caz pentru întreg subarborele cu rădăcina în nodul
tată înălţimea neagră a scăzut cu 1. Alterarea regulii 4 este
transferată nivelului superior (tratare similară cu una dintre
situaţiile de la inserarea unei chei într-un arbore RN);

În următoarele cinci cazuri nodul frate există!

 Cazul III: nodul nepot din dreapta colorat cu roşu, nodurile


tată şi nepot din stânga colorate oricum iar nodul frate este
evident colorat cu negru (având un fiu roşu!).
Refacerea proprietăţilor RN se face în acest caz prin
următoarele operaţiuni:
- o rotaţie simplă spre stânga în raport cu nodul tată;
- interschimbarea culorilor nodurilor care în urma
rotaţiei sunt la adresele nodului tată şi a nodului
eliminat;
- recolorarea nodului aflat la adresa unde iniţial era
nodul frate cu culoarea negru.
Rezolvarea acestui caz este ilustrată în figura 5.20.

120
nod roşu Presupunem că din acest arbore
sau negru eliminăm cheia cu valoarea 20.
28
20 35

10 30 51

28 Rezultă configuraţia alăturată. Se


efectuează o rotaţie simplă spre
10 35 stânga în raport cu nodul cu cheia
28.
30 51

Apoi
35 - se interschimbă culorile
nodurilor aflate în urma rotaţiei la
28 51 adresele unde se aflau nodul tată
şi nodul eliminat;
10 30 - se colorează în negru nodul aflat
la adresa unde iniţial se afla nodul
frate.

35

28 51

10 30

Fig. 5.20 Rezolvarea alterării regulii 4 prin eliminarea unei chei în


configuraţia specifică cazului I.

După cum se poate observa şi în figura 5.19, în configuraţia


rezultată:
- toate înălţimile negre au acelaşi valori ca în configuraţia de
dinaintea eliminării;
- nodul rădăcină şi-a păstrat culoarea, deci nu se pune
problema alterării regulii 3.
Rezultă că şi în acest caz, dacă operaţiunile au fost efectuate
într-un subarbore al unui arbore RN în care s-a efectuat
eliminarea unei chei au fost restabilite toate proprietăţile la
nivelul întregului arbore RN!
121
 Cazul IV: nodul nepot din dreapta colorat cu negru, nodul
tată colorat oricum, nodul nepot din stânga colorat cu roşu iar
nodul frate este evident colorat cu negru (având un fiu roşu!).
Refacerea proprietăţilor RN se face în acest caz prin
următoarele operaţiuni:
- o rotaţie dublă spre stânga în raport cu nodul tată;
- copierea culorii nodului care a fost iniţial nodul tată în
culoarea nodului ajuns după rotaţie la adresa (în
poziţia) nodului tată;
- atribuirea culorii negru nodului care iniţial a fost nodul
tată, ajuns acum la adresa nodului eliminat.
Rezolvarea acestui caz este ilustrată în figura 5.20:

Presupunem că din acest


28 nod roşu arbore eliminăm cheia cu
sau negru valoarea 20. Am notat cu 1
20 35 subarborele stâng al nodului
nepot din stânga şi cu 2
10 30 51 subarborele drept al nepotului
din stânga. Aceştia sunt
necesari în desen pentru a
1 2 justifica corectitudinea
0 0 metodei.

II
28 Rezultă configuraţia alăturată.
I
10 Se efectuează o rotaţie dublă
35 spre stânga în raport cu nodul
cu cheia 28.
30 51

1 2
0 0

30 Se atribuie noii rădăcini


culoarea nodului aflat iniţial în
28 35 rădăcină;

10 1 2 51
0 0

122
Apoi se colorează în negru
30 nodul aflat la la adresa unde
iniţial se afla nodul eliminat.
28 35

10 1 2 51
0 0

În configuraţia rezultată:
30 - toate înălţimile negre au
acelaşi valori ca în configuraţia
28 35 de dinaintea eliminării (inclusiv
la cei doi subarbori notaţi cu 1
10 2 şi 2);
1 51
0 - nodul rădăcină şi-a păstrat
0
culoarea.
Fig. 5.21 Rezolvarea alterării regulii 4 prin eliminarea unei chei
în configuraţia specifică cazului II.

Şi în acest caz se observă că în configuraţia rezultată:


- toate înălţimile negre au acelaşi valori ca în configuraţia de
dinaintea eliminării;
- nodul rădăcină şi-a păstrat culoarea, deci nu se pune
problema alterării regulii 3.
Rezultă că şi în acest caz, dacă operaţiunile au fost efectuate
într-un subarbore al unui arbore RN în care s-a efectuat
eliminarea unei chei au fost restabilite toate proprietăţile la
nivelul întregului arbore RN!

Înainte de a prezenta celelalte trei cazuri, observăm că primele două


cazuri analizate au epuizat toate configuraţiile în care cel puţin unul
dintre nodurile nepoţi era colorat cu roşu. Deci în cazurile rămase,
nodurile nepoţi sunt colorate cu negru!

 Cazul V: nodul tată colorat roşu, nodul frate colorat cu negru


(iar nodurile nepoţi colorate cu negru).
Refacerea proprietăţilor RN se face în acest caz prin:
- interschimbarea culorilor nodurilor tată şi frate;
Rezolvarea acestui caz este ilustrată în figura 5.22:

30
123
28
Presupunem că din acest
28 arbore eliminăm cheia cu
valoarea 20.
20 35

10 30 51

28
Rezultă configuraţia alăturată.
10 35 Se interschimbă culorile
nodurilor cu cheile 28 (nodul
tatăl) cu nodul cu cheia 35
30 51 (nodul frate).

28
10 35 În configuraţia rezultată:
- toate înălţimile negre au
acelaşi valori ca în configuraţia
30 51 de dinaintea eliminării;

Fig. 5.22 Rezolvarea alterării regulii 4 prin eliminarea unei chei


în configuraţia specifică cazului III.
Şi în acest caz se observă că în configuraţia rezultată:
- toate înălţimile negre au acelaşi valori ca în configuraţia de
dinaintea eliminării;
- nodul rădăcină a fost colorat în negru deci nu se pune
problema alterării regulii 3.
Rezultă că şi în acest caz, dacă operaţiunile au fost efectuate
într-un subarbore al unui arbore RN în care s-a efectuat
eliminarea unei chei au fost restabilite toate proprietăţile la
nivelul întregului arbore RN!

Se impune să mai facem o observaţie şi anume că prin primele trei


cazuri se rezolvă la nivelul întregului arbore alterarea regulii 4 care
apare în urma eliminării unei chei în toate cazurile când nodul tată al
nodului eliminat este colorat cu roşu.

 Cazul VI: nodul tată colorat cu negru, nodul frate colorat cu


negru (iar nodurile nepoţi colorate cu negru sau inexistente).
Refacerea proprietăţilor RN se face în acest caz prin:
- schimbarea culorii nodului frate în roşu.
124
Aceasta are ca efect micşorarea cu 1 a tuturor înălţimilor
negre din arbore, deci la nivel local regula 4 este restabilită
dar dacă era vorba de un subarbore, alterarea regulii 4 este
transferată cu un nivel mai sus (situaţie similară cu cea din
cazul I de la inserare!) şi întreaga analiză se transferă la
nivelul superior. Astfel este posibil ca această transferare a
alterării regulii 4 să se reporteze până la nivelul nodului
rădăcină când această modificare restabileşte proprietăţile
RN pentru întregul arbore.
Rezolvarea acestui caz este ilustrată în figura 5.23:
Presupunem că din acest arbore
eliminăm cheia cu valoarea 20.
28

20 35

10 30 51

28 Rezultă configuraţia alăturată.


10 Se schimbă culoarea nodului cu
35
cheia 35 (nodul frate).

30 51

În configuraţia rezultată:
- toate înălţimile negre ale
28 subarborelui cu rădăcina în
nodul cu cheia 28 au o valoare
10 35 mai mică cu 1 decât în
configuraţia de dinaintea
30 51 eliminării; se transmite la nivelul
superior alterarea regulii 4.
Fig. 5.23 Rezolvarea alterării regulii 4 prin eliminarea unei chei în
configuraţia specifică cazului IV.

 Cazul VII: nodul tată colorat cu roşu, nodul frate colorat cu


negru (iar nodurile nepoţi colorate cu negru).
Refacerea proprietăţilor RN se face în acest caz printr-o
rotaţie simplă spre stânga în raport cu nodul tată ceea ce are
ca efect transferarea alterării regulii 4 la nivelul subarborelui
nou creat care are rădăcina la adresa unde se afla nodul
eliminat. La această adresă se va afla în urma rotaţiei nodul
125
care iniţial era rădăcină, deci un nod roşu (aşa cum se
observă şi în figura 5.24). Deci configuraţia va corespunde
unuia dintre cazurile I, II sau III şi aplicând corecţiile
corespunzătoare, operaţiunile efectuate în aceste cazuri refac
proprietăţile RN la nivelul întregului arbore!

Presupunem că din acest


arbore eliminăm cheia cu
28 valoarea 20.
20 35

10 30 51

28
10 Rezultă configuraţia alăturată.
35
Se efectuează o rotaţie
simplă spre stânga în raport
30 51 cu nodul cu cheia 28.

35
În configuraţia rezultată
28 51 pentru subarborele cu
rădăcina în nodul cu cheia 28
10 30 se observă că:
- înălţimea neagră a
subarborelui stâng a scăzut
cu 1;
- înălţimea subarborelui drept
s-a păstrat;
Se aplică corecţiile specifice
cazurilor III, IV sau V având
în vedere că rădăcina
subarborelui din chenar este
roşie!

Fig. 5.24 Rezolvarea alterării regulii 4 prin eliminarea unei


chei în configuraţia specifică cazului V.

126
În concluzie relativ la operaţiunea de ştergere a unei chei dintr-un
arbore RN putem afirma următoarele:
- are la bază operaţiunea de ştergere dintr-un arbore binar de
căutare neechilibrat;
- re-echilibrarea se face prin recolorări şi/sau rotaţii;
- în cel mai defavorabil caz se fac trei rotaţii (cazul numerotat cu
VII);
- complexitatea operaţiunii este dată de înălţimea arborelui
deoarece se parcurge „dus-întors” un drum de la rădăcină până
la un nod care poate fi eliminat (de tip frunză sau căruia îi
lipseşte unul dintre fii). Deci complexitatea este O(log n).

Implementarea algoritmul de eliminare a unei chei dintr-un arbore


RN

Funcţia de eliminare pe care o prezentăm are la bază funcţia de


inserare într-un arbore binar de căutare (prezentată în subcapitolul
4.2.3). La fel ca la funcţia de inserare într-un arbore AVL, după apelul
recursiv trebuie verificat dacă nu a fost deteriorată structura arborelui
RN prin încălcarea regulii 4. Pentru aceste verificări trebuie să ştim ce
culori au nodurile aflate pe drumul urmat de funcţia de inserare şi aflate
cu două niveluri mai jos faţă de nodul curent (nodul frate al nodului
eliminat şi nodurile nepoţi) şi dacă a apărut sau nu vreo alterare a
regulilor RN. Din acest motiv funcţia de inserare pe care o prezentăm
returnează o valoare de tip int care oferă informaţii referitoare la o
posibilă alterare a regulii 4, şi anume:
- valoarea 0: înălţimea neagră a subarborelui în care s-a făcut
eliminarea nu s-a modificat, deci nu este necesară o corecţie;
- valoarea -1: înălţimea neagră a subarborelui în care s-a făcut
eliminarea a scăzut cu 1, deci se impune efectuarea de coreţii;
Pentru a simplifica codul funcţiei de eliminare, corecţiile care trebuie
efectuate corespunzătoare celor şapte cazuri prezentate mai înainte au
fost implementate într-o funcţie separată denumită reparastRN. Şi
această funcţie returnează o valoare de tip int şi anume:
- valoarea 0 dacă în urma corecţiei efectuate întreg arborele RN
este corect;
- valoarea -1 dacă corecţia aplicată a rezolvat alterarea regulii 4
local, dar alterarea a fost reportată nivelului superior:

GENERIC int reparastRN(nod<T> * &parinte)


//repara RN dupa eliminare spre stanga
//cand fiul celui eliminat este negru
{nod<T> *fiudr,*nepotst,*nepotdr; //toti din dreapta
127
culoare culfiudr,culnepotst,culnepotdr,culparinte;
culparinte = retculoare(parinte);
fiudr = parinte->dr;
if (fiudr == NULL) //nu exista fratele
if (culparinte == rosu) //cazul I
{ parinte->cul = negru;
return 0;
}
else //cazul II: culparinte == negru
return -1;
//exista fratele
culfiudr = retculoare(fiudr);
nepotdr = fiudr->dr;
nepotst = fiudr->st;
culnepotdr = retculoare(fiudr->dr);
culnepotst = retculoare(fiudr->st);
if (culnepotdr == rosu) //cazul III
{ rotsst(parinte);
swap(parinte->cul, parinte->st->cul);
parinte->dr->cul = negru;
return 0;
}
if (culnepotst == rosu) //cazul IV
{ rotdst(parinte);
parinte->cul = parinte->st->cul;
parinte->st->cul = negru;
return 0;
}
if (culparinte == rosu && culfiudr == negru)
{ swap(parinte->cul, fiudr->cul); //cazul V
return 0;
}
if (culparinte == negru && culfiudr == negru)
{
fiudr->cul = rosu; //cazul VI
return -1;
}
//cazul VII
swap(parinte->cul, fiudr->cul);
rotsst(parinte);
int rez = reparastRN(parinte->st);
return rez;
}

128
Funcţia care aplică corecţiile necesare restaurării proprietăţilor RN
după eliminarea unei chei din subarborele drept a fost denumită
reparadrRN şi este simetrica celei prezentate mai înainte. Invităm
cititorii să scrie codul acestei funcţii cu antet-ul:

GENERIC int reparadrRN(nod<T> * &parinte)


//repara RN dupa eliminare spre dreapta
//cand fiul celui eliminat este negru
{...
}

Funcţia care implementează eliminarea unei chei este următoarea:

GENERIC int eliminareRN(nod<T> *&nodcrt, T cheie)


{if (nodcrt==NULL)
{ cout << "valoarea nu este in arbore\n";
return 0;
}
if (nodcrt->info > cheie) //eliminare în stânga
{ int dezechilibru = eliminareRN(nodcrt->st,cheie);
if (dezechilibru ==0) return 0;
if (retculoare(nodcrt->st) == rosu)
{ //fiul celui eliminat este rosu
nodcrt->st->cul = negru;
return 0;
}
int rez = reparastRN(nodcrt);
return rez;
}
else //eliminare în dreapta
if (nodcrt->info < cheie)
{ int dezechilibru=eliminareRN(nodcrt->dr,cheie);
if (dezechilibru ==0) return 0;
if (retculoare(nodcrt->dr) == rosu)
{ nodcrt->dr->cul = negru;
return 0;
}
int rez = reparadrRN(nodcrt);
return rez;

}
else //cheia apare in nodul curent
{//cazul 1: frunza
if (nodcrt->st == NULL && nodcrt->dr == NULL)
{ culoare culeliminat = nodcrt->cul;
129
delete nodcrt;
nodcrt = NULL;
if (culeliminat == negru)
return -1; // deficit de noduri negre
else
return 0;
}
//cazul 2: lipseste subarborele stang
if (nodcrt->st == NULL && nodcrt->dr != NULL)
{ culoare culeliminat = nodcrt->cul;
nod<T> *aux = nodcrt->dr;
delete nodcrt;
nodcrt = aux;
if (culeliminat == negru)
return -1;
else
return 0;
}
//cazul 2: lipseste subarborele drept
if (nodcrt->st != NULL && nodcrt->dr == NULL)
{ culoare culeliminat = nodcrt->cul;
nod<T> *aux = nodcrt->st;
delete nodcrt;
nodcrt = aux;
if (culeliminat == negru)
return -1;
else
return 0;
}
//cazul 3: ambii subarbori prezenti
T cheieaux = minimarb(nodcrt->dr);
nodcrt->info = cheieaux;
cout << "se elimina "<<cheieaux << endl;
int dezechilibru;
dezechilibru=eliminareRN(nodcrt->dr,cheieaux);
if (dezechilibru == 0) return 0;
if (retculoare(nodcrt->dr)== rosu)
{ nodcrt->dr->cul = negru;
return 0;
}
int rez = reparadrRN(nodcrt);
return rez;
}
}

130
5.5 Îmbogăţirea structurilor de date

În ingineria programării sunt rare situaţiile în care structurile de


date prezentate anterior (tablouri, arbori binari, liste simple sau duble)
se pot folosi aşa cum au fost prezentate. Pe de altă parte, sunt rare şi
situaţiile în care trebuie proiectată o structură complet diferită de cele
uzuale. De cele mai multe ori, diversele probleme care apar se rezolvă
adăugând câmpuri suplimentare la informaţia reţinută de o structură de
date standard. Acest procedeu de a adăuga informaţii suplimentare la
fiecare element al unei structuri de date este cunoscut sub numele de
îmbogăţirea unei structuri de date.

În cele ce urmează vom prezenta un exemplu foarte simplu care


ilustrează acest concept. Vom presupune că avem un şir de chei şi că
acestea se pot repeta. Ne interesează să reţinem cheile din şir într-o
structură de date care să optimizeze următoarele operaţiuni:
 inserarea unei chei în şir;
 eliminare a unui număr de chei egale din şir;
 determinarea numărului de apariţii ale unei chei în şir.
O structură de date care oferă posibilitatea implementării acestor
operaţii cu o complexitate logaritmică în numărul de elemente din şir
este un arbore binar echilibrat. Să presupunem că alegem metoda
AVL. Nodurile acestuia vor reţine pe lângă informaţiile prezentate în
capitolele anterioare (cheia, pointerii spre fiul drept şi fiul stâng şi
înălţimea respectivului nod) şi numărul de apariţii ale cheii respective în
şirul de valori. Algoritmii de inserare, căutare şi eliminare din arbore nu
suferă modificări esenţiale:
 funcţia de inserare va conţine o acţiune suplimentară şi
anume, atunci când cheia pe care vrem să o inserăm apare
deja în arbore vom incrementa numărul de apariţii ale
acesteia;
 funcţia de eliminare în momentul în care a localizat nodul
care reţine cheia va compara numărul de apariţii ale
acesteia cu numărul de „exemplare” care trebuie eliminat.
Dacă numărul de apariţii este mai mare decât numărul de
valori de eliminat, atunci se face o simplă ajustare a
câmpului care reţine numărul de apariţii. În caz contrar, se
intră pe ramura care face eliminarea unui nod din arbore.

Din punct de vedere al implementării structurii propriu-zise,


avem diverse posibilităţi:

131
 să definim un tip structură cu două câmpuri: cheie şi număr
de apariţii. Programele din subcapitolul 5.3 fiind generice
d.p.d.v. al tipului cheii vor folosi modificări minore, însă va
trebui să redefinim operatorii de comparaţie pentru tipul
structură nou declarat.
 să folosim pentru cheie tipul pair;
 să adăugăm un câmp nou pentru numărul de apariţii în
structura denumită nod utilizată în subcapitolul 5.3:

132
5.6 Containerul set

În numeroase aplicaţii, datele cu care se lucrează este util să fie


văzute ca fiind elementele unei mulţimi iar cele trei operaţiile operaţii
fundamentale ce se pot efectua asupra unei structuri de date
(inserarea, căutarea şi eliminarea unei chei) să fie văzute ca
includerea, testul de apartenenţă şi respectiv excluderea unui element
dintr-o mulţime. Datorită complexităţii logaritmice pe care o conferă
arborii binari echilibraţi pentru aceste trei operaţii fundamentale în
ansamblul de biblioteci STL a fost creată şi o bibliotecă care
implementează mulţimi având la bază arborii roşu-negru. Containerul
a fost denumit set.

5.6.1. Prezentarea generală a variabilelor de tip set

Variabilele de tip set sunt arbori binari de căutare echilibraţi de


tip roşu-negru. Respectând teoria mulţimilor, aceeaşi valoare nu
poate să apară de două ori într-o variabil de tip set.
Variabilele de tip set conţin nodurile unui arbore RN, diverse
informaţii referitoare la listă (de exemplu adresa „primului” nod din
arbore (nodul cu valoarea cea mai mică), numărul de elemente etc)
precum şi o serie de funcţii care implementează operaţiile
fundamentale asupra unei structuri de date (eliminare, inserare,
adăugare etc.).
Din punct de vedere al accesului pe care îl avem la elementele
unei variabile de tip set, singura informaţie pe care o putem accesa
este informaţia propriu-zisă (sau cheia) reţinută de nodurile arborelui.
Legăturile dintre noduri ne sunt inaccesibile. Aceste legături se
modifică automat în momentul în care apelăm funcţii care modifică
structura arborelui.
Parcurgerea unei variabile de tip set se face cu iteratori. În
cazul variabilelor de tip set, ei pot fi direcţi (pentru parcurgere de la
„stânga la dreapta”) sau inverşi/reversibili (pentru parcurgere de la
„dreapta la stânga”). Pentru a trece de la un element la altul aceşti
pointeri pot fi incrementaţi sau decrementaţi. În schimb nu este definită
operaţia de adunare între un iterator şi un număr, aceasta deoarece
elementele unui arbore nu ocupă poziţii consecutive în memorie.

133
5.6.2. Utilizarea variabilelor de tip set

În primul rând trebuie inclusă biblioteca corespunzătoare din STL


denumită set:
#include <set>

Apoi se declară variabile de tip set cu sintaxa:


set <tip_elemente> var1,var2,...,varn;

Variabilele de tip mulţime vor avea numele din declaraţia anterioară iar
elementele acestor mulţimi, care vor constitui cheile nodurilor arborilor
vor avea informaţia propriu-zisă de tipul precizat între parantezele
unghiulare. Deoarece într-o structură de tip arbore se consideră că
există o relaţie de ordine definită pentru cheile reţinute, pentru tipul
precizat între parantezele unghiulare trebuie să fie definit operatorul de
comparaţie <.
În urma unei astfel de declaraţii, mulţimile reţinute de aceste variabile
vor fi vide.

De exemplu:
set <int> v1;
reprezintă declararea unei variabile v1 care conţine o mulţime de
valori de tip int

set <double> a,b;


reprezintă dedclararea a două variabile denumite a şi b care conţin
fiecare câte o mulţime de valori reale

set <int> A[100];


reprezintă declararea unei variabile A care este un tablou de 100 de
mulţimi.

Există şi alte posibilităţi de declarare, de exemplu:

set <tip_elemente> nume(adrbegin,adrend);


Prin această declarare, variabila declarată va conţine toate elementele
aflate în memorie între adresele adrbegin (inclusiv) şi adrend
(exclusiv). Tipul elementelor aflate între cele două adrese trebuie să fie
acelaşi cu cel declarat între parantezele unghiulare.
De exemplu:

int x[] = {10,20,40,20,30};


set <int> v2(x,x+5);

134
este declararea unei variabile denumită v2 care conţine o mulţime cu
patru elemente: 10, 20, 30, 40.

5.6.3. Operaţii implementate pentru variabile de tip set

 atribuire. Acesta este permise numai pentru mulţimi care


conţine elemente de acelaşi fel. Pentru exemplele anterioare,
putem scrie în program instrucţiunea:
v1 = v2;
prin aceasta se face atât alocarea de memorie pentru
elementele mulţimii din v1 cât şi atribuirile corespunzătoare.
În schimb atribuirea
a = v2;
este eronată.

 comparări cu operatorii ==, !=, >, >= etc. Şi acestea sunt


permise numai pentru mulţimi care conţin elemente de
acelaşi fel. Compararea se face lexicografic presupunând
elementele fiecărei mulţimi dispuse în ordine crescătoare.

D. Funcţii conţinute de o variabilă de tip set

Oricare dintre aceste funcţii se apelează cu sintaxe de genul:


variabila_list.nume_funcţie(parametri)

begin()
Returnează un pointer la primul element din mulţime.

end()
Returnează un pointer la sfârşitul mulţimii (adresa de după ultimul
element din mulţime).

rbegin()
Returnează un pointer la ultimul element din mulţime.

rend()
Returnează un pointer „capătul din stânga” al mulţimii (util pentru
parcurgerea în sens invers)

size()
Returnează numărul de elemente din mulţime (valoare de tip
unsigned)
135
max_size()
Returnează numărul maxim de elemente pe care le-ar putea avea
mulţimea.

empty()
Testează dacă mulţimea este vidă.

Toate aceste funcţii au complexitatea este O(1).

insert(val)
Inserează un element care va conţine valoarea val în mulţime.

erase(val)
Elimină elementul cu val din mulţime. În versiunile C++11 returnează
un iterator la elementul aflat după elementul eliminat sau valoarea
egală cu valoarea funcţiei end() dacă elementul eliminat era ultimul.
În versiunile anterioare nu returnează vreo valoare.

find(val)
Caută valoarea val în mulţime. Returnează o valoare egală cu
iteratorul la nodul care reţine elementul respectiv când val apare în
mulţime sau iteratorul de la sfârşitul mulţimii (valoare egală cu valoarea
returnată de funcţia end()) când val nu este în mulţime.
Aceste trei funcţii au complexitatea O(log n) (n=numărul de elemente
din mulţime).

5.6.4. Accesarea elementelor unei mulţimi

Parcurgea unei mulţimi se poate face prin iteratori. Aceştia se pot


declara astfel:
 în cazul unui iterator direct:
set <tip_elem>::iterator nume_iterator;
 în cazul unui iterator reversibil (care ne oferă posibilitatea să
parcurgem mulţimea în sens invers):
set<tip_elem>::reverse_iterator nume_iterat;

Dacă dorim să declarăm funcţii în care tipul informaţiei reţinută în


mulţime este generic, atunci declarările precedente trebuie precedate
de cuvântul rezervat typename. Astfel iteratorul direct îl vom declara
astfel:
typename set <tip_elem>::iterator nume_iterat;

136
Vom exemplifica utilizarea iteratorilor prin declararea a două funcţii
pentru afişarea informaţiilor conţinute de o mulţime. În prima funcţie
vom parcurge mulţimea în ordinea crescătoare a valorilor (practic vom
parcurge arborele binar în inordine) iar în a doua funcţie vom
parcurge mulţimea în sens invers.

template <class tipelem>


void afisaredir(set <tipelem> v)
//in ordine crescatoare
{ typename set <tipelem> :: iterator it;
for (it = v.begin(); it !=v.end(); it++)
cout << *it << ' ';
cout << endl;
}

template <class tipelem>


void afisareinv(set <tipelem> v)
//in ordine descrescatoare
{ typename set <int> :: reverse_iterator it;
for (it = v.rbegin(); it !=v.rend(); it++)
cout << *it << ' ';
cout << endl;
}

O altă posibilitate de a declara iteratori este să utilizăm declaraţii auto.


Acestea au sintaxe de genul:
auto variabila = expresie;
Se observă că nu trebuie să precizăm tipul variabilei declarate. Acesta
rezultă „automat” din tipul valorii expresiei!
Revenind la containerele pentru mulţimi, dacă vrem să declarăm un
iterator pentru a parcurge mulţimea în sens crescător, putem utiliza
sintaxa:
auto it = v.begin();

Astfel putem rescrie prima dintre funcţiile generice care afişează


conţinutul unei mulţimi astfel:

template <class tipelem>


void afisaredir(set <tipelem> v)
{ for (auto it = v.begin(); it !=v.end(); it++)
cout << *it << ' ';
cout << endl;
}

137
5.7 Probleme propuse

Problema 1. Într-un arbore binar de căutare echilibrat prin metoda AVL


sunt inserate conform algoritmului din acest capitol următoarele valori:
132, 185, 157, 15, 166, 169, 100, 56, 65, 109.
Determinaţi
 rădăcina arborelui rezultat;
 frunzele arborelui rezultat;
 înălţimea arborelui rezultat;

Problema 2. Într-un arbore binar de căutare echilibrat prin metoda


roşu-negru sunt inserate conform algoritmului din acest capitol
următoarele valori: 132, 185, 157, 15, 166, 169, 100, 56,
65, 109.
Determinaţi
 rădăcina arborelui rezultat;
 frunzele arborelui rezultat;
 înălţimea arborelui rezultat;

Pentru problemele 3,4 şi 5 se presupune că sunt valabile următoarele


declaraţii pentru tipul nodurilor unui arbore binar de căutare roşu-negru:
enum culoare {negru=0,rosu=1};
template <class T> struct nod
{ T info;
culoare cul;
nod * st, * dr;
};

Problema 3. Scrieţi o funcţie cu un parametru de tip pointer la un nod


dintr-un arbore binar de căutare roşu-negru care să returneze înălţimea
neagră a subarborelui cu rădăcina în nodul dat ca parametru.

Problema 4. Scrieţi o funcţie cu un parametru de tip pointer la un nod


dintr-un arbore binar de căutare roşu-negru care să returneze valoarea
1 dacă subarborele cu rădăcina în nodul dat ca parametru respectă
regula 3 impusă arborilor roşu-negru şi valoarea 0 în caz contrar. Se
reaminteşte că regula 3 impusă arborilor roşu-negru este următoarea:
orice nod colorat roşu are fiii coloraţi cu negru.

Problema 5. Scrieţi o funcţie cu un parametru de tip pointer la un nod


dintr-un arbore binar de căutare roşu-negru care să returneze valoarea

138
1 dacă subarborele cu rădăcina în nodul dat ca parametru respectă
regula 4 impusă arborilor roşu-negru şi valoarea 0 în caz contrar. Se
reaminteşte că regula 4 impusă arborilor roşu-negru este următoarea:
fiecare drum de la rădăcină la o frunză conţine acelaşi număr de noduri
negre.
Indicaţie: se va presupune că orice legătură spre fiul stâng sau fiul
drept a unui nod care are valoarea NULL pointează spre un nod
santinelă negru, deci are înălţimea neagră 1.

Problema 6. Fie următoarea declaraţie pentru nodurile unui arbore


binar de căutare de tip AVL corespunzătoare exemplului dat în
capitolul 5.5.
tempalte <class T> struct nod
{ T info;
short int h, nraparitii;
nod * st, * dr;};
Implementaţi următoarele funcţii:
 inserarea unei chei în arbore;
 eliminare a unui număr de chei egale din arbore;
 determinarea numărului de apariţii ale unei chei în arbore.

Problema 7. Fie următoarea declaraţie pentru nodurile unui arbore


binar de căutare neechilibrat:
tempalte <class T> struct nod
{ T info;
int nrnodurist;
nod * st, * dr;};
în care câmpul nrnodurist reţine numărul de noduri aflate în
subarborele stâng al nodului respectiv.
Scrieţi o funcţie cu doi parametri:
 nodcrt de tip pointer spre un nod;
 n de tip int;
Funcţia va returna a n-a cheie din arbore (cheia aflată în poziţia n în
şirul sortat de chei reţinute în subarborele cu rădăcina în nodul
respectiv). Se garantează că n ≤ numărul de noduri din arbore.

Problema 8. Rescrieţi funcţiile de inserare şi eliminare dintr-un arbore


binar neechilibrat astfel încât acestea să actualizeze în mod
corespunzător câmpul nrnodurist din problema 6.

Problema 9. Rescrieţi funcţiile de rotaţii, inserare şi eliminare dintr-un


arbore binar echilibrat astfel încât acestea să actualizeze în mod
corespunzător câmpul nrnodurist din problema 6.
139

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