Sunteți pe pagina 1din 13

Structuri de Date – Lucrarea nr.

12

B-arbori

1. Introducere
2. Operații pe structuri de tip B-arbore
3. Aplicații

1. Introducere

B-arborele reprezintă una din categoriile de arbori de căutare care au proprietatea


de auto-echilibrare. Structuri de date similare cum sunt arborii AVL accesează informații
din memoria internă. B-arborii pleacă de la premisa că memoria internă nu poate stoca o
cantitate foarte mare de informație. În cazul în care numărul de chei gestionate este foarte
mare, informațiile vor fi preluate din unitatea de memorie fixă sub formă de blocuri, însă
timpul de acces va crește foarte mult comparativ cu memoria internă. Ideea principală a
structurii de tip B-arbore este reducerea numărului de accesări a informațiilor stocate în
unitatea de memorie fixă. Majoritatea operațiilor efectuate pe un B-arbore (căutare,
inserare, ștergere, identificarea elementului minim sau maxim, etc.) vor necesita 𝑂(ℎ)
accesări ale unității de memorie fixe. Înălțimea ℎ a unui B-arbore este redusă prin inserarea
numărului maxim de chei în fiecare nod, nodurile la rândul lor fiind dimensionate în funcție
de mărimea blocurilor preluate din memorie.
Nu există uniformitate în terminologie. In continuare, vom considera proprietățile unui
B-arbore definite de Knuth:
• Ordinul arborelui B se notează cu M și reprezintă numărul maxim de descendenți
ai unui nod;
• Toate frunzele sunt la același nivel;
• Orice nod care nu este rădăcină are cel puțin M/2 descendenți și M/2-1 chei (pt. M
par!) ;
• Rădăcina este singurul nod care poate conține o singură cheie și are cel puțin doi
descendenți dacă nu este frunză;
• Numărul de descendenți ai unui nod este egal cu 𝑘 + 1 unde 𝑘 este numărul de chei
din nodul respectiv;
• Toate cheile stocate într-un nod sunt sortate în ordine crescătoare. Descendenții
dintre cheile 𝑘1 și 𝑘2 conțin toate cheile din intervalul (𝑘1 ; 𝑘2 );
• B-arborii cresc și se îngustează spre rădăcină, spre deosebire de BST care cresc și
se îngustează la extremități;
• Similar celorlalți arbori de căutare, complexitatea timp pentru operațiile de căutare,
inserare sau ștergere este 𝑂(log 𝑛).
Structuri de Date – Lucrarea nr. 12

2. Operații pe structuri de tip b-arbore

Fie structura 𝑏𝑛𝑜𝑑 propusă pentru implementarea unui b-arbore:

#define M 6
enum tipNod { frunza, interior };

struct bnod {
tipNod tip;
int n;
int val[M -1];
bnod *leg[M];
};

inițializat pe baza funcției:

void createBTree(bnod *&t)


{
t = new bnod;
t->tip = frunza;
t->n = 0;
for (int i = 0; i <= M-1; i++)
t->leg[i] = 0;
}

2.1. Căutarea într-un b-arbore

Căutarea este similară căutării într-un BST. Fie x cheia care trebuie căutată, se
pleacă de la rădăcină și se parcurge arborele în mod recursiv în jos. Pentru fiecare nod
intern se va verifica dacă acesta conține cheia. În caz afirmativ se va returna 𝑡𝑟𝑢𝑒, iar în
caz căutarea va continua de la cel mai apropiat descendent (de dinaintea nodului cu cheia
mai mare). Pentru nodurile de tip frunză care nu conțin cheia respectivă se va returna 𝑓𝑎𝑙𝑠𝑒.
RecursiveSearch(r,x)
i=0;
while i<n and val[i]<x
i=i+1;
endwhile;

if i<n and x==val[i]


then return true;
endif;

if leg[i]==NULL
then return false;
else return RecursiveSearch(leg[i],x);
endif;

endRecursiveSearch;

Căutarea poate fi realizată și în mod iterativ, după cum urmează:


