Alberi binari di ricerca
In questa pagina 6
Un albero binario di ricerca (ABR, binary search tree) implementa una mappa ordinata (vedi Mappe e dizionariADT mappa (chiavi distinte) con get, put, remove, keySet/values/entrySet; famiglia mappa/mappa ordinata/dizionario (multimappa); applicazioni; implementazioni semplici ma poco efficienti (lista, array indicizzato dalle chiavi); dizionario realizzato con una mappa di liste.Mappe e dizionari →) su chiavi di un universo ordinato. È un albero binario proprio (vedi Alberi binariAlbero binario e albero binario proprio; interfaccia; relazioni tra nodi, foglie e altezza (m = n-m+1, h+1 <= m <= 2^h, 2h+1 <= n <= 2^(h+1)-1) con dimostrazioni; visita inorder; parse tree e valutazione di espressioni; heightSum come esempio di calcolo di un'informazione più ricca.Alberi binari →) i cui nodi interni memorizzano entry e le cui foglie sono vuote (sentinelle esterne), tale che per ogni nodo interno con chiave :
- le chiavi del sottoalbero sinistro di sono ;
- le chiavi del sottoalbero destro di sono .
Conseguenza: la visita inorder tocca le entry in ordine crescente di chiave. L'albero con entry ha nodi interni e foglie (vedi la relazione in Alberi binariAlbero binario e albero binario proprio; interfaccia; relazioni tra nodi, foglie e altezza (m = n-m+1, h+1 <= m <= 2^h, 2h+1 <= n <= 2^(h+1)-1) con dimostrazioni; visita inorder; parse tree e valutazione di espressioni; heightSum come esempio di calcolo di un'informazione più ricca.Alberi binari →).
Ricerca
Algoritmo TreeSearch(k, v)
Input: chiave k, nodo v Output: nodo di T_v con chiave k, o la foglia in cui k andrebbe
if T.isExternal(v) OR v.getElement().getKey() = k then return v
if k < v.getElement().getKey() then return TreeSearch(k, T.left(v))
else return TreeSearch(k, T.right(v))Per cercare in tutto l'albero si parte da T.root(). La correttezza è immediata dalla definizione. Complessità (albero della ricorsione, vedi Algoritmi ricorsiviAlgoritmi ricorsivi come induzione eseguita; albero della ricorsione e record di attivazione nello stack; esempi ReverseArray, LinearSum, Power in tempo logaritmico, Fibonacci ricorsivo (esponenziale) e con memoizzazione; analisi della complessità con l'albero della ricorsione e correttezza per induzione.Algoritmi ricorsivi →): le chiamate sono sui nodi di un cammino di lunghezza , una per nodo, ciascuna ; esistono istanze in cui il cammino arriva alla foglia più profonda: (intendendo per includere ).
Metodi della mappa
get(k):TreeSearch(k, root); se è esterno restituiscenull, altrimenti il valore di . .put(k, x):TreeSearch. Se è interno si sostituisce il valore e si restituisce il vecchio; altrimenti è una foglia e si espande: diventa nodo interno con la entry e due nuove foglie come figli; si incrementa il numero di entry e si restituiscenull. .remove(k):TreeSearch; se è esterno restituiscenull. Altrimenti due casi:- ha almeno un figlio foglia: si elimina e la foglia e si collega l'altro figlio al padre di (se era la radice, diventa radice);
- ha due figli interni: si prende = predecessore inorder di (il nodo più a destra del sottoalbero sinistro: entry con la chiave immediatamente minore) e si copia la sua entry in ; non ha figlio destro interno, quindi si elimina con il caso 1. L'albero resta di ricerca: la nuova chiave in è maggiore di tutte le chiavi a sinistra e minore di quelle a destra.
Costo: per la ricerca, per trovare il predecessore, per aggiornare i nodi: .
Esempi
Inserimenti (albero inizialmente vuoto) delle chiavi : radice; a destra; a sinistra; figlio destro di ; figlio sinistro di ; figlio destro di ; figlio sinistro di ; figlio destro di . Preorder , inorder , altezza .
L'ordine di inserimento conta. Le stesse chiavi inserite nell'ordine danno un albero diverso (preorder , altezza , tutta la parte destra è un cammino).
Altri due casi d'esame: inserendo si ottiene un albero perfetto di altezza , con inorder ; inserendo la radice è , a sinistra con figlio destro che ha figlio sinistro , a destra con figli e (inorder , altezza ).
Rimozioni dall'albero del primo esempio: remove(26) (caso 1: ha solo foglie): inorder ; poi remove(24) ( ha solo il figlio sinistro : caso 1, prende il suo posto); poi remove(30) sulla radice (caso 2: predecessore , che sostituisce la radice): preorder .
Altezza e caso pessimo
Le tre operazioni costano e può arrivare a (chiavi inserite in ordine crescente o decrescente: l'albero degenera in una lista), per cui al caso pessimo sono ; in un albero bilanciato . Per ottenere garantito: alberi che si ristrutturano (AVL, rosso-neri) oppure nodi più capienti, gli alberi (2,4) (vedi Multi-way search tree e alberi (2,4)Multi-way search tree (nodi con più entry, d figli e d-1 chiavi ordinate), ricerca in O(d_max h), un MWS tree con n entry ha n+1 foglie; albero (2,4): nodi con 2-4 figli e tutte le foglie alla stessa profondità, altezza Theta(log n) con dimostrazione, operazioni in Theta(log n); idea di overflow e underflow; tabella di confronto con gli alberi binari di ricerca.Multi-way search tree e alberi (2,4) →).
Alberi aumentati
Spesso gli esercizi aggiungono a ogni nodo un campo che riassume il sottoalbero e che si aggiorna, risalendo, a ogni inserimento o rimozione ( nodi coinvolti).
Contare le entry con chiave con v.size = numero di entry di :
Algoritmo contaMinoriUguali(v, k)
Input: nodo v, chiave k Output: numero di entry in T_v con chiave <= k
if T.isExternal(v) then return 0
if k < v.key then return contaMinoriUguali(T.left(v), k)
else return size(T.left(v)) + 1 + contaMinoriUguali(T.right(v), k)(con size di una foglia uguale a ). Se tutto e il sottoalbero destro sono scartati; altrimenti e tutto il sottoalbero sinistro contano e si prosegue a destra. Si scende lungo un solo cammino: .
Interrogazione con v.max (esame sui terremoti): è una mappa tempo intensità e ogni nodo ha v.max = massima intensità in . Si chiede se esiste una entry con chiave e valore .
Algoritmo TFR(v, t, i)
Input: nodo v, tempo t, intensità i Output: yes/no
if T.isExternal(v) OR v.max < i then return no (nessuna entry in T_v può bastare)
if v.key >= t then
if v.value >= i then return yes
if TFR(T.left(v), t, i) = yes then return yes (a sinistra ci sono chiavi < v.key, forse >= t)
return TFR(T.right(v), t, i)Se tutto il sottoalbero sinistro ha chiavi e si va solo a destra. Se tutto il sottoalbero destro ha chiavi , quindi per esso conta solo max.
Complessità . I nodi su cui l'algoritmo prosegue "a cavallo" della soglia formano un unico cammino radice-foglia. Da ciascuno di essi parte al più una seconda chiamata, sul sottoalbero interamente nell'intervallo: se il suo max è termina subito con in ; altrimenti esiste una entry valida, e la discesa lungo il cammino dei nodi con max la trova restituendo dopo al più passi (e a quel punto tutto finisce). In totale . Il codice è stato confrontato con la ricerca esaustiva su 500 alberi casuali.
Errori comuni
- Credere che : vale solo se l'albero è bilanciato; il caso pessimo è .
- Nel caso 2 della rimozione, eliminare senza sostituirlo con il predecessore (o con il successore): l'albero perde la proprietà di ricerca.
- Supporre che inserire le stesse chiavi in ordine diverso dia lo stesso albero.
- Dimenticare di aggiornare i campi aumentati (
size,max) lungo il cammino dopoputeremove.
Versione ripasso
- ABR = albero binario proprio con entry nei nodi interni (foglie vuote): sinistra chiave destra. Inorder = chiavi crescenti; entry foglie (vedi Alberi binariAlbero binario e albero binario proprio; interfaccia; relazioni tra nodi, foglie e altezza (m = n-m+1, h+1 <= m <= 2^h, 2h+1 <= n <= 2^(h+1)-1) con dimostrazioni; visita inorder; parse tree e valutazione di espressioni; heightSum come esempio di calcolo di un'informazione più ricca.Alberi binari →).
- TreeSearch(, ): se esterno o chiave uguale restituisce , altrimenti scende a sinistra o a destra; .
- get ; put: foglia nodo interno con due nuove foglie, o sostituzione del valore; ; remove:
- caso 1 (almeno una foglia figlia): il figlio interno sostituisce ;
- caso 2 (due figli interni): si copia in l'entry del predecessore inorder (massimo del sottoalbero sinistro) e si elimina quel nodo col caso 1; .
- Altezza: (inserimenti ordinati ⇒ lista), nel caso pessimo; bilanciato (AVL, rosso-neri, (2,4): vedi Multi-way search tree e alberi (2,4)Multi-way search tree (nodi con più entry, d figli e d-1 chiavi ordinate), ricerca in O(d_max h), un MWS tree con n entry ha n+1 foglie; albero (2,4): nodi con 2-4 figli e tutte le foglie alla stessa profondità, altezza Theta(log n) con dimostrazione, operazioni in Theta(log n); idea di overflow e underflow; tabella di confronto con gli alberi binari di ricerca.Multi-way search tree e alberi (2,4) →). L'ordine di inserimento cambia l'albero.
- Esempi: ⇒ inorder , ; ⇒ perfetto, ; ⇒ inorder , .
- Aumentati (campi aggiornati risalendo):
size⇒ conteggio chiavi lungo un solo cammino, ;max⇒TFR: se scarta il sottoalbero; . - Pseudocodice:
TreeSearch(k, v): se è esterno ov.key = krestituisce ; sek < v.keyricorre suleft(v), altrimenti suright(v).get(k):TreeSearch(k, root); se esterno restituiscenull, altrimenti il valore. - put: esterno ⇒ diventa nodo interno con la entry e due nuove foglie (expandExternal),
size++, restituiscenull; interno ⇒ sostituisce il valore e restituisce il vecchio. - remove, caso 2: = nodo più a destra del sottoalbero sinistro di (predecessore inorder, chiave immediatamente minore); si copia la sua entry in e si elimina , che non ha figlio destro interno, con il caso 1. Da :
remove(26),remove(24),remove(30)danno preorder finale . - Fatto: entry ⇒ nodi interni e foglie.
- Errori: sempre; predecessore dimenticato nel caso 2; stesso albero per ordini diversi; campi non aggiornati.