Strutture dati avanzate
Vedremo alcune strutture dati che permettono
di eseguire in modo efficiente un determinato
repertorio di operazioni:
- B-alberi .
- strutture dati per insiemi disgiunti.
B-alberi
I B-alberi sono alberi bilanciati adatti per
memorizzare grandi masse di dati in memoria
secondaria.
Sono simili agli alberi rosso-neri ma sono
progettati per minimizzare gli accessi alla memoria
secondaria.
Le operazioni sui B-alberi sono Insert, Delete e
Search alle quali si aggiungono Split e Join.
A differenza dei nodi degli alberi rosso-neri
che hanno una sola chiave e due figli, i nodi
dei B-alberi possono avere un numero di
chiavi n ≥ 1 ed n+1 figli.
Ecco un esempio di B-albero:
T.root
M
D H
B C
F G
Q T X
J K L
N P
R S
V W
Y Z
Il funzionamento di una memoria esterna su
disco può essere schematizzato nel seguente
modo:
Meccanismo di spostamento della testina
Testina di lettura-scrittura
Disco
Traccia
Pagina
Settore
Le operazioni di lettura/scrittura hanno
come unità elementare una pagina che
normalmente contiene da 2 a 10 kbyte.
L’accesso alle informazioni registrate su
disco richiede alcune operazioni meccaniche:
- spostamento della testina;
- rotazione del disco.
Il tempo di accesso al disco comprende il
tempo per posizionare la testina di lettura
scrittura e poi aspettare che la pagina passi
sotto la testina.
Esso è dell’ordine dei millisecondi.
Tipicamente 10-20 msec.
Il tempo di accesso alla memoria RAM, non
richiedendo operazioni meccaniche è invece
dell’ordine dei nanosecondi.
Tipicamente 10-100 nsec.
Spesso occorre più tempo per leggere i dati da
disco in memoria e per scrivere i risultati su disco
di quanto serva per la loro elaborazione.
Per questo, nel valutare la complessità delle
operazioni, terremo separate le due componenti:
1. tempo di lettura-scrittura su disco (che
assumiamo proporzionale al numero di pagine
lette o scritte).
2. tempo di CPU (tempo di calcolo in memoria
RAM).
L’operazione di lettura da disco è:
DiskRead(x)
dato il riferimento x ad un oggetto ricopia
l’oggetto da disco in memoria.
Assumiamo che sia possibile accedere al
valore di un oggetto soltanto dopo che esso
sia stato letto in memoria.
L’operazione di scrittura su disco è :
DiskWrite(x)
che, dato il riferimento x ad un oggetto presente in
memoria, copia tale oggetto su disco.
Tipicamente l’elaborazione di un oggetto residente
su disco segue lo schema:
DiskRead(x)
..... // elaborazioni che accedono-modificano x
DiskWrite(x) // non necessaria se x invariato
..... // elaborazioni che non modificano x
Le operazioni di lettura-scrittura devono
leggere e scrivere almeno una pagina e
questo anche se l’oggetto x è molto più
piccolo.
Siccome, con i B-alberi, la maggior parte del
tempo è utilizzata per le operazione di
lettura-scrittura da disco è opportuno
sfruttare al massimo ciascuna operazione.
Per questa ragione si usano nodi la cui
dimensione sia circa quella di una pagina.
Il numero massimo di figli di un nodo è
limitato dalla dimensione di una pagina.
Un numero massimo di figli compreso tra 50
e 2000 è la norma.
Un elevato grado di diramazione riduce di molto sia
l’altezza dell’albero che il numero di letture da disco
necessarie per cercare una chiave.
La figura seguente mostra che un B-albero di altezza 2 con
un grado di diramazione 1000 può contenere 109-1 chiavi
(un miliardo)
T.root
999
1000
999
999
999
1 nodo
999 chiavi
999
1000
.....
1000
.....
999
1000
999
1.000 nodi
999.000 chiavi
1.000.000 nodi
999.000.000 chiavi
Poiché la radice può essere tenuta stabilmente in
memoria, due accessi a disco sono sufficienti per
cercare una qualsiasi chiave.
Per semplicità di esposizione assumeremo che le
informazioni associate a ciascuna chiave (o i
puntatori a tali informazioni) siano memorizzate
assieme alle chiavi in tutti i nodi dell’albero.
Un’altra possibilità è mettere tali informazioni
nelle foglie e mettere nei nodi interni soltanto
chiavi e puntatori ai figli, massimizzando quindi
il grado di diramazione dei nodi interni.
Esempio di B-albero con grado massimo 4 e che
usa la prima tecnica:
T.root
M*
Q* T* X*
D* H*
B* C*
E* F*
J* K* L*
N* O* P*
R* S*
W*
Y* Z*
Ogni nodo deve poter contenere 3 chiavi, 4
puntatori ai figli e 3 puntatori alle informazioni
associate
10 campi in totale
Esempio di B-albero con grado massimo 4 e che
usa la seconda tecnica
T.root
M
D H K
P S W
B*C*D* E*F*H* J*K* L*M* N*O*P* Q*R*S* T*W* X*Y*Z*
Ogni nodo interno deve poter contenere 3 chiavi
e 4 puntatori ai figli.
Una foglia deve poter contenere 3 chiavi e 3
puntatori alle informazioni associate.
al più 7 campi in totale
Definizione di B-albero
Un B-albero T è un albero di radice T.root tale che:
1. Ogni nodo x contiene i seguenti campi:
a) x.n : il numero di chiavi presenti nel nodo.
b) x.key1 ≤ x.key2 ≤ .... ≤ x.keyx.n :
le x.n chiavi in ordine non decrescente.
c) x.leaf : valore booleano che è true se il
nodo x è foglia, false altrimenti.
2. se il nodo non è una foglia contiene anche
d) x.c1, x.c2,...., x.cx.n+1:
gli x.n+1 puntatori ai figli.
3. le chiavi x.key1, x.key2,...., x.keyx.n di un nodo
interno separano le chiavi dei sottoalberi.
In altre parole, se ki è una chiave qualsiasi del
sottoalbero di radice x.ci allora
k1 ≤ x.key1 ≤ k2 ≤ ... ≤ kx.n ≤ x.keyx.n ≤ kx.n+1
4. Le foglie sono tutte alla stessa profondità h detta
altezza dell’albero.
5. Vi sono limiti inferiori e superiori al numero di
chiavi in un nodo e tali limiti dipendono da una
costante t detta grado minimo del B-albero.
a) ogni nodo, eccetto la radice, ha almeno t -1
chiavi e, se non è una foglia, ha almeno t figli.
b) se l’albero non è vuoto, la radice contiene
almeno una chiave.
c) ogni nodo ha al più 2t -1 chiavi e, se non è
foglia, ha al più 2t figli.
Ad ogni chiave sono generalmente associate delle
informazioni ausiliarie.
Assumeremo implicitamente che quando viene
copiata una chiave vengano copiate anche tali
informazioni.
I B-alberi più semplici sono quelli con grado
minimo t = 2.
Ogni nodo interno ha 2,3 o 4 figli.
In questo caso si dicono anche 2-3-4-alberi.
Storicamente i 2-3-4-alberi sono predecessori sia
dei B-alberi che degli alberi rosso-neri.
Esercizio 20.
Perché non possiamo avere grado minimo t = 1?
Esercizio 21.
Calcolare il numero massimo di chiavi che può
contenere un B-albero in funzione del suo grado
minimo t e della sua altezza h
Esercizio 22.
Dire quale struttura dati si ottiene se in ogni nodo
nero di un albero rosso-nero conglobiamo i suoi
eventuali figli rossi.
Altezza di un B-albero
Proprietà. Ogni B-albero non vuoto di grado
minimo t con N chiavi ha altezza
h
N 1
log t 2
Dimostrazione.
Invece della diseguaglianza
h
N 1
log t 2
h
N