IterativeSearch(r,x)
p = r;
while p!=NULL execute
pos=0;
Structuri de Date – Lucrarea nr. 12

while pos<n and val[pos]<x


pos = pos+1;
endwhile;

if pos<n and x==val[pos]


then return true;
endif;

p=leg[pos];
endwhile;

return false;
endIterativeSearch;

Funcțiile echivalente în C++ vor avea prototipurile:


bool IterativeSearch(bnod *r, int x);
bool RecursiveSearch(bnod *r, int x);

2.2. Traversarea unui b-arbore

Pentru a afișa nodurile arborelui de pe fiecare nivel poate fi folosită funcția:


void display(bnod *v, int niv)
{
if (v)
{
if (v->tip == interior)
display(v->leg[0], niv + 1);
for (int i = 0; i < v->n; i++)
{
for (int j = 0; j < niv; j++)
cout << "\t";
cout << v->val[i] << endl;
if (v->tip == interior)
display(v->leg[i + 1], niv + 1);
}
}
}

Traversarea în inordine este similară parcurgerii în inordine a unui arbore binar. Se


pleacă de la descendentul cel mai din stânga și se avansează recursiv în stânga, repetându-
se procesul cu descendenții rămași și cheile din nod. La final se vor parcurge recursiv
descendenții din dreapta.
Inorder(r)
{
if (r == 0)
then return;
endif;
// repeta ptr fiecare fiu al lui r indexat în leg de la 1 la n
for i = 1 to n-1 execute,
Inorder(leg[i]);
// scrie valori mai mici ca r->val[i]
cout<< val[i];
// scrie valoarea i din vectorul val
endfor;
Inorder(leg[n]);
// scrie valori mai mari ca ultima din nodul r
}

Funcția echivalentă în C++ va avea prototipul:


void Inorder(bnod* r);
Structuri de Date – Lucrarea nr. 12

2.3. Inserarea unei chei într-un b-arbore

Cheile noi sunt inserate întotdeauna ca frunze. Fie 𝑘 cheia care trebuie inserată.
Similar arborilor BST, se pleacă de la rădăcină și se coboară spre nodurile frunză,
inserându-se valoarea în nodul frunză identificat. Spre deosebire de BST, există un număr
predefinit de chei care pot fi inserate într-un nod și de aceea trebuie verificat mereu dacă
respectiva cheie poate fi adăugată. Pentru a elibera spațiul necesar inserării într-un anumit
nod se folosește operația de spargere a unui nod descendent 𝑆𝑝𝑙𝑖𝑡 ilustrată grafic în figura
de mai jos:

1 50 80 100 200
1 50 100 200

55 70 85 90
55 70 80 85 90

T1 T2 T3 T4 T5 T6
T1 T2 T3 T4 T5 T6

Fie 𝑥 rădăcina arborelui și 𝑘 cheia ce trebuie inserată. Cât timp 𝑥 nu este frunză, se
identifică descendentul lui 𝑥 care urmează a fi vizitat și se notează cu 𝑦. Dacă 𝑦 nu este
complet, el devine noua rădăcină. Dacă 𝑦 este complet, acesta va fi spart și 𝑥 va indica
către unul din descendenții lui 𝑦 în funcție de 𝑘. Dacă 𝑘 este mai mic decât cheia mediană
din 𝑦, noua rădăcină va indica către prima jumătate din 𝑦, altfel, noua rădăcină va indica
către a doua parte din 𝑦. La spargerea nodului y, cheia mediană va migra către nodul părinte
𝑥. Dacă rădăcina nu mai are descendenți, algoritmul se oprește. 𝑥 trebuie să aibă spațiu
pentru inserarea unei chei după spargerea tuturor nodurilor, iar 𝑘 va fi inserat în 𝑥.
Avantajul de a sparge un nod înainte de inserare îl reprezintă traversarea acestuia o
singură dată. În cazul în care spargerea nu este efectuată înainte de a ajunge într-un nod
(inserare proactivă), ci fix în momentul inserării (inserare reactivă) poate necesita
traversarea tuturor nodurilor încă o data, plecând de la frunze spre rădăcină. Această
traversare este necesară atunci când toate nodurile situate pe calea de la rădăcină la frunză
sunt pline. De aceea, atunci când se ajunge la frunză, aceasta va fi spartă și una din chei va
urca un nivel. Spargerile se vor efectua în cascadă ținând cont că toate nodurile de pe cale
sunt pline. La rândul său, algoritmul de inserare proactiv are un dezavantaj în spargerile
inutile pe care le poate produce.

