Implementazione in Java Dizionario con alberi AVL

Transcript

Implementazione in Java Dizionario con alberi AVL
IMPLEMENTAZIONE DI UN ALBERO AVL
Dedichiamoci ora all’implementazione dei dettagli ed all’analisi dell’ADT Dizionario costruito
tramite un albero di ricerca AVL.
Le operazioni di inserimento e rimozione richiedono che noi siamo in grado di effettuare una
ristrutturazione di tipo ‘trinode’ e che conosciamo la differenza di altezza dei due nodi fratelli.
Per quanto riguarda la ristrutturazione, è necessario estendere ai metodi già esistenti del dizionario
di ricerca fatto con l’albero binario di ricerca, aggiungendo però il metodo restructure()
Chiaramente l’operazione di ristrutturazione può essere fatta in un tempo O(1) se l’albero è
implementato come una struttura a riferimenti ricorsivi. E’ per quello che si preferisce utilizzare tale
tipo di implementazione per questi scopi.
Per quanto riguarda l’informazione memorizzata sui singoli nodi, è preferibile aggiungere anche
l’elemento altezza del nodo. Motivo per cui il nodo utilizzato per la costruzione dell’albero
diventerà:
public class AVLItem extends Item
{
int height;
// nuovo elemento aggiunto = altezza del nodo
AVLItem(Object k, Object e, int h) {
super(k, e);
height = h;
}
public int height() { return height; }
public int setHeight(int h) {
int oldHeight = height;
height = h;
return oldHeight;
}
}
Per quanto riguarda l’informazione necessaria per sapere se il sottoalbero che ha come radice un
certo nodo v è bilanciato, essa è ottenibile attraverso il confronto dell’altezza dei sottoalberi destro e
sinistro di tale nodo, come mostrato nel seguente frammento di codice:
private boolean isBalanced(Position p) {
// test se il nodo p ha il fattore di bilanciamento
//compreso fra -1 e +1
int bf = height(T.leftChild(p)) - height(T.rightChild(p));
return ((-1 <= bf) && (bf <= 1));
}
Il calcolo precedente è stato eseguito tramite l’ausilio della variabile locale bf (balance factor) o
fattore di bilanciamento, ottenuta come differenza delle altezze del sottoalbero destro e sinistro del
nodo p.
La classe AVLTree estende la classe BinarySearchTree vista in precedenza.
/** Realization of a dictionary by means of an AVL tree. */
public class AVLTree extends BinarySearchTree implements Dictionary
Il costruttore di tale classe non fa altro che istanziare un oggetto T dalla classe BinaryTree. Anzi,
dalla classe RestructurableNodeBinaryTree che è una classe che implementa la classe BinaryTree
ed in più supporta il metodo restructure().La figura illustra meglio il concetto:
Il metodo di ristrutturazione a partire dal nodo v funziona in questo modo:
Gli altri metodi della classe AVLTree riguardano la implementazione del metodo costruttore e altri
due metodi aggiuntivi.
Per quanto riguarda il costruttore esso non fa altro che istanziare un oggetto della classe
RestructurableNodeBinaryTree ed eseguire il metodo della classe superiore, naturalmente in ordine
inverso:
public AVLTree(Comparator c) {
super(c);
T = new RestructurableNodeBinaryTree();
}
Gli altri metodi della classe hanno a che fare con la gestione dell’informazione sull’altezza del nodo
così come è stata creata nella classe AVLItem.
Per esempio l’altezza di un nodo si calcola in questo modo:
private int height(Position p) {
if(T.isExternal(p))
return 0;
else
return ((AVLItem) p.element()).height();
}
Il metodo setHeight non fa altro che ricalcolare l’altezza del nodo p fornito come parametro:
private void setHeight(Position p) { // called only if p is internal
((AVLItem) p.element()).setHeight(1+Math.max(height(T.leftChild(p)),
height(T.rightChild(p))));
}
La figura sottostante chiarisce meglio cosa fa il metodo:
Ora il metodo successivo diventa di facile comprensione. Infatti esso non fa altro che ritornare la
posizione del figlio del nodo p, fornito come parametro, che ha altezza maggiore:
private Position tallerChild(Position p) {
// return a child of p with height no smaller than that of the other
child
if(height(T.leftChild(p)) >= height(T.rightChild(p)))
return T.leftChild(p);
else
return T.rightChild(p);
}
Mancano solamente da descrivere gli ultimi tre metodi.
Il metodo insertItem non fa altro che inserire nell’albero l’elemento, come già visto per precedenti
implementazioni. Successivamente viene richiamato il metodo rebalance() per rimettere a posto
l’albero, dato che il metodo di inserimento di un elemento è quello ereditato da un albero binario
non bilanciato:
// methods of the dictionary ADT
/** Overrides the corresponding method of the parent class. */
public void insertItem(Object key, Object element)
throws InvalidKeyException {
super.insertItem(key, element); // may throw an InvalidKeyException
Position zPos = actionPos; // start at the insertion position
T.replaceElement(zPos, new AVLItem(key, element, 1));
rebalance(zPos);
}
Il metodo rebalance() funziona così: viene atraversato l’albero dal punto dell’inserimento
actionPos fino alla radice dell’albero. Se il nodo visitato non è bilanciato, allora viene chiamata
una procedura di ristrutturazione seguita dal ricalcalo delle altezze di tutti i nodi coinvolti.
/**
* Auxiliary method called by insertItem and removeElement.
* Traverses the path of T from the given node to the root. For
* each node zPos encountered, recomputes the height of zPos and
* performs a trinode restructuring if zPos is unbalanced.
*/
private void rebalance(Position zPos) {
while (!T.isRoot(zPos)) {
zPos = T.parent(zPos);
setHeight(zPos);
if (!isBalanced(zPos)) {
// perform a trinode restructuring
Position xPos = tallerChild(tallerChild(zPos));
zPos = ((RestructurableNodeBinaryTree) T).restructure(xPos);
setHeight(T.leftChild(zPos));
setHeight(T.rightChild(zPos));
setHeight(zPos);
}
}
}
Il metodo di rimozione è analogo:
/** Overrides the corresponding method of the parent class. */
public Object removeElement(Object key)
throws InvalidKeyException {
Object toReturn = super.removeElement(key);
// may throw an InvalidKeyException
if(toReturn != NO_SUCH_KEY) {
Position zPos = actionPos; // start at the removal position
rebalance(zPos);
}
return toReturn;
}