You are on page 1of 25

13.

ARBORI B


13.1 Arbori B. Definiie. Proprieti.

n cazul sistemelor de gestiune a bazelor de date relaionale (SGBDR)
este important ca pe lng stocarea datelor s se realizeze i regsirea
rapid a acestora. n acest scop sunt folosii indecii. Un index este o
colecie de perechi <valoare cheie, adresa articol>. Scopul primar al unui
index este acela de a facilita accesul la o colecie de articole. Un index se
spune ca este dens dac el conine cte o pereche <valoare cheie, adresa
articol> pentru fiecare articol din colecie. Un index care nu este dens
uneori este numit index rar.
Structura de date foarte des folosit pentru implementarea indecilor
este arborele de cutare. Articolele memorate pot fi orict de complexe, dar
ele conin un cmp numit cheie ce servete la identificarea acestora. S
notm cu C mulimea cheilor posibile ce vor trebui regsite cu ajutorul
arborelui de cutare. Dac arborele de cutare este astfel construit nct
folosete o relaie de ordine total pe C, atunci vom spune c arborele de
cutare este bazat pe ordinea cheilor. Arborii de cutare, bazai pe ordinea
cheilor, sunt de dou feluri: arbori binari de cutare (au o singur cheie
asociat fiecrui nod) sau arbori multici de cutare (au mai multe chei
asociate fiecrui nod).
Performanele unui index se mbuntesc n mod semnificativ prin
mrirea factorului de ramificare a arborelui de cutare folosit. Arborii
multici de cutare sunt o generalizare a arborilor binari de cutare. Astfel,
unui nod oarecare, n loc s i se ataeze o singur cheie care permite
ramificarea n doi subarbori, i se ataeaz un numr de m chei, ordonate
strict cresctor, care permit ramificarea n m + 1 subarbori. Numrul m
difer de la nod la nod, dar n general pentru fiecare nod trebuie s fie ntre
anumite limite (ceea ce va asigura folosirea eficient a mediului de stocare).
Cele m chei ataate unui nod formeaz o pagin. Determinarea poziiei cheii
cutate n cadrul unui nod se realizeaz secvenial n cazul paginilor cu
numr mic de chei sau prin cutare binar. Un exemplu de arbore multici
de cutare de ordin 3 este dat n figura 13.1.


50 100

20 40

120 140

70

15
30

42 43 60 65

110 130 136 150

7 18 26 36 69 62 58
145


Fi gur a 13. 1 Arbore multici de cutare de ordin 3

Un arbore multici de cutare care nu este vid are urmtoarele
proprieti:
- fiecare nod al arborelui are structura dat n figura 13.2;


n P
0
K
0
P
1
K
1
P
2
P
n -
1
K
n - 1
P
n


Fi gur a 13. 2 Structura de nod pentru un arbore multici de cutare
de ordin n

unde P
0
, P
1
, , P
n
sunt pointeri ctre subarbori i K
0
, K
1
, , K
n 1

sunt valorile cheilor. Cerina ca fiecare nod s aib un numr de
ramificaii mai mic sau egal dect m conduce la restricia n m
1.
- valorile cheilor ntr-un nod sunt date n ordine cresctoare:

K
i
< K
i + 1
, 2 , 0 = n i (13.1)

- toate valorile de chei din nodurile subarborelui indicat de P
i
sunt
mai mici dect valoarea cheii K
i
, 1 , 0 = n i .
- toate valorile de chei din nodurile subarborelui indicat de P
n
sunt
mai mari dect valoarea de cheie K
n 1
.
- subarborii indicai de P
i
, n i , 0 = sunt de asemenea arbori multici
de cutare.