Fie secvența de întregi care vor fi inserați într-un B-arbore de grad minim 𝑡 = 3,
inițial vid: 10, 20, 30, 40, 50, 60, 70, 80 ș𝑖 90:
Inițial rădăcina arborelui este setată pe NULL.
Se inserează 10:
Structuri de Date – Lucrarea nr. 12

Se inserează în continuare 𝟐𝟎, 𝟑𝟎, 𝟒𝟎 ș𝒊 𝟓𝟎. Toate valorile vor fi inserate în nodul
rădăcină dat fiind faptul că aceasta poate conține maxim 2𝑡 − 1 = 5 chei:

Pentru inserarea lui 60, nu mai există poziții libere în nodul rădăcină. Acesta va fi spart,
iar valoarea 60 va fi inserată în nodul descendent adecvat:

Vor fi inserate în continuare, valorile 70 și 80. Acestea vor fi introduse în nodul frunză
adecvat fără a produce spargeri:

După inserarea lui 90, se va produce o spargere, iar cheia mediană va urca în nodul
părinte:
Structuri de Date – Lucrarea nr. 12

Dacă un nod 𝑣 este încărcat la maxim, pentru a insera o cheie nouă este necesară
spargerea acestuia. Prin spargere cheia mediană a nodului 𝑣 este mutată în părintele 𝑢 al
acestuia. Se va crea un nou nod 𝑤, toate cheile din 𝑣 situate la dreapta cheii mediane sunt
mutate în nodul 𝑤. Cheile din 𝑣 situate la stânga cheii mediane rămân în 𝑣. Nodul nou 𝑤
devine fiu imediat la dreapta cheii mediane care a fost mutată în părintele 𝑢, iar 𝑣 devine
fiu imediat la stânga. Spargerea transformă nodul cu 𝑀 − 1 chei în două noduri cu (𝑀 −
2)/2 chei fiecare.

void splitNode(bnod *&u, int i, bnod *&v)


{
bnod *w = new bnod;
w->tip = v->tip; // nodul w mosteneste tipul nodului v (cel care se sparge)
w->n = (M - 1) / 2; // cate noduri va avea nodul w

for (int k = 1; k < M; k++)


w->leg[k] = 0; // initial, w nu are descendenti

// se copiaza valorile din v in w, cu un offset de (M-1)/2+1


for (int j = 0; j <= (M - 1) / 2; j++)
w->val[j] = v->val[j + (M - 1) / 2 + 1];
// daca v este interior, atunci ne asteptam sa aiba descendenti in dreapta medianei,
// asa ca ii copiem in w pentru ca w va fi jumatatea din dreapta medianei
// se copiaza cu un offset de (M-1)/2+1
if (v->tip == interior)
for (int j = 0; j <= (M - 1) / 2; j++)
w->leg[j] = v->leg[j + (M - 1) / 2 + 1];

v->n = (M - 1) / 2; // cate elemente raman in v


// valoarea P->val[k] are legatura din stanga la P->leg[k] si cea din dreapta P->leg[k+1]
// deplasam la dreapta toate legaturile nodului parinte din intervalul [i+1..n]
// pentru ca urmeaza sa urcam mediana lui v in u
for (int j = u->n; j >= i + 1; j--)
u->leg[j + 1] = u->leg[j];

u->leg[i + 1] = w; // mediana se pune pe pozitia i si in dreapta ei se leaga w

// deplasam la dreapta valorile din u pentru a face loc medianei


for (int j = u->n - 1; j >= i; j--)
u->val[j + 1] = u->val[j];

u->val[i] = v->val[(M - 1) / 2]; // punem mediana din fiul v pe pozitia i in parinte


u->n++; // creste numarul de noduri din parinte
}