2
t
1
dimostriamo quella equivalente
Sia T.root la radice ed h ≥ 0 l’altezza.
Indichiamo con mi il numero di nodi a
livello i.
Allora
m0 = 1
m1 = T.root.n + 1 ≥ 2
mi ≥ t mi -1 ≥ t i -1 m1 per i > 1
e dunque mi ≥ 2t i -1
per ogni i ≥ 1
Quindi
N   x x .n
 T .root .n   i 1
h
 x .n
x di livello i
 1   i 1 2t i 1 ( t  1)
h
th 1
 1  2( t  1)
t 1
 1  2( t h  1)  2t h  1
L’altezza di un B-albero è O(logt n) dello stesso
ordine O(log2 n) degli alberi rosso-neri.
Ma la costante nascosta nella O è inferiore di un
fattore log2t che, per 50 ≤ t ≤ 2000, è compreso tra
5 e 11.
Operazioni elementari
Adottiamo le seguenti convenzioni per le
operazioni sui B-alberi:
1. La radice del B-albero è sempre in
memoria.
2. I nodi passati come parametri alle
procedure sono stati preventivamente
caricati in memoria.
Defineremo le seguenti operazioni:
- Create costruttore di un B-albero vuoto
- Search che cerca una chiave nell’albero
- Insert che aggiunge una chiave
- Delete che toglie una chiave
Ci serviranno anche alcune procedure ausiliarie:
- SplitChild
- InsertNonfull
- AugmentChild
- DeleteNonmin
- DeleteMax
Un B-albero vuoto si costruisce con la
procedura:
Create(T)
x = AllocateNode()
x.leaf = true
x.n = 0
DiskWrite(x)
T.root = x
la cui complessità è O(1).
Search(x, k)
i = 1, j = x.n+1
// x.key1 .. i-1 < k ≤ x.keyj .. x.n
while i < j
// Ricerca binaria
m = (i+j)/2
if k ≤ x.keym
j=m
else i = m+1
// x.key1.. i-1 < k ≤ x.keyi .. x.n
if i ≤ x.n and k == x.keyi
return (x, i)
elseif x.leaf
return nil
else DiskRead(x.ci)
return Search(x.ci , k)
Il numero di DiskRead è al più uguale all’altezza
h dell’albero e quindi O(logt N)
Il tempo di CPU di Search è:
T  ( h  1) log 2 ( 2t  1)
 O(log t N log 2 t )
 O(log 2 N )
