370 likes | 505 Views
RB-alberi (Red-Black trees). Proprietà degli RB-alberi Rotazioni Inserimento Rimozione. Proprietà degli RB-alberi. Un RB-albero (Red-black tree) è un albero binario di ricerca dove ogni nodo ha in aggiunta un campo per memorizzare il suo colore : RED (rosso) o BLACK (nero).
E N D
RB-alberi(Red-Black trees) Proprietà degli RB-alberi Rotazioni Inserimento Rimozione
Proprietà degli RB-alberi • Un RB-albero (Red-black tree) è un albero binario di ricerca dove ogni nodo ha in aggiunta un campo per memorizzare il suo colore: RED (rosso) o BLACK (nero). • La colorazione avviene mediante regole precise, che assicurano che nessun cammino dalla radice ad una foglia risulti lungo più del doppio di qualsiasi altro. • Si ottiene così che l’albero è abbastanza bilanciato. • Ciascun nodo dell’albero contiene i campi color, key, left, right, e p.
Proprietà degli RB-alberi • Un RB-albero (red-black tree) soddisfa le seguenti proprietà: • Ciascun nodo è rosso o nero. • Ciascuna foglia (NIL) è nera. • Se un nodo è rosso allora entrambi i suoi figli sono neri. • Ogni cammino da un nodo ad una foglia sua discendente contiene lo stesso numero di nodi neri. Dalla proprietà 4 si definisce la b-altezza di un nodo x. bh(x) = numero di nodi neri su un cammino da un nodo x, non incluso, ad una foglia sua discendente. Nota: Un nodo nero può avere figli rossi o neri. Tutti i nodi interni hanno due figli.
Red-Black Albero root[T] 9 5 15 4 7 12 19 NIL NIL NIL 2 8 11 13 6 NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL
Altezza di RB-albero Un RB-albero con n nodi interni ha un’altezza di al più 2 lg(n+1). Dim.: Si ha che ogni sottoalbero con radice in x contiene almeno 2bh(x)-1 nodi interni. Dimostrazione per induzione: Se bh(x)=0, x è una foglia e si ha 20-1=1-1=0. Supponiamo vera per bh(x)=k-1, allora consideriamo un nodo x con bh(x)=k e k>0. x è un nodo interno (k>0) e ha due figli con b-altezzabh(x) o bh(x)-1. Allora i nodi interni nel sottoalbero con radice in x sono almeno (2bh(x)-1-1)+(2bh(x)-1-1)+1= 2bh(x)-1.
Altezza di RB-albero Dim. (prosegue): Sia h l’altezza del RB-albero. Per la proprietà 3 almeno metà dei nodi sono neri. Quindi la b-altezza della radice è almeno h/2. Dunque, i nodi interni n sono almeno 2h/2-1. 2h/2-1 ≤ n h ≤ 2 lg(n+1).
Operazioni su un RB-albero • Le operazioni di SEARCH, PREDECESSOR, MINIMUM e MAXIMUM sono quelle viste per un albero binario di ricerca. Possono essere eseguite in un RB-albero con un tempo pari a O(h) = O(lg(n)). • Le operazioni di INSERT e DELETE si complicano, poiché devono tener conto delle proprietà aggiuntive del RB-albero. Più precisamente si devono effettuare dei cambiamenti in modo da ripristinare le regole di colorazione dei nodi. • Tuttavia, RB-INSERT() e RB-DELETE() possono essere eseguite in tempo O(lg(n)).
Rotazioni • Le rotazioni sono delle operazioni che cambiano la struttura dei puntatori di due nodi (padre e figlio). • E’ un’operazione locale dell’albero di ricerca che non modifica l’ordinamento delle chiavi. • Questa operazione sono utilizzate da INSERT e DELETE per ripristinare le proprietà violate degli RB-alberi mantenendo l’albero sempre un albero binario di ricerca.
Rotazioni RIGHT-ROTATE(T,y) y x x y LEFT-ROTATE(T,x) c a a b b c a ≤ x ≤ b ≤ y ≤ c I due nodi interni x e y potrebbero essere ovunque nell’albero. Le lettere a, b e c rappresentano sottoalberi arbitrari. Un’operazione di rotazione mantiene l’ordinamento delle chiavi secondo la visita inorder.
Rotazioni • LEFT-ROTATE(T,x) • y ← right[x] // inizializzazione di y • right[x] ← left[y] // inizio rotazione: sposta b • if left[y] ≠ NIL • then p[left[y]] ← x • p[y] ← p[x] // metti y al posto di x • if p[x] = NIL • then root[T] ← y // se x era la radice • else if x = left[p[x]] // modifica puntatore di p[x] • then left[p[x]] ← y • else right[p[x]] ← y • left[y] ← x // metti x a sinistra di y • p[x] ← y Si opera solo sui puntatori lasciando inalterati gli altri campi. RIGHT-ROTATE() è un procedura simmetrica. Entrambe le procedure sono eseguite in un tempo costante, quindi O(1).
Inserimento • RB-INSERT(T,x) • TREE-INSERT(T,x) // ins. come albero b. di ricerca • color[x] ← RED // la proprietà 3 potrebbe essere violata p[x] = RED Si usa la procedura TREE-INSERT per inserire il nodo x nel RB-albero, visto che è un albero binario. Il nodo x viene colorato di rosso (i due figli NIL sono neri). Proprietà 1 e 2 sono soddisfatte: il nuovo nodo è rosso e ha due figli NIL neri. Proprietà 4 si mantiene il nuovo nodo è rosso e non aumenta la b-altezza fino ai suoi due figli neri. Potrebbe essere violata la proprietà 3, che dice che un nodo rosso non può avere figli rossi.
Inserimento • while x ≠ root[T] and color[p[x]] = RED // finché prop. 3 violata • do if p[x] = left[p[p[x]]] // 3 casi: p[x] sinistra di p[p[x]] • then y ← right[p[p[x]]] • if color[y] = RED // caso 1: p[x] e fratello y RED • then color[p[x]] ← BLACK // y “zio” di x • color[y] ← BLACK • color[p[p[x]]] ← RED • x ← p[p[x]] Il ciclo while continua ad essere eseguita finché la proprietà 3 risulta invariata: x e p[x] sono entrambi RED. Lo scopo è quello di far “risalire” nell’albero questa violazione, mentre tutte le altre proprietà rimangono valide. Dopo ogni iterazione: o il puntatore x risale l’albero o viene eseguita qualche rotazione ed il ciclo termina.
Inserimento Ci sono 3 casi + altri 3 simmetrici (la linea 4 li suddivide). Caso 1: y il fratello di p[x] è esso stesso RED. Quindi p[p[x]] è BLACK. Possibile nuova violazione tra p[p[x]] e suo padre: se entrambi RED! p[p[x]] p[p[x]] y p[x] y p[x] x x
Inserimento • else if x = right[p[x]] // caso 2: zio y BLACK • then x ← p[x] // e x a destra di p[x] • LEFT-ROTATE(T,x) Caso 2: lo zio di x è BLACK e x è figlio destro di p[x]. Quindi, p[p[x]] è BLACK (unica violazione: tra x e p[x]!). bh(a) = bh(b) = bh(c) p[p[x]] Caso 3 x ← p[x] LEFT-ROTATE(T,x) y y p[x] x y a x c x c b b a
Inserimento • color[p[x]] ← BLACK // caso 3: zio y BLACK • color[p[p[x]]] ← RED // e x a sinistra di p[x] • RIGHT-ROTATE(T,p[p[x]]) Caso 3: lo zio y di x è BLACK e x (RED) è figlio sinistro di p[x] (RED). Quindi, p[p[x]] è BLACK (unica violazione: tra x e p[x]!). color[p[x]] ← B color[p[p[x]]] ← R p[p[x]] p[p[x]] p[x] RIGHT-ROTATE(T,p[p[x]]) p[p[x]] p[x] p[x] y y x c a b c x x c y a b a b bh(a) = bh(b) = bh(c) = bh(y)
Inserimento Il Caso 2 si trasforma nel Caso 3. La proprietà 4 si preserva. A questo punto (Caso 3) si deve effettuare alcuni cambiamenti di colore ed una rotazione destra che preserva la proprietà 4. Dopo il Caso 3 il ciclo while non è più ripetuto, in quanto p[x] è nero. A questo punto tutte le proprietà sono rispettate.
Inserimento • else (altri 3 Casi simmetrici: scambia “right” e “left”) • color[root[T]] ← BLACK // nel caso x risalga fino alla radice La linea 17 è l’inizio degli altri 3 Casi simmetrici. Il codice risulta uguale, basta scambiare “right” con “left” e viceversa. L’ultima riga è importante: la radice risulta sempre nera. Se x non è la radice e p[x] risulta rosso, allora p[x] non è esso stesso la radice dell’albero e, quindi, esiste p[p[x]]. Questa condizione è fondamentale per il corretto funzionamento dell’algoritmo.
Inserimento • RB-INSERT(T,x) • TREE-INSERT(T,x) // ins. come albero b. di ricerca • color[x] ← RED // la proprietà 3 violata? • while x ≠ root[T] and color[p[x]] = RED // finché prop. 3 violata • do if p[x] = left[p[p[x]]] // 3 casi: p[x] sinistra di p[p[x]] • then y ← right[p[p[x]]] • if color[y] = RED // caso 1: p[x] e fratello y RED • then color[p[x]] ← BLACK // y “zio” di x • color[y] ← BLACK • color[p[p[x]]] ← RED • x ← p[p[x]] • else if x = right[p[x]] // caso 2: zio y BLACK • then x ← p[x] // e x a destra di p[x] • LEFT-ROTATE(T,x) • color[p[x]] ← BLACK // caso 2: zio y BLACK • color[p[p[x]]] ← RED// e x a sinistra di p[x] • RIGHT-ROTATE(T,p[p[x]]) • else (altri 3 Casi simmetrici: scambia “right” e “left”) • color[root[T]] ← BLACK // nel caso x risalga fino alla radice
Inserimento Analisi del tempo di esecuzione: • Dato che l’altezza di un RB-albero di n nodi è O(lg(n)), la chiamata TREE-INSERT() costa tempo O(lg(n)). • Il ciclo while è ripetuto solo se si esegue il Caso 1 con il conseguente spostamento verso la radice di x. • Per cui il numero massimo di volte che il ciclo while può essere ripetuto è O(lg(n)). • Ogni ciclo while è eseguito in tempo costante. • Quindi, RB-INSERT() impiega un tempo totale pari a O(lg(n)).
Inserimento Un esempio pratico root[T] RB-INSERT(T,x) key[x] = 5 11 4 5 15 3 7 13 19 NIL NIL NIL NIL NIL 8 2 6 NIL NIL NIL NIL NIL NIL
Inserimento Un esempio pratico root[T] Caso 1 11 4 15 3 7 13 19 y NIL NIL NIL NIL NIL 8 2 6 x NIL NIL NIL NIL NIL 5 Proprietà 3 violata NIL NIL
Inserimento Un esempio pratico root[T] Caso 2 11 y 4 15 Proprietà 3 violata x 3 7 13 19 NIL NIL NIL NIL NIL 8 2 6 NIL NIL NIL NIL NIL 5 NIL NIL
Inserimento Un esempio pratico root[T] Caso 3 Proprietà 3 violata 11 y 7 15 x 8 4 13 19 NIL NIL NIL NIL NIL NIL 3 6 NIL NIL 5 2 NIL NIL NIL NIL
Inserimento Un esempio pratico root[T] Nota: l’albero è più bilanciato! 7 4 11 8 3 6 15 NIL NIL 5 2 13 19 NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL
Rimozione La rimozione di un nodo da un RB-albero è un po’ più complicata dell’inserimento. Per semplificare il codice si fa uso di una sentinella nil[T] al posto di ogni foglia NIL. Il colore di nil[T] è BLACK, mentre gli altri campi (p, left, right, key) sono arbitrari. Tutti i puntatori a NIL sono sostituiti con puntatori a nil[T]. Il vantaggio è quello di poter trattare nil[T] come un nodo e poter quindi assegnare il valore p[nil[T]] necessario per il corretto funzionamento dell’algoritmo.
Rimozione • RB-DELETE(T,z) • if left[z] = nil[T] o right[z] = nil[T] • then y ← z // z ha 0 o 1 figlio • else y ← TREE-SUCCESSOR(z) // z ha due figli, trova succ(z) • if left[y] ≠ nil[T] // x punta ad eventuale • then x ← left[y] // unico figlio di y, altrimenti a nil[T] • else x ← right[z] • p[x] ← p[y] // taglia fuori y • if p[y] = nil[T] • then root[T] ← x // se y è la radice • else if y = left[p[y]] // altrimenti • then left[p[y]] ← x // completa eliminazione di y • else right[p[y]] ← x • if y ≠ z // se y è il successore • then key[z] ← key[y] // copia y in z • copia anche altri attributi di y in z • if color[y] = BLACK // chiama fixup se proprietà 4 • then RB-DELETE-FIXUP(T,x) // risulta violata • return y
Inserimento Un esempio pratico root[T] RB-DELETE(T,x) con key[x] = 2 7 4 11 8 3 6 15 z = y 5 2 13 19 NIL
Inserimento Un esempio pratico root[T] RB-DELETE(T,x) con key[x] = 2 7 4 11 8 3 6 15 5 13 19 NIL
Rimozione La rimozione di un nodo da un RB-albero è una semplice modifica della procedura TREE-DELETE. Dopo aver rimosso il nodo y, si chiama RB-DELETE-FIXUP se color[y] è BLACK. Questo perché viene eliminato un nodo interno nero e la proprietà 4 sulla b-altezza non è più valida. In RB-DELETE-FIXUP(T,x) si considera che x abbia un colore nero “doppio”, cioè pari a 2. La proprietà 4 risulta cosi soddisfatta. Lo scopo è di spostare il colore nero di troppo verso la radice in modo da eliminarlo.
Rimozione • RB-DELETE-FIXUP(T,x) • while x ≠ root[T] and color[x] = BLACK // spingi su BLACK extra verso radice • do if x = left[p[x]] // 4 casi se x = left[p[x]] • then w ← right[p[x]] • if color[w] = RED // caso 1 • then color[w] ← BLACK // fratello RED • color[p[x]] ← RED • LEFT-ROTATE(T, p[x]) • w ← right[p[x]] Caso 2, 3 o 4 Nero per color[w] Nero doppio Caso 1 D B x w B A D E w x C A e a b f C E Nero doppio c d a b c d e f
Rimozione • if color[left[w]] = BLACK and color[right[w]] = BLACK • then color [w] ← RED // caso 2: w BLACK • x ← p[x] // figli di w BLACK Se il nuovo p[x] è RED il ciclo while termina. Per esempio, se si passa dal Caso 1 al Caso 2, il nuovo x (p[x]) è RED. Aggiungi colore nero Caso 2 nuovo x B B w x D D A A C C E E a b a b Nero doppio e e c d f c d f
Rimozione • else if color [right[w]] = BLACK // caso 3: w BLACK, left[w] RED • then color[left[w]] ← BLACK // right[w] BLACK • color[w] ← RED • RIGHT-ROTATE(T,w) • w ←right[p[x]] Si è trasformato il Caso 3 nel Caso 4. B Caso 4 Caso 3 nuovo w x B C A w x D A c D a b C E Nero doppio a b d E Nero doppio e c d f e f
Rimozione • color[w] ← color[p[x]] // caso 4: w BLACK • color[p[x]] ← BLACK // right[w] RED • color[p[x]] ← BLACK • LEFT-ROTATE(T,p[x]) • x ← root[T] // per terminare il ciclo e assicurarsi che root sia BLACK color ← cpx due neri Caso 4 cpx = color[p[x]] D B w w x B E D A A C e f C E a b nuovo x = root[T] c a b d Nero doppio e c d f
Rimozione • RB-DELETE-FIXUP(T,x) • while x ≠ root[T] and color[x] = BLACK // spingi su BLACK extra verso radice • do if x = left[p[x]] // 4 casi se x = left[p[x]] • then w ← right[p[x]] • if color[w] = RED // caso 1: w RED • then color[w] ← BLACK // fratello RED • color[p[x]] ← RED • LEFT-ROTATE(T, p[x]) • w ← right[p[x]] • if color[left[w]] = BLACK and color[right[w]] = BLACK • then color [w] ← RED // caso 2: w BLACK • x ← p[x] // figli di w BLACK
Rimozione • else if color [right[w]] = BLACK // caso 3: w BLACK • then color[left[w]] ← BLACK // right[w] BLACK • color[w] ← RED • RIGHT-ROTATE(T,w) • w ←right[p[x]] • color[w] ← color[p[x]] // caso 4: w BLACK • color[p[x]] ← BLACK // right[w] RED • color[p[x]] ← BLACK • LEFT-ROTATE(T,p[x]) • x ← root[T] // per terminare il ciclo e assicurarsi che root sia BLACK • else (analogo con “right” e “left” scambiati) • color[x] ← BLACK
Rimozione In tutti i quattro casi (+ gli i 4 simmetrici) la proprietà 4 si mantiene dopo le modifiche, ossia il numero di nodi neri dalla radice a ognuno dei sottoalberi a, b, c ,d , e ed f rimane identico dopo la trasformazione. Tempo di esecuzione: Data l’altezza dell’albero è pari a O(lg(n)), la chiamata di RB-DELETE() senza considerare RB-DELETE-FIXUP() ha un costo di O(lg(n)) (vedi rimozione in un albero binario). In RB-DELETE-FIXUP(), il Caso 1 si riconduce ai Casi 2, 3 o 4 in tempo costante. I Casi 3, e 4 fanno terminare la procedura dopo l’esecuzione di un numero costante di passi.
Rimozione Il Caso 2 è l’unico per il quale si potrebbe ripetere l’esecuzione del ciclo while. Questo può avvenire al più O(lg(n)) volte. Quindi la procedura RB-DELETE-FIXUP() impegna un tempo O(lg(n)) ed esegue al più tre rotazioni. Il tempo di esecuzione complessivo della procedura RB-DELETE() risulta O(lg(n)). Su un RB-albero le operazioni di SEARCH, PREDECESSOR, MINIMUM, MAXIMUM, INSERT e DELETE sono tutte O(lg(n)).