Pentru a insera o valoare 𝑘 în b-arborele cu rădăcina 𝑟, trebuie găsit nodul în care


urmează să se facă inserarea. Inserarea se va face mereu într-un nod de tip frunză. Dacă
nodul în care se va face inserarea conține mai puțin de 𝑀 valori, inserarea se va face direct
în nodul respectiv. Dacă nodul conține 𝑀 − 1 valori, acesta va fi spart în două noduri cu
(𝑀 − 1)/2 valori, valoarea mediană fiind mutată în nodul părinte.
Insert(&r, k)
v=r;
if n==M-1 then
u=new interior node with 0 values, linked to v;
r=u;
SplitNode(u, 0, v);
InsertToNode(u,k);
else InsertToNode(v,k);
endif;
endInsert;
Structuri de Date – Lucrarea nr. 12

InsertToNode(&v,k)
i=n;
if(type(v)==leaf) // directly insert value to node
then while i>=1 and k<val[i] execute
val[i+1]=val[i];
i=i-1;
endwhile;
val[i+1]=k;
n=n+1;
else
//search for the appropriate child node for inserting the value
while i>=1 and k<val[i] execute
i=i-1;
endwhile;
if k>val[i] then i=i+1;
endif;
if IsFull(leg[i]) //mai exact v->leg[i]->n==M-1
then SplitNode(v,i,leg[i]);
if k>val[i] then i=i+1; endif;
endif;
InsertToNode(leg[i],k);
endInsertToNode;

Funcțiile echivalente în C++ vor avea prototipurile:


void insertNonFullNode(bnod *&v, int k);
void insertBTree(bnod *&t, int k);

2.4. Ștergerea

Operația de ștergere este ceva mai complicată decât operația de inserarea, deoarece
cheia care va fi ștearsă poate proveni din orice nod și nu neapărat dintr-un nod frunză.
Problema apare la ștergerea unei chei dintr-un nod intern, operație care va necesita
rearanjarea nodurilor descendente. La fel ca operația de inserare, ștergerea trebuie să
păstreze proprietățile structurii de b-arbore. Similar modului în care se asigură aglomerarea
cheilor într-un nod la inserare, se vor evita diminuările excesive a numărului de chei dintr-
un nod în timpul ștergerii. Rădăcina poate fi o excepție în această situație, deoarece ea
poate conține mai puțin decât minimul de 𝑡 − 1 chei. Ștergerea poate fi abordată
asemănător încercării de a insera o valoare într-un nod plin. La fel cum algoritmul de
inserare trebuie să se întoarcă înapoi în arbore dacă nodul în care trebuie să se facă inserarea
este plin, ștergerea abordată asemănător trebuie să revină în sus în arbore, dacă nodul din
care ar trebui să se facă ștergerea, cu excepția rădăcinii, conține numărul minim de chei.
Procedura de ștergere elimină cheia 𝑘 din subarborele cu rădăcina în x. Această
procedură garantează că la fiecare apel recursiv pentru un nod 𝑥, numărul de chei din 𝑥
este minim 𝑡, unde 𝑡 este gradul arborelui. Această condiție necesită o cheie în plus față de
minimul cerut de condițiile structurii de tip b-arbore. Din acest motiv, unele chei trebuie
coborâte în nodurile copil înaintea apelului recursiv pentru copilul respectiv. Această
condiție suplimentară permite ștergerea unei chei din arbore printr-o singură traversare de
sus în jos, fără a necesita reveniri, cu o singură excepție. Specificațiile următoare se referă
la ștergerea dintr-un b-arbore în cazul în care nodul rădăcină 𝑥 devine nod intern fără nici
o cheie (cazurile 2.c) și 3.b)) atunci 𝑥 va fi șters, rădăcina fiind înlocuită de singurul ei
copil 𝑥. 𝑐1. Înălțimea arborelui va fi decrementată și se va conserva proprietatea structurii
că rădăcina conține cel puțin o cheie.
Structuri de Date – Lucrarea nr. 12