Prima oar arborele B a fost descris de R. Bayer i E. McCreight n
1972. Arborii B rezolv problemele majore ntlnite la implementarea
arborilor de cutare stocai pe disc:
- au ntotdeauna toate nodurile frunz pe acelai nivel (cu alte
cuvinte sunt echilibrai dup nlime);
- operaiile de cutare i actualizare afecteaz puin blocuri pe disc;
- pstreaz articolele asemntoare n acelai bloc pe disc;
- garanteaz ca fiecare nod din arbore va fi plin cu un procent
minim garantat.
Un arbore B de ordin m este un arbore multici de cutare i are
urmtoarele proprieti:
i. toate nodurile frunz sunt pe acelai nivel;
ii. rdcina are cel puin doi descendeni, dac nu este frunz;
iii. fiecare pagin conine cel puin
(
(
(

2
m
chei (excepie face rdcina
care poate avea mai puine chei, dac este frunz);
iv. nodul este fie frunz, fie are n + 1 descendeni (unde n este
numrul de chei din nodul respectiv, cu
(
(
(

2
m
n m-1 );
v. fiecare pagin conine cel mult m-1 chei; din acest motiv, un nod
poate avea maxim m descendeni.
Proprietatea i menine arborele balansat. Proprietatea ii foreaz
arborele s se ramifice devreme. Proprietatea iii ne asigur c fiecare nod al
arborelui este cel puin pe jumtate plin.
nlimea maxim a unui arbore B d marginea superioar a
numrului de accese la disc necesare pentru a localiza o cheie.

Se consider h nlimea maxim a unui arbore B cu N chei, unde
valoarea indicatorului este dat de relaia:

|
.
|

\
| +
=
(
(
(

2
1
log
2
N
h
m
(13.2)

Ca exemplu, pentru N = 2.000.000 i m = 20, nlimea maxim a
unui arbore B de ordin m va fi 3, pe cnd un arborele binar corespondent va
avea o nlime mai mare de 20.


13.2 Operaii de baz ntr-un arbore B

Procesul de cutare ntr-un arbore B este o extindere a cutrii ntr-
un arbore binar. Operaia de cutare n arborele B se realizeaz comparnd
cheia cutat x cu cheile nodului curent, plecnd de la nodul rdcin. Dac
nodul curent are n chei, atunci se disting urmtoarele cazuri:
- c
i
< x < c
i +1
, n i , 1 = se continu cutarea n nodul indicat de P
i
;
- c
n
< x se continu cutarea n nodul indicat de P
n
;
- x < c
0
se continu cutarea n nodul indicat de P
0
.
Lungimea maxim a drumului de cutare este dat de nlimea
arborelui. Fiecare referire a unui nod implic selecia unui subarbore.
Arborele B suport cutarea secvenial a cheilor. Arborele este
traversat secvenial prin referirea n inordine a nodurilor. Un nod este referit
de mai multe ori ntruct el conine mai multe chei. Subarborele asociat
fiecrei chei este referit nainte ca urmtoarea cheie sa fie accesata. Arborii
B sunt optimi pentru accesul direct la o cheie. Pentru accesul secvenial la o
cheie nu se obin performane satisfctoare.
Condiia ca toate frunzele s fie pe acelai nivel duce la un
comportament caracteristic arborilor B: fa de arborii binari de cutare,
arborilor B nu le este permis s creasc la frunze; ei sunt forai sa creasc
la rdcin.
Operaia de inserare a unei chei n arborele B este precedat de
operaia de cutare. n cazul unei cutri cu succes (cheia a fost gsit n
arbore) nu se mai pune problema inserrii ntruct cheia se afla deja n
arbore. Dac cheia nu a fost gsit, operaia de cutare se va termina ntr-
un nod frunz. n acest nod frunz se va insera noua cheie. Funcie de
gradul de umplere al nodului frunz afectat, se disting urmtoarele cazuri:
- nodul are mai puin de m 1 chei; inserarea se efectueaz fr s
se modifice structura arborelui ;
- nodul are deja numrul maxim de m 1 chei; n urma inserrii
nodul va avea prea multe chei, de aceea el va fisiona. n urma
fisionrii vom obine dou noduri care se vor gsi pe acelai nivel
i o cheie median care nu se va mai gsi n nici unul din cele
dou noduri. Cele
(
(
(

2
m
chei din stnga rmn n nodul care
fisioneaz. Cele
(
(
(

2
m
din dreapta vor forma cel de-al doilea nod.
Cheia median va urca n nodul printe, care la rndul lui poate s

Se observ c procesul de inserare a unei chei garanteaz c fiecare
nod intern va avea cel puin jumtate din numrul maxim de descendeni.
n urma operaiilor de inserare arborele va deveni mai nalt i mai lat, figura
13.3.




Fi gur a 13. 3 Modificrile dimensionale ale unui arbore B
dup efectuarea inserrii

S considerm un arbore B de ordin 5 (deci numrul maxim de chei
dintr-un nod va fi 4). n urma inserrii valorilor de cheie 22, 57, 41, 59
nodul rdcin va fi cel din figura 13.4.

22 41 57 59

Fi gur a 13. 4 Structura nodului rdcin dup inserarea cheilor

n urma inserrii cheii 54, nodul rdcin va conine prea multe chei,
aa c el va fisiona, figura 13.5.


22 41 54 57 59 22 41 57 59
cheia mediana
54 va promova

Fi gur a 13. 5 Fisionarea nodului rdcin

n urma promovrii cheii mediene 54 se va forma o nou rdcin,
arborele crescnd n nlime cu 1, figura 13.6.

54

22 41 57 59
a
b c


Fi gur a 13. 6 Formarea noii rdcini a arborelui

Inserarea cheilor 33, 75, 124 nu ridic probleme, figura 13.7.


54

22 33 57 59 41 75
124
a
b c


Fi gur a 13. 7 Structura arborelui dup inserarea cheilor 33, 75, 124

Inserarea cheii 62 ns duce la divizarea nodului c, figura 13.8.


57

59 62

75
c
124 57 59 75

124
Cheia mediana 62
va promova
c d


Fi gur a 13. 8 Fisionarea nodului c

Cheia 62 va promova n nodul rdcin, figura 13.9.


54

22 41 57 59
62

75
124
b
c
d
a


Fi gur a 13. 9 Promovarea cheii 62 n rdcin

n urma inserrii cheilor 33, 122, 123, 55, 60, 45, 66, 35 configuraia
arborelui va fi cea din figura 13.10.

54 35
122
62
22 33 41 45 55 57 59 60 66 75
123 124
b
a
f d
e c


Fi gur a 13. 10 Structura arborelui dup inserri succesive

Inserarea cheii 56 se va face n nodul c i acesta va fisiona, figura
13.11.

55 56 57 59
c
60 55 56 59 60
Cheia mediana 57
va promova
c g


Fi gur a 13. 11 Fisionarea nodului c


Oricum, nodul printe a este deja plin i nu poate primi noua cheie 57
i pointerul ctre nodul nou format f. Algoritmul de fisionare este aplicat din
nou, dar de data aceasta nodului a, figura 13.12.


35 54 57 62
a
122 35 54 62
122
Cheia mediana 57
va promova
a h


Fi gur a 13. 12 Fisionarea nodului a

Cheia median promovat 57 va forma noua rdcin a arborelui B,
avnd subarborele stng a i subarborele drept h, figura 13.13.

57
i
35 54 62
122
22 33 41 45 55 56 59 60 66 75
123 124
b
a h
f c g d e


Fi gur a 13. 13 Noua configuraie a arborelui B

Uzual, n special pentru arbori B de ordin mare, un nod printe are
suficient spaiu disponibil pentru a primi valoarea unei chei i un pointer
ctre un nod descendent. n cel mai ru caz algoritmul de fisionare este
aplicat pe ntreaga nlime a arborelui. n acest mod arborele va crete n
nlime, lungimea drumului de cutare crescnd cu 1.
Sintetizat, algoritmul de inserare a unei valori de cheie n arbore este
prezentat n figura 13.14.


Adaug noua valoare
de cheie n nodul
frunz corespunzator
OVERFLOW?
Divide nodul n dou noduri
aflate pe acelai nivel i
promoveaz cheia median


Fi gur a 13. 14 Algoritmul de inserare a unei chei n arbore

Algoritmul de inserare ntr-un arbore B (pseudocod):

- insereaz noua valoare de cheie n nodul frunz corespunztor;
- nodul_curent = nodul_frunza;
- while( starea pentru nodul_curent este OVERFLOW ):
- divide nodul_curent n dou noduri aflate pe acelai nivel i
promoveaz cheia median n nodul printe pentru
nodul_curent;
- nodul_curent = nodul printe pentru nodul_curent.
n cel mai ru caz, inserarea unei chei noi duce la aplicarea
algoritmului de fisionare pe ntreaga nlime a arborelui, fisionndu-se h
1 noduri, unde h este nlimea arborelui nainte de inserare. Numrul total
de fisionri care au aprut cnd arborele avea p noduri este de p 2. Prima
fisionare adaug dou noduri noi; toate celelalte fisionri produc doar un
singur nod nou. Fiecare nod are un minim de 1
2

(
(
(

m
chei, cu excepia
rdcinii, care are cel puin o cheie. Deci arborele cu p noduri conine cel
puin ( )
|
|
.
|

\
|

(
(
(

+ 1
2
1 1
m
p chei. Probabilitatea ca o fisionare s fie necesar
dup inserarea unei valori de cheie este mai mic dect :

( )
chei
divizari
(13.3)
( )
|
|
.
|

\
|

(
(
(

1
2
1 1
2
m
p
p


care este mai mic dect 1 divizare per 1
2

(
(
(

m
inserri de chei (deoarece
2
1
p
tinde ctre 0 pentru o valoare mare a lui p i
2
1

p
p
este aproximativ
1). De exemplu, pentru m = 10, probabilitatea apariiei unei divizri este de
0.25. Pentru m = 100, probabilitatea divizrii este 0.0204. Pentru m = 200,
probabilitatea divizrii este 0.0101. Cu alte cuvinte, cu ct ordinul arborelui
este mai mare, cu att este mai mic probabilitatea ca inserarea unei valori
de cheie sa duc la divizarea unui nod.
Operaia de tergere dintr-un arbore B este ceva mai complicat
dect operaia de inserare. Operaia de tergere se realizeaz simplu dac
valoarea de cheie care urmeaz a fi tears se afl ntr-un nod frunz. Dac
nu, cheia va fi tears logic, fiind nlocuit cu o alta, vecin n inordine, care
va fi tears efectiv. n urma tergerii se disting urmtoarele cazuri:
- dac nodul conine mai mult de
(
(
(

2
m
chei, tergerea nu ridic
probleme;
- dac nodul are numrul minim de chei
(
(
(

2
m
, dup tergere
numrul de chei din nod va fi insuficient. De aceea se mprumut
o cheie din nodul vecin (aflat pe acelai nivel n arbore) dac
acesta are cel puin
(
(
(

2
m
chei, caz n care avem de-a face cu o
partajare. Dac nu se poate face o partajare cu nici unul din

S considerm urmtoarea configuraie de arbore B de ordin 5 din
figura 13.15.


57
a
35 54 62
122
22 33
40 41
55 56 59 60 66 75
123 124
d
b c
e
f g h i
22
45 50


Fi gur a 13. 15 Arbore B de ordin 5

tergerea valorilor de cheie 40 i 45 din nodul e nu ridic probleme,
figura 13.16

57
a
35 54 62
122
22 33 41 50 55 56 59 60 66 75
123 124
d
b c
e f g h i
34


Fi gur a 13. 16 Structura arborelui dup tergerea cheilor 40 i 45

tergerea valorii de cheie 50 necesit ns partajarea ntre nodurile d
i e, figura 13.17.


57
a
35 54 62
122
22 33 41 50 55 56 59 60 66 75
123 124
d
b c
e f g h i
34
valoarea de cheie 35
coboara; valoarea de
cheie 34 urca


Fi gur a 13. 17 Partajarea ntre nodurile d i e



57
a
34 54 62
122
22 33 35 41 55 56 59 60 66 75
123 124
d
b c
e f g h i


Fi gur a 13. 18 Structura arborelui dup partajare

tergerea valorii de cheie 22 va necesita fuzionarea nodurilor d i e,
figura 13.19.

fuzionare
57
a
34 54 62

122
22

33 35

41 55 56 59 60 66

75

123

124
d
b c
e f g h i



Fi gur a 13. 19 Fuzionarea nodurilor d i e

ns n urma fuzionrii nodurilor d i e, nodul b va conine prea
puine valori de cheie, aa c vor fuziona i nodurile b i c, figura 13.20.



57
a
54 62
122
33 34 35 41 55 56 59 60 66 75
123 124
d
b c
f g h i
fuzionare


Fi gur a 13. 20 Fuzionarea nodurilor a, b i c

Astfel, n final, arborele B va arta astfel ca n figura 13.21.



54 57 62
122
33 34 35 41 55 56 59 60 66 75
123 124


Fi gur a 13. 21 Structura final a arborelui B

Algoritmul de tergere dintr-un arbore B, n pseudocod:
- if (valoarea de cheie care se terge nu este ntr-un nod frunz)
then: nlocuiete valoarea de cheie cu succesor / predecesor;
- nodul_curent = nodul_frunza;
- while (starea pentru nodul_curent este UNDERFLOW ):
- ncearc partajarea cu unul din nodurile vecine aflate pe acelai
nivel, via nodul printe;
- if (nu este posibil ) then:
1. fuzioneaz nodul_curent cu un nod vecin, folosind o valoare
de cheie din nodul printe;
2. nodul_curent = printele pentru nodul_curent.


13.3 Algoritmii C++ pentru inserarea unei valori de cheie
ntr-un arbore B

nainte de a prezenta algoritmii pentru cutare i inserare ntr-un
arbore B, s ncepem mai nti cu declaraiile necesare pentru
implementarea unui arbore. Pentru simplicitate vom construi ntregul arbore
B n memoria heap, folosind pointeri pentru a descrie structura
arborescent. n majoritatea aplicaiilor, aceti pointeri vor trebui nlocuii
cu adrese ctre blocuri sau pagini stocate ntr-un fiier pe disc (deci
accesarea unui pointer va nsemna un acces la disc).
Un nod al arborelui B va fi descris de clasa btree_node. Pentru a fi ct
mai general n ceea ce privete tipul valorii de cheie folosit va fi
implementat ca fiind o clas template. Oricum, tipul valorii de cheie
folosit va trebui s permit ordonarea cheilor; n cazul implementrii de

fa cerina este de a suporta testele de inegalitate strict (<) i de
egalitate (= =).
Pentru generalitate se vor folosi funcii pentru testele de egalitate i
inegalitate strict. Declaraiile acestora sunt urmtoarele:

template <typename FIRST_ARGUMENT_TYPE,
typename SECOND_ARGUMENT_TYPE,
typename RETURN_TYPE
>
struct binary_function { /** empty */ } ;

template <typename TYPE>
struct less_than : public binary_function<TYPE, TYPE, bool> {
inline bool operator()( const TYPE& first, const TYPE& second ) {
return first < second ;
}
} ; /** struct less_than */

template <>
struct less_than<char*> : public binary_function<char*, char*, bool> {
inline bool operator()( const char* first, const char* second ) {
return strcmp( first, second ) < 0 ;
}
} ; /** struct less_than<char*> */

template <typename TYPE>
struct equal_to : public binary_function<TYPE, TYPE, bool> {
inline bool operator()( const TYPE& first, const TYPE& second ) {
return first == second ;
}
} ; /** struct equal_to */

template <>
struct equal_to<char*> : public binary_function<char*, char*, bool> {
inline bool operator()( const char* first, const char* second ) {
return strcmp( first, second ) == 0 ;
}
} ; /** struct equal_to<char*> */

Declaraia clasei btree_node este:

template <typename KEY_NODE_TYPE>
class btree_node {
friend class btree<KEY_NODE_TYPE> ;

public:
typedef KEY_NODE_TYPE key_node_type ;
typedef key_node_type* key_node_ptr ;
typedef btree_node<KEY_NODE_TYPE> btree_node_type ;
typedef btree_node_type* btree_node_ptr ;

typedef struct {
key_node_type key_value ;
btree_node_ptr node_ptr ;
} btree_node_tuple ;

public:
enum BTREE_NODE_STATUS {
EMPTY = 0,
UNDERFLOW,

MINIMAL,
OPERATIONAL,
FULL,
OVERFLOW
} ;

public:
btree_node( int ncapacity ) ;
virtual ~btree_node( void ) ;

BTREE_NODE_STATUS status( void ) const ;
int capacity( void ) const ;
int size( void ) const ;

key_node_type& key_value( int position ) ;
btree_node<KEY_NODE_TYPE>* child( int position ) ;

int push( const key_node_type& value, btree_node_ptr child ) ;
bool find( const key_node_type& value, int& position ) ;
int remove_at( const int position ) ;

bool is_leaf( void ) const ;

int split( btree_node_tuple* tuple ) ;

protected:
static int initialize( btree_node_ptr node ) ;
int shift2right( const int start ) ;
int shift2left( const int start ) ;

protected:
btree_node( const btree_node<KEY_NODE_TYPE>& ) ;
btree_node<KEY_NODE_TYPE>& operator =( const
btree_node<KEY_NODE_TYPE>& ) ;

protected:
less_than<key_node_type> _less_than ;
equal_to<key_node_type> _equal_to ;

protected:
key_node_ptr node_keys ;
btree_node_ptr* node_childs ;
int node_capacity ;
int node_size ;
} ; /** class btree_node */

Structura btree_node_tuple va fi folosit de algoritmul de fisionare
(divizare) a unui nod. Valorile din cadrul enumerrii BTREE_NODE_STATUS
nseamn:
- EMPTY nodul nu conine nici o cheie ;
- UNDERFLOW nodul conine prea puine chei ;
- MINIMAL nodul conine numrul minim de chei ;
- OPERATIONAL numrul de chei coninut de nod respecta cerinele
de arbore B;
- FULL nodul conine numrul maxim de chei ;
- OVERFLOW nodul conine prea multe chei.
Ordinea n care cmpurile din enumerare apar este important (a se
vedea funciile de introducere i tergere a unei chei din arbore).
Constructorul clasei btree_node primete ca parametru numrul maxim de

chei care pot fi pstrate ntr-un nod (capacitatea nodului). Operaiile de
copiere a unui nod nu sunt permise (este dezactivat constructorul de
copiere i operatorul de atribuire). Cmpurile _less_than i _equal_to sunt
folosite n testele de egalitate i inegalitate strict. Cmpul node_capacity
pstreaz numrul maxim de chei dintr-un nod (capacitatea nodului).
Cmpul node_size menine numrul de chei aflate la un moment dat ntr-un
nod. Cmpul node_keys este un vector n care vor fi pstrate valorile de
chei din nod (n implementarea de fa, pentru implementarea mai uoara a
algoritmului de divizare a unui nod, numrul de chei pstrate ntr-un nod va
fi cu unu mai mare dect numrul maxim de chei). Cmpul node_childs va
pstra pointeri ctre descendeni (numrul de descendeni ai unui nod este
cu unu mai mare dect numrul de chei din acel nod).
Constructorul btree_node<KEY_TYPE>::btree_node() apeleaz
funcia initialize() care iniializeaz un nod al arborelui (aloc memoria
necesar pentru pstrarea valorilor de chei i a pointerilor ctre
descendeni, seteaz numrul de chei prezente n arbore la 0):

template <typename KEY_NODE_TYPE>
btree_node<KEY_NODE_TYPE>::btree_node( int capacity ) {
this->node_capacity = capacity ;
initialize( this );
}

template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::initialize( btree_node_ptr node ) {
if( 0 = = node ) return -1 ;
node->node_keys = new key_node_type[ node->capacity() + 1 ];
if( 0 = = node->node_keys ) return -1 ;
node->node_childs = new btree_node_ptr[ node->capacity() + 2 ];
if( 0 = = node->node_childs ) {
delete []node->node_keys ;
node->node_keys = 0;
return -1 ;
}
memset(node->node_childs,0,sizeof(btree_node_ptr)*
(node->capacity() + 2 ) );
node->node_size = 0;

return 0;
}

Destructorul btree_node<KEY_TYPE>::~btree_node() elibereaz
memoria alocat:

template <typename KEY_NODE_TYPE>
btree_node<KEY_NODE_TYPE>::~btree_node( void ) {
delete []this->node_keys ;
delete []this->node_childs ;
}

Pentru a se afla numrul de chei aflate la un moment dat ntr-un nod
se folosete funcia size():

template <typename KEY_NODE_TYPE>
inline int
btree_node<KEY_NODE_TYPE>::size( void ) const

{ return this->node_size ; }

Pentru a se determina numrul maxim de chei care pot fi pstrate
ntr-un nod se folosete funcia capacity():

template <typename KEY_NODE_TYPE>
inline int
btree_node<KEY_NODE_TYPE>::capacity( void ) const
{ return this->node_capacity ; }

Un nod poate fi interogat pentru starea n care se afl folosind funcia
status():

template <typename KEY_NODE_TYPE>
inline btree_node<KEY_NODE_TYPE>::BTREE_NODE_STATUS
btree_node<KEY_NODE_TYPE>::status( void ) const {
if( 0 == size() ) return EMPTY ;
else if( size() < ( capacity() / 2 ) ) return UNDERFLOW ;
else if( size() == ( capacity() / 2 ) ) return MINIMAL ;
else if( size() == capacity() ) return FULL ;
else if( size() > capacity() ) return OVERFLOW ;
return OPERATIONAL ;
}

Se observ c starea nodului este funcie de numrul de chei aflate la
un moment dat n nod. Mai exact, dac avem un arbore B de ordin m,
atunci numrul maxim de chei dintr-un nod va fi m 1, iar numrul
minim de chei va fi 1
2

(
(
(

m
. Parametrul primit de constructor va fi m 1,
deci capacitatea nodului va fi m 1. Cum numrul de chei aflate la un
moment dat ntr-un nod se obine folosind funcia size(), vom avea:
- dac size() returneaz zero, atunci n nod nu se gsete nici o
cheie starea nodului este EMPTY ;
- numrul minim de chei din nod este capacity()/2; deci dac
size()==capacity()/2, atunci nodul are numrul minim de chei
starea nodului este MINIMAL ;
- dac size() < capacity() / 2, atunci nodul are prea puine chei
starea nodului este UNDERFLOW ;
- dac size() == capacity(), atunci nodul are numrul maxim de
chei permise starea nodului este FULL ;
- dac size() > capacity(), atunci nodul are prea multe chei
starea nodului este OVERFLOW.
Pentru a se determina dac nodul este un nod frunz se folosete
funcia is_leaf(), care va returna true dac nodul este o frunz (un nod este
nod frunz dac nu are nici un descendent):

template <typename KEY_NODE_TYPE>
bool
btree_node<KEY_NODE_TYPE>::is_leaf( void ) const {
assert( 0 != this->node_childs ) ;

// return 0 == this->node_childs[0] ;
for( int idx = 0; idx <= size(); idx++ )
if( 0 != this->node_childs[idx] ) return false ;

return true ;
}

Ca funcii pentru interogarea valorii unei chei i unui descendent
dintr-o anume poziie se folosesc funciile key_value() i child():

template <typename KEY_NODE_TYPE>
inline KEY_NODE_TYPE&
btree_node<KEY_NODE_TYPE>::key_value( int position ) {
if( position < 0 ||
position >= size()
) {
/** signal out of bounds*/
assert( false ) ;
}

return this->node_keys[position] ;
}

template <typename KEY_NODE_TYPE>
inline btree_node<KEY_NODE_TYPE>*
btree_node<KEY_NODE_TYPE>::child( int position ) {
if( position < 0 ||
position > size()
) {
/** signal out of bounds */
assert( false ) ;
return 0 ;
}

return this->node_childs[position] ;
}

Cutarea unei valori de cheie ntr-un nod se face folosind funcia
find(). Primul parametru primit de funcie este valoarea de cheie care se
caut n nod. Dup cum valoarea de cheie cutat se gsete sau nu n nod,
find() va returna true sau false, cu meniunea ca al doilea parametru al
funciei (position, care este un parametru de ieire) va fi setat dup cum
urmeaz:
- daca valoarea de cheie cutat se gsete n nod, atunci position
este setat la indexul la care se gsete cheia n nod;
- dac valoarea de cheie cutat nu se gsete n nod, position va
indica indexul subarborelui n care s-ar putea gsi valoarea de
cheie cutat.

template <typename KEY_NODE_TYPE>
bool
btree_node<KEY_NODE_TYPE>::find( const key_node_type& value,
int& position ) {
bool ret_value ;
position = -1 ;

if( _less_than( value, this->node_keys[0] ) ) {
position = 0 ;
ret_value = false ;
} else {
for( position = size() - 1 ;
_less_than( value, key_value( position ) ) &&

position > 0;
position--
) ;

ret_value = _equal_to( value, this->node_keys[position] ) ;
if( !ret_value ) position++ ;
}

return ret_value ;
}

Inserarea unei valori de cheie n nod se realizeaz folosind funcia
push(). n implementarea de fa inserarea valorii de cheie ntr-un nod se
face ntotdeauna specificnd i pointerul ctre subarborele din dreapta
valorii de cheie (subarbore care conine toate valorile de cheie mai mari
dect valoarea de cheie inserat). Dac nodul este n starea OVERFLOW sau
valoarea de cheie exist deja n nod, funcia va returna 1, fr a face nimic
altceva. n urma determinrii poziiei pe care va fi inserat valoarea de
cheie, ar putea fi necesar o deplasare ctre dreapta a valorilor de cheie din
nod mai mari dect cea care se insereaz (deplasarea se face mpreun cu
pointerii ctre descendeni; funcia folosit este shift2right()).

template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::push(
const key_node_type& value,
btree_node_ptr child
) {
if( OVERFLOW == status() ) return -1 ;

if( EMPTY == status() ) {
this->node_keys[0] = value ;
this->node_childs[1] = child ;
this->node_size = 1 ;
return 0 ;
}

int key_position = -1 ;
if( find( value, key_position ) ) {
/** duplicate key value */
return -1 ;
}

if( key_position < size() ) shift2right( key_position ) ;
this->node_keys[key_position] = value ;
this->node_childs[key_position + 1] = child ;
this->node_size++ ;

return 0 ;
}

Funcia shift2right() este:

template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::shift2right( const int start ) {
if( EMPTY == status() ||
start < 0 ||

start >= size()
) return -1 ;

for( int idx = size(); idx > start; idx-- ) {
this->node_keys[idx] = this->node_keys[idx - 1] ;
this->node_childs[idx + 1] = this->node_childs[idx] ;
}
return 0 ;
}

Pentru eliminarea unei chei dintr-o anumit poziie se va folosi funcia
remove_at() (practic, eliminarea presupune diminuarea cu unu a numrului
de chei coninute de nod i o deplasare ctre stnga a valorilor de cheie i a
subarborilor aflai n dreapta poziiei din care se terge cheia).



template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::remove_at( const int position ) {
if( -1 == shift2left( position ) ) return -1 ;
this->node_size-- ;

return 0 ;
}

Funcia shift2left() este:

template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::shift2left( const int start ) {
if( EMPTY == status() ||
start < 0 ||
start >= size()
) return -1 ;

for( int idx = start + 1; idx < size(); idx++ ) {
this->node_keys[idx - 1] = this->node_keys[idx] ;
this->node_childs[idx] = this->node_childs[idx + 1] ;
}

return 0 ;
}

Atunci cnd starea unui nod este de OVERFLOW, acesta se va diviza.
n urma divizrii se va obine un nod nou. Funcia de divizare a unui nod
este split(). Parametrul funciei split() este de ieire, fiind de tipul
btree_node_tuple:

typedef struct {
key_node_type key_value ;
btree_node_ptr node_ptr ;
} btree_node_tuple ;

n urma procesului de divizare a unui nod va urca n nodul printe
cheia median i un pointer ctre nodul nou format. Cheia median va fi
cmpul key_value al structurii btree_node_tuple. Pointerul ctre nodul nou

format va fi cmpul node_ptr al structurii btree_node_tuple. Noul nod va
conine valorile de chei i subarborii din dreapta cheii mediane.

template <typename KEY_NODE_TYPE>
int
btree_node<KEY_NODE_TYPE>::split(
btree_node<KEY_NODE_TYPE>::btree_node_tuple* tuple
) {
if( 0 == tuple ) return 0 ;
if( OVERFLOW != this->status() ) return 0;

int median_position = this->size() / 2;
tuple->key_value = this->key_value( median_position );

btree_node_ptr new_node = new btree_node_type( this->capacity());
if( 0 == new_node ) return -1;

for( int idx = median_position + 1;
idx < this->size() ;
idx++
) new_node->push( this->key_value( idx ), this->child( idx + 1 ) );
new_node->node_childs[0] = this->child( median_position + 1 );

this->node_size = median_position;
tuple->node_ptr = new_node;

return 0 ;
}

n continuare, va fi dat declaraia clasei de arbore B:

template <typename KEY_TYPE>
class btree {

public:
typedef KEY_TYPE key_type ;
typedef btree_node<KEY_TYPE> btree_node_type ;
typedef btree_node_type* btree_node_ptr ;
typedef btree_node_type::btree_node_tuple btree_node_tuple ;
typedef btree<KEY_TYPE> btree_type ;
typedef btree_type* btree_ptr ;

public:
btree( int order ) ;
virtual ~btree( void ) ;

int push( const key_type& value ) ;
int remove( const key_type& value ) ;

protected:
int push_down( btree_node_tuple* tuple, btree_node_ptr current ) ;

int remove_down( const key_type& value, btree_node_ptr current ) ;
int replace_with_predecessor( btree_node_ptr node, int position ) ;
void restore( btree_node_ptr current, const int position ) ;
void move_left( btree_node_ptr current, const int position ) ;
void move_right( btree_node_ptr current, const int position ) ;
void combine( btree_node_ptr current, const int position ) ;

private:

btree_node_ptr root ;
int order ;
} ; /** class btree */

Clasa btree este o clas template dup tipul valorii de cheie. La fel ca
la clasa btree_node au fost folosite o serie de typedef-uri n cadrul clasei
(codul este mai uor de scris / citit dac tipul btree_node<KEY_TYPE> se
redefinete ca fiind btree_node_type). Nodul rdcin al arborelui B este
dat de cmpul root. Ordinul arborelui B este dat de cmpul order.
Constructorul btree<KEY_TYPE>::btree() primete ca parametru
ordinul arborelui i seteaz rdcina arborelui la 0.

template <typename KEY_TYPE>
btree<KEY_TYPE>::btree( int order ) {
this->order = order ;
this->root = 0 ;
}

Destructorul clasei btree<KEY_TYPE>::~btree() va elibera spaiul de
memorie ocupat de arbore.

template <typename KEY_TYPE>
btree<KEY_TYPE>::~btree( void ) {
clear( this->root ) ;
this->root = 0 ;
}

template <typename KEY_TYPE>
void btree<KEY_TYPE>::clear( btree_node_ptr node ) {
if( 0 == node ) return ;
if( !node->is_leaf() )
for( int idx = 0 ; idx <= node->size(); idx++ )
clear( node->child( idx ) ) ;
delete node ;
}

Funcia de inserare a unei valori de cheie n arbore este push(). Dac
arborele nu are nici o valoare de cheie inserat (adic rdcina arborelui
este 0 arborele este gol), atunci inserarea valorii de cheie este simpl: se
construiete rdcina arborelui cu valoarea de cheie care se insereaz. Daca
arborele nu este gol, atunci se insereaz cheia n arbore recursiv folosind
funcia push_down(), pornind de la nodul rdcin. Funcia push_down() va
returna 1 dac n urma procesului de inserare recursiv a valorii de cheie
nodul rdcin a fost divizat (cheia i un pointer ctre subarborele drept al
cheii vor fi coninute de cmpurile variabilei tuple). n acest caz nlimea
arborelui crete cu unu, formndu-se o nou rdcin cu valoarea de cheie
dat de cmpul kei_value al variabilei tuple; subarborele stng va fi vechea
rdcin a arborelui, iar subarborele drept va fi dat de cmpul node_ptr al
variabilei tuple.

template <typename KEY_TYPE>
int btree<KEY_TYPE>::push( const key_type& value ) {
if( 0 == this->root ) {
this->root = new btree_node_type( this->order - 1 ) ;
if( 0 == this->root ) return -1 ;


this->root->push( value, (btree_node_ptr)0 ) ;
return 0 ;
}

btree_node_tuple tuple ;
tuple.key_value = value ;
tuple.node_ptr = 0 ;

if( push_down( &tuple, this->root ) ) {
btree_node_ptr new_root = new btree_node_type( this->order - 1 );
if( 0 == new_root ) return -1 ;

new_root->push( tuple.key_value, tuple.node_ptr ) ;
new_root->node_childs[0] = this->root ;
this->root = new_root ;
}

return 0 ;
}

Funcia push_down() de inserare recursiv n arbore primete ca
parametri nodul curent current n care se ncearc inserarea, i, mpachetat
n variabila tuple, valoarea de cheie care se insereaz. Iniial, la primul apel,
cmpul node_ptr al variabilei tuple va avea valoarea 0. n cadrul
algoritmului de inserare se va cuta valoarea de cheie n nodul curent. Dac
aceasta exist deja n nod (arbore), funcia de inserare nu va face nimic i
va returna 1. Altfel, variabila key_position va indica:
- dac nodul curent este frunz, key_position indic locul n care se
va insera valoarea de cheie;
- dac nodul curent nu este o frunz, key_position va indica
subarborele n care va trebui s se fac inserarea.
Dac nodul curent este frunz, inserarea este simpl: se apeleaz
doar metoda push() de inserare a unei chei ntr-un nod (la inserarea ntr-un
nod frunz ntotdeauna cmpul node_ptr al parametrului tuple va fi 0).
Dac nodul curent nu este frunz, atunci va trebui urmrit dac n urma
inserrii recursive n nodul curent a urcat o valoare de cheie. Daca n nodul
curent a urcat o valoare de cheie (metoda push_down() a returnat valoarea
1) atunci:
- cmpurile parametrului tuple vor conine valoarea de cheie care a
urcat i un pointer ctre subarborele drept corespunztor cheii ;
- n nodul curent vor trebui inserate key_value i node_ptr indicate
de cmpurile parametrului tuple.
n final, se verific starea n care se afl nodul curent. Dac starea
acestuia este de OVERFLOW, atunci nseamn ca acesta conine prea multe
chei i va trebui divizat. Pentru aceasta se folosete metoda split() a nodului
care va primi ca parametru adresa lui tuple. n urma apelului metodei
split() coninutul cmpurilor key_value i node_ptr ale variabilei tuple vor fi
actualizate pentru a indica valoarea de cheie care va urca n nodul printe al
nodului curent i pointerul ctre subarborele drept; n acest caz metoda
push_down() va returna 1 pentru a indica faptul c n nodul printe vor
trebui folosite cmpurile variabile tuple. Dac starea nodului nu este
OVERFLOW, metoda push_down() va returna 0.

template <typename KEY_TYPE>
int btree<KEY_TYPE>::push_down(

btree_node_tuple* tuple,
btree_node_ptr current
) {
if( 0 == current ) return 0 ;

int key_position ;
bool duplicate_key = current->find( tuple->key_value, key_position)
;
if( duplicate_key ) {
/** signal duplicate value */
return -1 ;
}

if( current->is_leaf() ) {
current->push( tuple->key_value, tuple->node_ptr );
} else {
if( push_down( tuple, current->child( key_position ) ) )
current->push( tuple->key_value, tuple->node_ptr);
}

if( btree_node_type::OVERFLOW == current->status() ) {
current->split( tuple ) ;
return 1 ;
}

return 0 ;
}


13.4 Algoritmii C++ pentru tergerea unei valori de cheie
intr-un arbore B

Funcia de tergere a unei valori de cheie dintr-un arbore B este
remove(). Intern este folosit funcia recursiv remove_down(). Dac n
urma tergerii valorii de cheie nodul rdcin nu mai are nici o valoare de
cheie (starea nodului rdcin este EMPTY), atunci noua rdcin a
arborelui este subarborele stng al vechii rdcini. Vechea rdcin se
elimin din memorie.

template <typename KEY_TYPE>
int btree<KEY_TYPE>::remove( const key_type& value ) {
remove_down( value, this->root ) ;

if( btree_node_type::EMPTY == this->root->status() ) {
btree_node_ptr old_root = this->root ;
this->root = this->root->child( 0 ) ;

delete old_root ;
}

return 0 ;
}

Funcia de tergere recursiv a unei valori de cheie din arbore este
remove_down(). Parametrii primii de funcie sunt valoarea de cheie care se
terge i un pointer ctre nodul curent. Dac la un moment dat nodul curent
este 0, atunci nseamn c valoarea de cheie solicitat a fi tears nu se

gsete n arbore. Altfel, funcie de tipul nodului unde cheia este gsit
avem urmtoarele cazuri:
- dac valoarea de cheie se gsete ntr-un nod frunz, atunci
tergerea nseamn apelarea metodei remove() a nodului frunz
pentru eliminarea cheii aflat n poziia dat de variabila position ;
- dac valoarea de cheie a fost gsit ntr-un nod care nu este nod
frunza, atunci valoarea de cheie care trebuie tears va fi
nlocuit, n implementarea de fa, cu valoarea de cheie care o
precede (se poate demonstra c valoarea de cheie care o precede
se gsete ntr-un nod frunz). Dup ce se face nlocuirea folosind
funcia replace_with_predecessor(), se va continua algoritmul de
tergere, dar de data aceasta se va solicita tergerea valorii de
cheie cu care s-a fcut nlocuirea.
Se observ c dac nodul curent este valid (nu este 0) i valoarea de
cheie care se caut nu se gsete n nodul curent, atunci variabila position
va fi poziionat de metoda find() a nodului ca fiind poziia subarborelui care
este posibil s conin valoarea de cheie.
n finalul funciei, dac nodul curent nu este frunz (este un nod
printe care are descendeni care au fost afectai), se testeaz starea
nodului rdcin pentru subarborele pe care s-a efectuat coborrea n
procesul de tergere. Dac starea acestui nod este UNDERFLOW (conine
prea puine valori de cheie), atunci va fi apelat funcia restore() care va
restaura proprietile de arbore B.

template <typename KEY_TYPE>
int btree<KEY_TYPE>::remove_down(
const key_type& value,
btree_node_ptr current
) {
if( 0 == current ) {
/** signal value not found */
return 0 ;
}

int position ;
if( current->find( value, position ) ) {
if( current->is_leaf() ) current->remove_at( position ) ;
else {
replace_with_predecessor( current, position ) ;
remove_down(current->key_value(position ),
current->child( position)) ;
}
} else remove_down( value, current->child( position ) ) ;

if( !current->is_leaf() &&
btree_node_type::UNDERFLOW = = current->child(position)-
>status() )
restore( current, position ) ;

return 0 ;
}

Funcia de nlocuire a unei valori de cheie cu valoarea de cheie care o
precede este replace_with_succesor(). Parametrii primii de funcie sunt:
node, un pointer ctre nodul care conine valoarea de cheie care se
nlocuiete i position care d poziia subarborelui (n nodul indicat de node)

care conine valoarea de cheie care precede valoarea de cheie care se
nlocuiete.

template <typename KEY_TYPE>
int btree<KEY_TYPE>::replace_with_predecessor(
btree_node_ptr node,
int position
) {
if( position > node->size() ) return 0 ;

btree_node_ptr leaf = node->child( position ) ;
while( !leaf->is_leaf() ) leaf = leaf->child( leaf->size() ) ;
node->node_keys[position] = leaf->key_value( leaf->size() - 1 ) ;

return 1 ;
}

Funcia care asigur restaurarea proprietilor de arbore B este
restore(). Parametrii funciei sunt: un pointer ctre un nod printe (current)
i o poziie (position) care indic un subarbore al nodului current (nodul
rdcin al acestui subarbore conine prea puine valori de cheie). Funcia
folosit n implementarea de fa este cumva orientat ctre stnga, n
sensul c mai nti se uit la nodul vecin din stnga pentru a lua o valoare
de cheie, folosind nodul vecin din dreapta numai dac nu gsete suficiente
valori de cheie n nodul din stnga. Paii care sunt necesari sunt ilustrai n
figura 13.22.


move_right()
z
x
y
w

y
x z w

move_left()
y

x

z

w

z

x

y w

x

y

z
v w

combine()
x z
v

y

w



Fi gur a 13.22 Paii parcuri n funcia de restaurare
a proprietilor unor arbore B

template <typename KEY_TYPE>
void btree<KEY_TYPE>::restore(
btree_node_ptr current,

const int position
) {
if( 0 == position ) {
if( current->child( 1 )->status() > btree_node_type::MINIMAL )
move_left( current, 1 ) ;
else
combine( current, 1 ) ;
} else if( position == current->size() ) {
if(current->child(current->size()-1)->status()>
btree_node_type::MINIMAL)
move_right( current, position ) ;
else
combine( current, position ) ;
} else if( current->child( position - 1 )->status() >
btree_node_type::MINIMAL )
move_right( current, position ) ;
else if( current->child( position + 1 )->status() >
btree_node_type::MINIMAL )
move_left( current, position + 1 ) ;
else
combine( current, position );
}

Funciile move_left(), move_right() i combine() sunt uor de dedus
din figura 13.22.

template <typename KEY_TYPE>
void btree<KEY_TYPE>::move_left(
btree_node_ptr current,
const int position
) {
btree_node_ptr node = current->child( position - 1 ) ;
node->push(
current->key_value( position - 1 ),
current->child( position )->child( 0 )
) ;

node = current->child( position ) ;
current->node_keys[position - 1] = node->key_value( 0 );
node->node_childs[0] = node->node_childs[1] ;
node->shift2left( 0 ) ;
node->node_size-- ;
}

template <typename KEY_TYPE>
void btree<KEY_TYPE>::move_right(
btree_node_ptr current,
const int position
) {
btree_node_ptr node = current->child( position ) ;
node->shift2right( 0 ) ;
node->node_childs[1] = node->child( 0 ) ;
node->node_keys[0] = current->key_value( position - 1 ) ;
node->node_size++ ;
node = current->child( position - 1 ) ;
current->node_keys[position - 1] = node->key_value(node->size()-1);
current->child(position)->node_childs[0]=node->child(node->size());
node->node_size-- ;
}



template <typename KEY_TYPE>
void btree<KEY_TYPE>::combine(
btree_node_ptr current,
const int position )
{
btree_node_ptr rnode = current->child( position ) ;
btree_node_ptr lnode = current->child( position - 1 ) ;

lnode->push(current->key_value( position - 1 ), rnode->child( 0 ));
for( int idx = 0; idx < rnode->size(); idx++ )
lnode->push( rnode->key_value( idx ), rnode->child( idx + 1 ) );

current->remove_at( position - 1 ) ;
delete rnode ;
}

You might also like