Alberi binari di ricerca
Gli alberi binari di ricerca sono ottime strutture dati per memorizzare coppie di elementi (k, e)
chiave – elemento di un dizionario.
Un albero binario di ricerca T è un albero binario in cui in ogni nodo, ad esclusione di quelli esterni,
sono memorizzate delle coppie (k, e) chiave – elemento. Sia v uno di questi nodi che memorizza la
chiave k. Allora deve essere che:
1) le chiavi memorizzate nei nodi del sottoalbero sinistro del nodo v sono minori o uguali della
chiave k contenuta nel nodo stesso
2) le chiavi memorizzate nei nodi del sottoalbero destro del nodo v sono maggiori o uguali
della chiave k contenuta nel nodo stesso
Si stabilisce d’ora in avanti che i nodi esterni di un albero, cioè le sue foglie siano rappresentati da
nodi nulli che non contengono niente.
Algoritmo di ricerca in un albero binario
Per effettuare la ricerca in un dizionario D rappresentato attraverso l’utilizzo di un albero binario,
dobbiamo immaginare l’albero come un albero di decisione come quello riportato in figura:
In questo caso la questione è: su ogni nodo interno visitato la chiave contenuta nell’elemento (k,e)
del nodo è minore o uguale o maggiore di quella cercata?
Se la risposta è la prima (minore) allora la ricerca continua nel sottoalbero di sinistra.
Se la risposta è la seconda (uguale) allora la ricerca ha avuto esito positivo e si ferma.
Se la risposta è la terza (maggiore) allora la ricerca continua nel sottoalbero di destra.
Il frammento di codice sotto riportato segue questo procedimento. La ricerca è fatta su un albero
binario T a partire da un nodo v dell’albero. La ricerca avviene in base ad una chiave k da
confrontare. Nel caso la procedura arrivi a raggiungere una foglia esterna (null) significa che la
ricerca ha avuto esito negativo. In ogni caso l’algoritmo ritorna il riferimento o posizione
dell’ultimo nodo raggiunto:
Algorithm TreeSerach(k, v)
Se v è un nodo esterno allora
ritorna v
//v è un nodo foglia cioè nullo
Se k = k(v) allora
ritorna v
//ricerca terminata con successo
altrimenti
Se k < k(v) allora
ritorna TreeSerach(k, T.leftChild(v)); //chiamata ricorsiva
altrimenti // k > k(v)
ritorna TreeSerach(k, T.rightChild(v)); //chiamata ricorsiva
La complessità di questo algoritmo è O(h) con h altezza dell’albero.
Operazioni di modifica dell’albero binario di ricerca
Inserimento di un nuovo elemento
Per eseguire l’inserimento di un nuovo elemento in un albero binario di ricerca bisogna sempre
partire facendo eseguire l’algoritmo di ricerca sull’albero in questione.
Si possono presentare due casi:
1) l’algoritmo di ricerca non ha trovato l’elemento nell’albero e quindi ha restituito un
riferimento ad un nodo nullo.
A questo punto non bisogna fare altro che sostituire l’elemento nullo con il nuovo nodo da
inserire, aggiungendo anche i due figli nulli del nodo stesso (expandExternal)
2) L’algoritmo di ricerca ha trovato il nodo cercato v. Bisogna ripetere l’algoritmo di ricerca
sul sottoalbero che ha come radice uno dei due figli del nodo trovato fino a trovare un nodo
esterno. A questo punto è sufficiente inserire il nuovo nodo esterno con i suoi due figli nulli
(expandExternal)
La figura seguente mostra l’effetto dell’inserimento di un nuovo elemento su di un albero di
ricerca:
Rimozione di un elemento esistente
La procedura di rimozione da un albero binario di ricerca è un po’ più complessa di quella di
inserimento.
Come al solito si inizia sempre con l’applicare l’algoritmo di ricerca per recuperare il riferimento al
nodo che conserva la chiave il cui elemento vogliamo eliminare.
Si presentano i seguenti casi:
1) l’algoritmo di ricerca non ha trovato l’elemento e quindi ha ritornato un riferimento ad una
foglia nulla. In questo caso non c’è da fare nulla;
2) l’algoritmo ha ritrovato il nodo w e ritorna un riferimento a tale nodo che è per forza un
nodo interno. Allora bisogna considerare i seguenti casi:
a) se uno dei due figli z del nodo w è esterno allora è sufficiente eliminare tale nodo
esterno z e il nodo w, sostituendo w con il fratello (sibling) di z.
(Questa procedura prende il nome di removeAboutExternal).
b) se entrambe i figli di w sono interni allora bisogna seguire questo procedimento:
•
recuperare il primo nodo interno y che segue w applicando un algoritmo di
attraversamento Inordine. Cioè partendo dal nodo w bisogna andare prima
a destra e poi sempre a sinistra fino ad incontrare un nodo foglia x nullo. Il
genitore di x sarà il nodo y cercato.
•
Sostituire l’elemento (k,e) del nodo da eliminare w con quello di y appena
trovato. Il valore del nodo w da eliminare va temporaneamente custodito
in una variabile (solo l’elemento e non la chiave)
•
Eliminare il nodo y
(removeAboutExternal)
•
Restituire il valore della variabile che conserva l’elemento di w
eliminato.
e
suo
figlio
x
come
al
punto
a)