Fie următorul B-arbore:

În cele ce urmează operația de ștergere este ilustrată pentru mai multe situații posibile:
1. Dacă cheia k care va fi ștearsă se află în nodul x de tip frunză, aceasta va fi ștearsă
simplu, fără implicații.
2. Dacă cheia k care va fi ștearsă se află într-un nod intern x:
a) Dacă nodul y, predecesorul lui k în nodul x, conține cel puțin t chei, atunci
trebuie identificat un 𝑘0 predecesorul lui k în subarborele cu rădăcina y. 𝑘0 va
fi șters recursiv și îl va înlocui pe 𝑘 în 𝑥.
b) Dacă 𝑦 are mai puțin de 𝑡 chei, va fi examinat descendentul 𝑧 al lui 𝑘 în nodul
𝑥. Dacă 𝑧 are cel puțin 𝑡 chei, atunci se identifică succesorul lui 𝑘, 𝑘0 din
subarborele cu rădăcina în 𝑧. 𝑘0 va fi șters recursiv și îl va înlocui pe 𝑘 în 𝑥.
c) Altfel, dacă atât 𝑦 cât și 𝑧 au doar 𝑡 − 1 chei, 𝑘 și 𝑧 vor fi grupate în nodul 𝑦
care va conține 2𝑡 − 1 chei. Astfel, 𝑧 va putea fi șters și 𝑘 va putea fi eliminat
recursiv din 𝑦.
3. Dacă cheia 𝑘 nu există în nodul intern, se va determina rădăcina celui mai apropiat
subarbore care conține cheia 𝑘 în 𝑥. 𝑐(𝑖), dacă există. Dacă aceasta conține doar
𝑡 − 1 chei, se vor efectua pașii 3.a) sau 3.b) pentru a garanta parcurgerea până la
un nod care sa conțină cel puțin 𝑡 chei. Algoritmul va continua recursiv pe cel mai
apropiat descendent al lui 𝑥.
a) Dacă rădăcina subarborelui lui 𝑥 indicată de 𝑥. 𝑐(𝑖) conține doar 𝑡 − 1 chei, dar
are la rândul ei un subarbore care conține în rădăcină cel puțin 𝑡 chei, nodul 𝑥
va fi spart și una dintre chei va ajunge în rădăcina subarborelui. Una din cheile
localizate în frații din stânga sau din dreapta lui 𝑥. 𝑐(𝑖) va fi propagată către 𝑥,
făcându-se legătura cheii cu 𝑥. 𝑐(𝑖).
b) Dacă rădăcina subarborelui indicat de 𝑥. 𝑐(𝑖) și frații săi din stânga și din
dreapta conțin 𝑡 − 1 chei, atunci subarborele va fi reunit cu unul din frați.
Această operație presupune mutarea unei chei din nodul 𝑥 în jos către noul nod
ca și cheie mediană.
Deoarece majoritatea cheilor dintr-un b-arbore sunt localizate în nodurile de tip
frunză, operațiile de ștergere sunt folosite cu precădere pentru a elimina chei din frunze.
Procedura recursivă de ștergere acționează printr-o traversare de sus în jos a arborelui, fără
a avea nevoie să revină într-un nod. Însă, atunci când trebuie ștearsă o cheie dintr-un nod
Structuri de Date – Lucrarea nr. 12

intern, procedura implică o traversare de sus în jos a arborelui și necesită revenirea la nodul
din care a fost făcută ștergerea pentru a înlocui cheia eliminată cu unul din predecesorii sau
din succesorii ei (cazurile 2.a) și 2.b)).

Întreg procesul este prezentat grafic în cele ce urmează:

Cazul 1. Ștergerea cheii 6 dintr-un nod frunză

Cazul 2a. Ștergerea lui 13

