Alberi binari di ricerca
In questa pagina 6
Definizione (albero binario di ricerca). 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 (Campi ordinati (Q e R)Gli assiomi di somma, prodotto e ordine comuni a Q e R, da cui derivano le regole di equazioni e disequazioni.Campi ordinati (Q e R) →). È l'analogo ad albero della ricerca binaria su array (Ricerca lineare e binariaRicerca lineare su sequenze qualsiasi in O(n); ricerca binaria su sequenze ordinate in O(log n), versione iterativa e ricorsiva, invariante e errori di indice; modulo bisect.Ricerca lineare e binaria →): a ogni confronto si scarta un intero sottoalbero come là si scartava metà intervallo, con il vantaggio che inserire e togliere non richiede di spostare gli elementi. È 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 .
Esempio. Inserendo in un ABR vuoto si ha radice , figlio destro e figlio sinistro ; l'inorder è .
Conseguenza: la visita inorder (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 →, Visite di alberiVisite in preorder e postorder come schemi generali (template) da adattare; complessità Theta(n + somma dei costi di visita) perché la somma dei figli è n-1; esempi (indice di un libro, spazio occupato in un file system); profondità con il preorder, altezza con il postorder; antenato comune più basso.Visite di alberi →) tocca le entry in ordine crescente di chiave: per ogni nodo prima tutto il sottoalbero sinistro (chiavi minori), poi il nodo, poi il destro (chiavi maggiori). 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(). Traccia di TreeSearch(26) sull'albero delle chiavi (vedi gli esempi sotto): in si ha , si va a sinistra; in si ha , si va a destra; in la chiave coincide e si restituisce il nodo, dopo 3 chiamate. Per TreeSearch(25) si arriverebbe alla foglia sinistra di (dopo ): è dove andrebbe inserito. 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
Definizione (get, put e remove su un ABR)
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: .
Esempio. In un ABR con inorder , remove(30) con due figli interni sostituisce la entry con il predecessore inorder, cioè .
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 ).
Contare le chiavi con size. Nell'albero del primo esempio i campi size sono: , (24, 11, 13, 26), , , . contaMinoriUguali(root, 26): in , , si va a sinistra; in , , si somma size(sinistro ) più per stesso, e si prosegue a destra; in , , si somma size(sinistro foglia più e si prosegue a destra, su una foglia che dà . Totale : le chiavi .
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
Proprietà (altezza e costo delle operazioni su un ABR). 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) →).
Esempio. Inserendo in ordine crescente l'albero diventa un cammino: e ogni operazione costa . Il grafico confronta l'altezza dell'albero degenere con quella minima possibile (albero completo, , vedi HeapAlbero binario completo e sua altezza floor(log2 n); heap = albero completo con heap-order property; proprietà (radice minima, cammini non decrescenti); rappresentazione su array con level numbering; insert con up-heap bubbling, removeMin con down-heap bubbling, rimozione di una entry qualsiasi; invarianti e costi Theta(log n); esempio svolto.Heap →): già con chiavi sono contro passi di ricerca.
Grafico interattivo: Altezza di un albero binario di ricerca con n chiavi: da floor(log2 n) (albero bilanciato) a n − 1 (chiavi inserite in ordine); per n = 64 sono 6 contro 63
In media, inserendo le chiavi in ordine casuale (cioè con tutte le permutazioni equiprobabili, vedi Calcolo combinatorio per la probabilitàNegli spazi uniformi bisogna contare: principio di moltiplicazione, disposizioni con ripetizione n^k, disposizioni semplici n!/(n−k)!, permutazioni n!, combinazioni (n su k); estrazioni con e senza reinserimento e distribuzione ipergeometrica.Calcolo combinatorio per la probabilità →) l'altezza è : il caso degenere richiede un ordine particolare, ma i dati reali sono spesso già parzialmente ordinati, ed è per questo che servono alberi che si bilanciano da soli.
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.