Ricerca di una chiave: Search(x, k) if x == nil or k == x.key return x if k < x.key return Search(x.left, k) else return Search(x.right, k) Complessità O(h) dove h è l’altezza dell’albero.
Si può anche fare iterativa: Search(x, k) while x ≠ nil and k ≠ x.key if k < x.key x = x.left else x = x.right return x Complessità O(h) dove h è l’altezza dell’albero.
Ricerca del minimo e del massimo: Minimum(x) // x ≠ nil while x.left ≠ nil x = x.left return x Maximum(x) // x ≠ nil while x.right ≠ nil x = x.right return x Complessità O(h) dove h è l’altezza dell’albero.
Ricerca di successivo e precedente Successor(x) if x.right ≠ nil return Minimum(x.right) y = x.p while y ≠ nil and x == y.right x = y, y = y.p return y Il precedente si ottiene cambiando right in left e Minimum in Maximum. Complessità O(h) dove h è l’altezza dell’albero.
Inserzione di un nuovo elemento Insert(T, z) // z.left = z.right = nil x = T.root, y = nil // y padre di x while x ≠ nil // cerco dove mettere z y = x if z.key < y.key x = y.left else x = y.right z.p = y // metto z al posto della foglia x if y == nil T.root = z elseif z.key < y.key y.left = z else y.right = z Complessità O(h) dove h è l’altezza dell’albero.
Eliminazione di un elemento: Delete(T, z) // z ≠ nil if z.left == nil or z.right == nil // tolgo z y = z // che ha al più un solo figlio else // tolgo il successore di z che non ha // sottoalbero sinistro y = Successor(z), z.key = y.key // cerco l’eventuale unico figlio x di y if y.left == nil x = y.right else x = y.left
// metto x al posto di y if x ≠ nil x.p = y.p if y.p == nil T.root = x elseif y == y.p.left y.p.left = x else y.p.right = x Complessità O(h) dove h è l’altezza dell’albero.
Alberi rosso-neri Le operazioni sugli alberi binari di ricerca hanno complessità proporzionale all’altezza h dell’albero. Gli alberi rosso-neri sono alberi binari di ricerca in cui le operazioni Insert e Delete sono opportunamente modificate per garantire un’altezza dell’albero h = O(log n) Bisogna aggiunge un bit ad ogni nodo: il colore che può essere rosso o nero.
Oltre ad essere alberi binari di ricerca, gli alberi rosso-neri soddisfano le proprietà: ogni nodo è o rosso o nero; la radice è nera; le foglie (nil) sono tutte nere; i figli di un nodo rosso sono entrambi neri; per ogni nodo x i cammini da x alle foglie sue discendenti contengono tutti lo stesso numero bh(x) di nodi neri: l’altezza nera di x; Notare che il nodo x non viene contato in bh(x) anche se è nero.
Esempio di albero rosso-nero: 26 17 41 14 21 30 47 10 16 19 23 28 38 7 12 15 20 35 39 3
c nil b g nil d nil a nil e nil f nil
E’ utile usare una sentinella al posto di nil 26 17 41 14 21 30 47 10 16 19 23 28 38 7 12 15 20 35 39 3 ?
c b g d a e f ? T.nil
Proprietà: Un albero rosso-nero con n nodi interni ha altezza h ≤ 2 log2(n+1) Dimostrazione: Osserviamo che i nodi rossi in un cammino dalla radice r alle foglie possono essere al più bh(r) e quindi h ≤ 2 bh(r). Basta quindi dimostrare che bh(r) ≤ log2(n+1) ossia che n ≥ 2bh(r) - 1
Dimostriamo n ≥ 2bh(r) – 1 per induzione sulla struttura dell’albero rosso-nero. Se T = Ø la radice r è una foglia, bh(r) = 0 e n = 0 = 2bh(r) – 1
Sia T = (r,T1,T2) e siano r1 ed r2 le radici di T1 e T2 ed n1 ed n2 il numero di nodi interni di T1 e T2. Allora: bh(r1) ≥ bh(r)-1 bh(r2) ≥ bh(r)-1 n = 1+ n1 + n2 Per ipotesi induttiva
Conseguenza: Su di un albero rosso-nero con n nodi interni le operazioni Search, Minimum, Maximum, Successor e Predecessor richiedono tutte tempo O(log n)
Anche le operazioni Insert e Delete su di un albero rosso-nero richiedono tempo O(log n) ma siccome esse modificano l’albero possono introdurre delle violazioni alle proprietà degli alberi rosso-neri ed in tal caso occorre ripristinare le proprietà. Per farlo useremo delle operazioni elementari, dette rotazioni, che preservano la proprietà di albero binario di ricerca.
x y Left-Rotate(T, x) y x Left-Rotate(T, x) y = x.right // y non deve essere la sentinella T.nil x.right = y.left, y.left.p = x // y.left può essere T.nil y.p = x.p if x.p == T.nil T.root = y elseif x == x.p.left x.p.left = y else x.p.right = y x.p = y, y.left = x Complessità (1)
x y Right-Rotate(T, y) y x Right-Rotate(T, y) x = y.left // x non deve essere la sentinella T.nil y.left = x.right, x.right.p = y // x.right può essere T.nil x.p = y.p if y.p == T.nil T.root = x elseif y == y.p.left y.p.left = x else y.p.right = x y.p = x, x.right = y Complessità (1)