Cazul 2c. Ștergerea lui 7

Cazul 3b. Ștergerea lui 4 (înălțimea arborelui scade)

Cazul 3a. Ștergerea lui 2


Structuri de Date – Lucrarea nr. 12

Dacă valoarea care trebuie ștearsă se regăsește în nodul curent, se va face ștergerea
în funcție de tipul nodului: nod intern sau nod frunză. Altfel, dacă valoarea ce trebuie
ștearsă nu este prezentă în nodul curent și nodul curent este un nod de tip frunză, se va
semnala imposibilitatea ștergerii. Dacă nodul în care ar trebui să existe valoarea are mai
puțin de (M-1)/2 valori, se va încerca împrumutul unei valori fie de la vecinul din stânga,
fie de la cel din dreapta. Dacă nodul din stânga conține mai mult de (M-1)/2 chei atunci se
va împrumuta o valoare de la acest nod. Dacă nodul din stânga conține mai puțin de (M-
1)/2 chei atunci se va încerca împrumutul unei valori de la nodul din dreapta. Dacă ambele
noduri stochează mai puțin de (M-1)/2 valori, atunci vom unifica nodurile prin regruparea
valorilor și a valoarii din nodul părinte într-un singur nod.
DeleteNode(&r, x)
pos=GetPosition(r,x);
if pos<n and val[pos]==x
then if IsLeaf(r)==true,
then DeleteFromLeafNode(r,pos);
else DeleteFromNonLeafNode(r,pos);
endif;
else
if IsLeaf(r)==false then return; endif;
if leg[pos] contains less or equal than (M-1)/2 keys,
then if pos!=1 and leg[pos] has more than (M-1)/2 keys
then BorrowFromPreviousNode(r,pos);
DeleteNode(leg[pos],x);
else if pos!=n+1 and leg[pos+1] has more than (M-1)/2 keys
then BorrowFromNextNode(r,pos);
DeleteNode(leg[pos],x);
else if pos!=n
then Merge(r,pos); //merge right
DeleteNode(leg[pos],x);
else Merge(r,pos-1); //merge left
DeleteNode(leg[pos-1],x);
endif;
endif;
endif;
else DeleteNode(leg[pos],x);
endif;
endif;
endDeleteNode;

Funcția 𝑓𝑖𝑛𝑑𝑃𝑜𝑠𝑖𝑡𝑖𝑜𝑛(𝑝, 𝑟) identifică poziția unei valori 𝑥 în cadrul unui nod 𝑝:

int findPosition(bnod *p, int x)


{
int pos = 0;
while (pos < p->n && x > p->val[pos])
pos++;
return pos;
}

Ștergerea dintr-un nod de tip frunză se va face folosind funcția următoare:


Structuri de Date – Lucrarea nr. 12

void removeFromLeaf(bnod *&p, int idx)


{
for (int i = idx + 1; i < p->n; ++i)
p->val[i - 1] = p->val[i];
p->n--;
}

void removeFromNonLeaf(bnod *&p, int idx)


{
int k = p->val[idx];

if (p->leg[idx]->n >(M - 1) / 2)
{
//inlocuim valoarea din nod cu valoarea maxima din subarborele stang si cautam
nodul de valoare maxima = cel mai din dreapta nod
int pred;
bnod *crt = p->leg[idx];
while (crt->tip != frunza) //cat timp nu am ajuns la un nod frunza
crt = crt->leg[crt->n];

//ultima valoare din frunza cea mai din dreapta din SAS
pred = crt->val[crt->n - 1];
p->val[idx] = pred;
crt->val[crt->n - 1] = k;

deleteNode(p->leg[idx], k);
}
else if (p->leg[idx + 1]->n > (M - 1) / 2)
{
//inlocuim valoarea din nod cu valoarea minima din subarborele drept
//cautam nodul de valoare minima = cel mai din stanga nod
bnod *crt = p->leg[idx + 1];
while (crt->tip != frunza)
crt = crt->leg[0];

//prima valoare din frunza cea mai din stanga din SAD
int succ = crt->val[0];
p->val[idx] = succ;
crt->val[0] = k;
deleteNode(p->leg[idx + 1], k);
}
else
{
//daca nodurile din stanga si dreapta au mai putin de (M-1)/2 noduri,
//vom uni cele 2 noduri, dupa care vom sterge valoare din nodul nou creat
merge(p, idx);
deleteNode(p->leg[idx], k);
}
}