Vediamo ora la Insert che aggiunge una
chiave.
Non possiamo aggiungere la chiave ad un
nodo interno perché dovremmo aggiungere
anche un sottoalbero.
L’aggiunta di una chiave ad un B-albero può
avvenire soltanto in una foglia e soltanto se
la foglia non è piena, cioè non ha il numero
massimo 2t-1 di chiavi.
Possiamo garantirci che la foglia a cui
arriveremo non sia piena se ad ogni passo
nella discesa dalla radice ci assicuriamo che il
figlio su cui scendiamo non sia pieno.
Nel caso in cui tale figlio sia pieno
chiamiamo prima una particolare funzione
SplitChild che lo divide in due.
La stessa cosa dobbiamo fare all’inizio se
la radice è piena.
Ecco come funziona Insert se la radice è
piena in un B-albero di grado minimo t = 4
root
P Q R S T U V
root
root.c1
T1 T2 T3 T4 T5 T6 T7 T8
y
P Q R S T U V
T1 T2 T3 T4 T5 T6 T7 T8
dopo di che richiama SplitChild
Ecco come funziona SplitChild su un figlio
pieno in un B-albero di grado minimo t = 4
x
... N W ...
x
x.ci
x.ci
y
P Q R S T U V
T1 T2 T3 T4 T5 T6 T7 T8
... N S W ...
y
P Q R
T1 T2 T3 T4
x.ci+1
T U V
z
T5 T6 T7 T8
SplitChild(x, i, y)
// x non pieno con y figlio i-esimo pieno
z = Allocate-Node()
z.leaf = y.leaf
z.n = t-1
// Sposta le ultime t -1 chiavi di y in z
for j = 1 to t -1
z.keyj = y.keyj+t
// Se y non è foglia sposta in z gli ultimi t puntatori di y
if not y.leaf
for j = 1 to t
z.cj = y.cj+t
y.n = t-1
// Mette la t-esima chiave di y in x
for j = x.n downto i
x.keyj+1 = x.keyj
x.keyi = y.keyt
// Aggiunge z come i+1-esimo figlio di x
for j = x.n+1 downto i+1
x.cj+1 = x.cj
x.ci+1 = z
x.n = x.n +1
// Scrive su disco i nodi modificati
DiskWrite(x), DiskWrite(y), DiskWrite(z)
La procedura Insert in un B-albero è:
Insert(T, k)
r = T.root
if r.n == 2t –1
s = AllocateNode()
T.root = s
s.n = 0, s.c1 = r, s.leaf = false
SplitChild(s,1, s.c1)
r=s
InsertNonfull(r, k)
InsertNonfull(x,k) // x non è pieno
if x.leaf
// Aggiunge k al nodo x
i = x.n
while i ≥ 1 and x.keyi > k
x.keyi+1 = x.keyi , i = i -1
x.keyi+1 = k, x.n = x.n+1
DiskWrite(x)
else
// Cerca il figlio in cui inserirla
i = 1, j = x.n+1
// x.key1..i-1 < k ≤ x.key j..x.n
while i < j
// Ricerca binaria
m = (i+j)/2
if k ≤ x.keym
j=m
else i = m+1
DiskRead(x.ci)
if x.ci .n == 2t -1
SplitChild(x, i, x.ci)
if k > x.keyi
i = i +1
InsertNonfull(x.ci , k)
La procedura SplitChild richiede al più 3
DiskWrite e tempo di CPU O(1)
(considerando t costante).
Se x è radice di un sottoalbero di altezza h
allora InsertNonfull(x, k) nel caso pessimo
effettua una DiskRead e una SplitChild ad
ogni livello per un totale di 4h accessi a
disco e richiede tempo di CPU O(h).
Quindi Insert richiede O(h) accessi a disco e
O(h) tempo di CPU.
G M P X
A C D E
J K
N O
R S T U V
Y Z
Insert(T,B)
G M P X
A B C D E
J K
N O
R S T U V
Y Z
G M P X
A B C D E
J K
N O
R S T U V
Y Z
Insert(T,Q)
G M P T X
A B C D E
J K
N O
Q R S
U V
Y Z
G M P T X
A B C D E
J K
N O
Q R S
U V
Y Z
Insert(T,L)
P
G M
A B C D E
J K L
T X
N O
Q R S
U V
Y Z
P
G M
A B C D E
J K L
T X
N O
Q R S
U V
P
Insert(T,F)
C G M
A B
D E F
J K L
Y Z
T X
N O
Q R S
U V
Y Z
Esercizio 23
Scrivere una funzione che cerca la chiave
minima contenuta in un B-albero ed una
funzione che data un valore c cerca la
minima chiave k > c presente nel B-albero.
Esercizio 24*
In un B-albero con grado minimo t = 2
vengono inserite le chiavi 1,2,3,....,n
nell’ordine. Valutare il numero di nodi
dell’albero in funzione di n.
Esercizio 25
Supponiamo che la dimensione di un nodo di
un B-albero si possa scegliere
arbitrariamente e che il tempo medio per
leggere un nodo da disco sia a+bt dove t è il
grado minimo ed a e b sono due costanti
Suggerire un valore ottimo per t quando
a = 30ms e b = 40s
Togliere una chiave da un B-albero è un po’
più complicato che inserirla.
Possiamo togliere una chiave solo da una
foglia e possiamo farlo soltanto se la foglia
non ha il minimo numero di chiavi t -1 (o
se la foglia è anche la radice).
Se dobbiamo togliere una chiave k = x.keyi
da un nodo x che non è foglia scambiamo
prima la chiave k con la massima chiave k'
del sottoalbero di radice x.ci.
k' = y.keyy.n è l’ultima chiave dell’ultima
foglia y del sottoalbero di radice x.ci.
Inoltre nel B-albero non vi è alcuna chiave di
valore compreso tra k' e k.
Scendendo dalla radice per cercare la chiave
k da togliere ci dobbiamo anche assicurare
che i nodi su cui ci spostiamo non abbiano
mai il numero di chiavi minimo t-1.
Se il nodo figlio su cui dobbiamo scendere
ha solo t-1 chiavi, prima di scendere
dobbiamo aumentarlo.
Possiamo farlo prendendo una chiave da uno
dei fratelli vicini o, se questo non è possibile,
riunendolo con uno dei fratelli vicini.
P
C G M
A B
D E F
J K L
T X
N O
Q R S
U V
P
Delete(T,F)
C G M
A B
D E
J K L
Y Z
T X
N O
Q R S
U V
Y Z
P
C G M
A B
D E
J K L
T X
N O
Q R S
U V
P
Delete(T,M)
C G L
A B
D E
J K M
Y Z
T X
N O
Q R S
U V
Y Z
P
C G L
A B
D E
J K M
T X
N O
Q R S
U V
Y Z
P
C G L
A B
D E
J K
T X
N O
Q R S
U V
Y Z
P
C G L
A B
D E
J K
T X
N O
Q R S
U V
P
Delete(T,G)
C L
A B
D E G J K
Y Z
T X
N O
Q R S
U V
Y Z
P
C L
A B
D E G J K
T X
N O
Q R S
U V
Y Z
P
C L
A B
D E J K
T X
N O
Q R S
U V
Y Z
P
C L
A B
D E J K
T X
N O
Q R S
U V
Y Z
Delete(T,D)
C L P T X
A B
D E J K
N O
Q R S
U V
Y Z
C L P T X
A B
D E J K
N O
Q R S
U V
Y Z
U V
Y Z
C L P T X
A B
E J K
N O
Q R S
C L P T X
A B
E J K
N O
Q R S
U V
Y Z
U V
Y Z
C L P T X
A B
E J K
N O
Q R S
C L P T X
A B
E J K
N O
Q R S
U V
Y Z
Delete(T,B)
E L P T X
A B C
J K
N O
Q R S
U V
Y Z
E L P T X
A B C
J K
N O
Q R S
U V
Y Z
U V
Y Z
E L P T X
A C
J K
N O
Q R S
La procedura di rimozione di una chiave da un Balbero è:
Delete(T, k)
if T.root.n ≠ 0
DeleteNonmin(T.root, k)
if T.root.n == 0 and not T.root.leaf
T.root = T.root.c1
si limita a richiamare la funzione ausiliaria
DeleteNonmin sulla radice dell’albero dopo
essersi assicurata che l’albero non sia vuoto.
Se al ritorno la radice è vuota e non è una foglia
essa viene sostituita con il figlio.
La procedura DeleteNonmin usa la
procedura AugmentChild per assicurarsi di
scendere sempre su di un nodo che non
contiene il minimo numero di chiavi.
La procedura AugmentChild aumenta il
numero di chiavi del figlio i-esimo
prendendo una chiave da un fratello vicino.
Se entrambi i fratelli vicini hanno anch’essi il
minimo numero di chiavi allora riunisce il
figlio i-esimo con uno dei fratelli.
AugmentChild(x, i, z) // x ha almeno t chiavi
// z, figlio i-esimo di x, ha solo t-1 chiavi
if i > 1
// z ha un fratello alla sua sinistra
y = x.ci-1, DiskRead(y)
if i > 1 and y.n > t-1 // si può togliere una chiave
// dal fratello sinistro
x
.... N R ...
x.ci-1
y
K L M
T1 T2 T3 T4
x
x.ci-1
x.ci
P Q
.... M R ...
z
T5 T6 T7
y
K L
T1 T2 T3
x.ci
N P Q
z
T4 T5 T6 T7
// Sposta avanti le chiavi in z
for j = z.n+1 downto 2
z.keyj = z.keyj-1
// Rotazione delle chiavi
z.key1 = x.keyi-1
x.keyi-1 = y.keyy.n
if not z.leaf
// Sposta anche i puntatori in z
for j = z.n+2 downto 2
z.cj = z.cj-1
z.c1 = y.cy.n+1
z.n = z.n+1, y.n = y.n-1
DiskWrite(x), DiskWrite(y), DiskWrite(z)
else
// i = 1 o il fratello sinistro y ha solo t -1 chiavi
if i ≤ x.n
// z ha un fratello alla sua destra
w = x.ci+1, DiskRead(w)
if i ≤ x.n and w.n > t -1
// si può togliere una chiave al fratello destro
x
x.ci
z
T1
x.ci+1
P Q
T2
x
... L R ...
T2
S T U
T4
x.ci
z
w
T5 T6 T7
... L S ...
T1
x.ci+1
P Q R
T2
T3 T4
T U
T5
w
T6 T7
// Rotazione delle chiavi
z.keyz.n+1 = x.keyi
x.keyi = w.key1
// Sposta indietro le chiavi in w
for j = 2 to w.n
w.keyj-1 = w.keyj
if not w.leaf
// Sposta anche i puntatori
z.cz.n+2 = w.c1
for j = 2 to w.n+1
w.cj-1 = w.cj
z.n = z.n+1, w.n = w.n-1
DiskWrite(x), DiskWrite(y), DiskWrite(z)
else // i = 1 oppure i = x.n+1 ma non entrambi
// Se i < x.n+1 esiste w che ha solo t -1 chiavi
// Se i > 1 esiste y che ha solo t -1 chiavi
if i ≤ x.n
// z ha il fratello destro w con t -1 chiavi
// Possiamo riunire z, x.keyi e w
x
... M R U...
x.ci
z
P Q
T1 T2
T3
... M U ...
x
x.ci+1
S T
T4
w
T5 T6
x.ci
z
P Q R S T
T1 T2 T3 T4 T5 T6
z.keyt = x.keyi
// Aggiunge x.keyi a z
for j = i +1 to x.n // Sposta chiavi e puntatori in x
x.keyj-1 = x.keyj
for j = i+2 to x.n+1
x.cj-1 = x.cj
x.n = x.n-1
for j = 1 to w.n
// Copia le chiavi di w in z
z.keyj+t = w.keyj
if not w.leaf
for j = 1 to w.n+1 // Copia i puntatori di w in z
z.ct+j = w.cj
z.n = 2t -1
DiskWrite(x), DiskWrite(z)
else // i = x.n+1
// e z ha fratello sinistro y con t-1 chiavi
// Possiamo riunire y, x.keyx.n e z
...... J M
x
x.cx.n
y
K L
T1 T2
T3
x
x.cx.n+1
N P
z
T4 T5 T6
...... J
x.cx.n
y
K L M N P
T1 T2 T3 T4 T5 T6
// Sposta x.key x.n in y
y.keyt = x.keyx.n
x.n = x.n-1
// Copia le chiavi di z in y
for j = 1 to z.n
y.keyj+t = z.keyj
if not y.leaf
// Copia anche i puntatori di z in y
for j = 1 to z.n+1
y.ct+j = z.cj
y.n = 2t -1
DiskWrite(x), DiskWrite(y)
DeleteNonmin(x, k) // x ha almeno t chiavi
i = 1, j = x.n+1
// x.key1..i-1 < k ≤ x.keyj..x.n
m = (i+j)/2
while i < j
if k ≤ x.keym
j=m
else i = m+1
if i ≤ x.n and k == x.keyi // Trovata k in x
if x.leaf // x è una foglia, posso togliere k
// spostando indietro le chiavi successive
for j = i+1 to x.n
x.keyj-1 = x.keyj
x.n = x.n-1
DiskWrite(x)
else // Trovata k in x ma x non è una foglia
DiskRead(x.ci)
if x.ci .n == t -1
AugmentChild(x, i, x.ci)
if i == x.n+2 // riuniti gli ultimi due figli
i=i-1
if x.keyi ≠ k
// k spostata in x.ci
DeleteNonmin(x.ci , k)
else // è rimasto k = x.keyi . Sposto in x.keyi la
// massima chiave del sottoalbero x.ci e poi
// elimino tale chiave dal sottoalbero
DeleteMax(x, i, x.ci)
else // k non è in x
// Può stare solo nel sottoalbero i-esimo
DiskRead(x.ci)
if x.ci .n == t -1
AugmentChild(x, i, x.ci)
if i == x.n+2 // riuniti gli ultimi due figli
i=i-1
DeleteNonmin(x.ci , k)
DeleteMax(x, i, y) // y ha almeno t chiavi
// Sposta in x.keyi la massima chiave
// del sottoalbero di radice y
if y.leaf // sposto in x.keyi l’ultima chiave di y
x.keyi = y.keyy.n
y.n = y.n-1
DiskWrite(x), DiskWrite(y)
else // scendo sull’ultimo figlio
z = y.cy.n+1
DiskRead(z)
if z.n == t -1
AugmentChild(y, y.n+1, z)
// y.n è aggiornato
DeleteMax(x, i, y.cy.n+1)
La procedura AugmentChild richiede al più 5
accessi a disco (due DiskRead e tre DiskWrite) e
tempo di CPU O(1) (considerando t costante).
Se y è radice di un sottoalbero di altezza h allora
DeleteMax(x, i, y) nel caso pessimo effettua 5h
accessi a disco e richiede tempo di CPU O(h).
Se x è radice di un sottoalbero di altezza h allora
DeleteNonmin(x, k) nel caso pessimo effettua 6h
accessi a disco e richiede tempo di CPU O(h).
Esercizio 26.
Si vuole aumentare un B-albero
aggiungendo ad ogni nodo x un campo h
che contiene l’altezza del sottoalbero di
radice x.
Dire quali sono le modifiche da apportare
a Insert e Delete.
Assicurarsi che la complessità asintotica
non aumenti.
Esercizio 27.
Siano dati due B-alberi T' e T'' ed una chiave
k tale che T' < k < T'' .
Scrivere un algoritmo che riunisce T', k e T''
in un unico B-albero T (operazione join).
Se T' e T'' hanno altezze h' ed h'' l’algoritmo
deve avere complessità O(|h'-h''|+1).
Se h' = h'' :
n' + n'' + 1 ≤ 2t -1
k
n' + n'' + 1 > 2t -1 ed n',n'' ≥ t -1
k
k
k
n' + n'' + 1 > 2t -1 ed n'' < t -1
m
k
m
k
Se h' > h'' :
k
k
k
k
k
m
m
k
Esercizio 28*
Siano dati un B-albero T ed una chiave k di T.
Trovare un algoritmo che divide T in un Balbero T' contenente le chiavi minori di k, la
chiave k e un B-albero T'' contenente le
chiavi maggiori di k (operazione split).
L’algoritmo deve avere complessità O(h+1)
in cui h è l’altezza di T.
k