/* vom imprumuta o valoare de la nodul din stanga, imprumutul se va face prin intermediul
nodului parinte */
void borrowFromPrevious(bnod *&p, int idx)
{

bnod *stg, *drt;


stg = p->leg[idx - 1];
drt = p->leg[idx];

//mutam valorile si legaturile cu o pozitie la dreapta


//pentru a face loc noii valori
for (int i = drt->n; i > 0; i--)
{
drt->val[i] = drt->val[i - 1];
}

if (drt->tip != frunza)
{
Structuri de Date – Lucrarea nr. 12

for (int i = drt->n; i >= 0; i--)


{
drt->leg[i + 1] = drt->leg[i];
}
}
drt->val[0] = p->val[idx - 1];

if (drt->tip != frunza)
{
drt->leg[0] = stg->leg[stg->n];
}

p->val[idx - 1] = stg->val[stg->n - 1];

//actualizam numarul de valori din cele 2 noduri


stg->n--;
drt->n++;
}

/* vom imprumuta o valoare din nodul din dreapta, imprumutul se va face prin intermediul
nodului parinte */
void borrowFromNext(bnod *&p, int idx)
{

bnod *stg, *drt;


stg = p->leg[idx];
drt = p->leg[idx + 1];

stg->val[stg->n] = p->val[idx];

if (stg->tip != frunza)
{
stg->leg[stg->n + 1] = drt->leg[0];
}
p->val[idx] = drt->val[0];

//in nodul din dreapta mutam valorile si legaturile cu o pozitie la stanga


for (int i = 1; i < drt->n; i++)
{
drt->val[i - 1] = drt->val[i];
}

if (drt->tip != frunza)
{
for (int i = 1; i <= drt->n; i++)
{
drt->leg[i - 1] = drt->leg[i];
}
}

//actualizam numarul de valori din cele doua noduri


stg->n++;
drt->n--;
}

void merge(bnod *&p, int idx)


{
bnod *childl = p->leg[idx];
bnod *childr = p->leg[idx + 1];

int t = (M - 1) / 2;

//imprumutam valoarea din nodul parinte


childl->val[t] = p->val[idx];

//copiem valorile din nodul din dreapta


for (int i = 0; i < childr->n; ++i)
childl->val[i + t + 1] = childr->val[i];
Structuri de Date – Lucrarea nr. 12

if (childl->tip != frunza)
{ //copiem legaturile din nodul din dreapta
for (int i = 0; i <= childr->n; ++i)
childl->leg[i + t + 1] = childr->leg[i];
}

//shiftam valorile si legaturile din nodul parinte


for (int i = idx + 1; i < p->n; ++i)
p->val[i - 1] = p->val[i];

for (int i = idx + 2; i <= p->n; ++i)


p->leg[i - 1] = p->leg[i];

//actualizam numarul de valori din nodul parinte si noul nod


childl->n += childr->n + 1;
p->n--;

delete childr;
}

3. Aplicații

Se construiește o structură de tip B-arbore pe baza unor chei citite de la tastatura (șir care
se încheie cu o valoare 0) :
a) Să se scrie o funcție pentru inserarea unei valori în arbore, funcție care va fi apelată
pentru fiecare valoare din șir;
b) Să se afișeze în inordine conținutul arborelui ;
c) Se citește o valoare pentru care să se verifice dacă este sau nu conținută în arbore ;
d) Se citește o valoare care să fie ștearsă din arbore și apoi să se afișeze arborele în
inordine ;
e) Să se scrie funcții pentru afișarea nodurilor de cheie minimă respectiv maximă din
B arbore;