ALBERI E CLASSI GENERICHE
Alberi e classi generiche
Appunti di informatica per la classe 4° - articolazione informatica
Prof. G. Malafronte
Premessa ........................................................................................................................................................... 3
Alberi ................................................................................................................................................................. 3
Perché si utilizzano gli alberi?........................................................................................................................ 3
Alberi binari ................................................................................................................................................... 5
Alberi binari di ricerca - BST .......................................................................................................................... 6
Prestazioni di un albero binario di ricerca - BST ............................................................................................ 7
BST vs Array vs LinkedList .......................................................................................................................... 7
BST vs hashtable (hashmap) ...................................................................................................................... 8
Realizzazione di alberi binari di ricerca BST....................................................................................................... 9
Criteri di visita di un albero BST....................................................................................................................... 16
Visita di alberi binari (BST)........................................................................................................................... 17
Visita in pre-ordine e in post-ordine ....................................................................................................... 18
Visita in ampiezza .................................................................................................................................... 18
Algoritmi di visita ..................................................................................................................................... 20
Realizzazione di alberi binari di ricerca BST – criteri di visita. ..................................................................... 20
progetto Test1AlberoBST ........................................................................................................................ 20
Esercizi ................................................................................................................................................. 21
Diagramma UML delle classi del progetto Test1AlberoBST ................................................................ 26
Classi Generiche .............................................................................................................................................. 27
Perché generiche? ....................................................................................................................................... 28
Definizione di classi generiche .................................................................................................................... 28
Type Parameter Naming Conventions......................................................................................................... 30
Invoking and Instantiating a Generic Type .................................................................................................. 30
The Diamond ........................................................................................................................................... 30
Multiple Type Parameters ........................................................................................................................... 30
Parameterized Types ................................................................................................................................... 31
Generic Methods ......................................................................................................................................... 31
Bounded Type Parameters .......................................................................................................................... 32
Multiple Bounds ...................................................................................................................................... 33
Generic Methods and Bounded Type Parameters ...................................................................................... 33
Generics, Inheritance, and Subtypes ........................................................................................................... 34
Generic Classes and Subtyping .................................................................................................................... 35
Wildcards ..................................................................................................................................................... 35
Upper Bounded Wildcards ...................................................................................................................... 35
Unbounded Wildcards ............................................................................................................................. 36
Lower Bounded Wildcards....................................................................................................................... 37
Wildcards and Subtyping ......................................................................................................................... 37
Alberi Binari Generici ....................................................................................................................................... 38
Classe AlberoBST ver.2: Progetto Test2AlberoBST .................................................................................. 38
//Classe MioOggetto ........................................................................................................................... 38
//classe Nodo....................................................................................................................................... 39
//Classe AlberoBST .............................................................................................................................. 39
Verso un progetto migliore della classe AlberoBST .................................................................................... 42
Classe AlberoBST ver.3: progetto Test3AlberoBST.................................................................................. 42
//Classe Nodo ...................................................................................................................................... 42
//classe AlberoBST ............................................................................................................................... 43
//classe VoceDizionario ....................................................................................................................... 46
Diagramma UML del progetto Test3AlberoBST .................................................................................. 49
Classe AlberoBST ver.4: progetto Test4AlberoBST.................................................................................. 49
Progetto Dizionario: il benchmark definitivo .................................................................................................. 51
Package Dizionario ...................................................................................................................................... 52
Package tree ................................................................................................................................................ 52
Classe Dizionario .......................................................................................................................................... 53
ArrayList vs. AlberoBST vs. HashMap .......................................................................................................... 55
Tipologie di Alberi binari di ricerca .................................................................................................................. 60
Alberi nelle API di Java..................................................................................................................................... 60
TreeMap<K,V> ............................................................................................................................................. 60
TreeSet<E>................................................................................................................................................... 61
Premessa
In queste note si introdurranno gli alberi (ADT tree) in generale e gli alberi binari di ricerca (BST) in dettaglio,
sviluppando anche classi generiche.
L’obiettivo di questa unità didattica è duplice: comprendere le caratteristiche e gli usi degli alberi come ADT
e imparare a scrivere classi generiche.
Alberi
Un albero è una struttura dati (ADT) che può essere vista come un particolare grafo (aciclico), con le
seguenti condizioni aggiuntive:
https://en.wikipedia.org/wiki/Tree_(data_structure)
https://it.wikipedia.org/wiki/Albero_(informatica)
Ogni nodo deve avere al massimo un unico arco entrante, mentre dai diversi nodi possono uscire diversi
numeri di archi uscenti. Si chiede infine che l'albero possegga un
unico nodo privo di arco entrante: questo nodo viene
detto radice (root) dell'albero.
Un albero si compone di due tipi di sottostrutture fondamentali:
il nodo, che in genere contiene informazioni, e l'arco che stabilisce
un collegamento gerarchico fra due nodi: si parla allora di un nodo
padre dal quale esce un arco orientato che lo collega ad un nodo
figlio
Solitamente ogni nodo porta con sé delle informazioni e molto
spesso anche una chiave con cui è possibile identificarlo
univocamente all'interno dell'albero. L'altezza o profondità dell'albero è il massimo delle lunghezze dei
suoi cammini massimali, cammini che vanno dalla radice alle sue foglie.
Perché si utilizzano gli alberi?
Perché gli alberi sono utilizzati in alcune applicazioni informatiche? Per quale motivo non bastano le liste?
Alberi binari
Alberi binari di ricerca - BST
L’albero binario è di ricerca se esiste una relazione di ordinamento tra i valori dei
nodi (valori comparabili). In particolare, dato un nodo, il sottoalbero sinistro ha
nodi i cui valori sono più piccoli di quello del nodo d’origine, mentre il sottoalbero
destro ha nodi con valori più grandi.
Un BST soddisfa le seguenti proprietà:
- ogni nodo v contiene un elemento cui è associato una chiave presa da un
dominio totalmente ordinato.
- le chiavi nel sottoalbero sinistro di v sono minori della chiave di v
- le chiavi nel sottoalbero destro di v sono maggiori della chiave di v
Le operazioni tipiche che possono essere svolte sulle collezioni di dati sono
search, insert e delete.
Prestazioni di un albero binario di ricerca - BST
BST vs Array vs LinkedList
Su un array di oggetti si può effettuare un algoritmo di ricerca con complessità
O(n). Infatti se l’array non è ordinato è possibile, nel caso peggiore, dover
effettuare tutti i confronti sulle chiavi di ricerca di tutti gli oggetti.
Su un array di oggetti ordinato è possibile effettuare un algoritmo di ricerca, detta
ricerca dicotomica, con complessità O(log n).
https://it.wikipedia.org/wiki/Ricerca_dicotomica
public class BinarySearch {
public int binarySearch (int[] a, int p, int r, int k) {
int q, s = -1;
if (p <= r) {
q = (p+r)/2;
if (k < a[q])
s = binarySearch(a, p, q-1, k);
else if (k > a[q])
s = binarySearch(a, q+1, r, k);
else if (k == a[q])
return q;
}
return s;
}
}
Tuttavia gli algoritmi per gli inserimenti e le cancellazioni su un array ordinato
hanno complessità O(n). Ad esempio, per inserire un elemento in un array
ordinato, bisogna prima trovare la posizione in cui inserire l’elemento e poi
eventualmente spostare gli elementi maggiori (minori) dell’elemento inserito.
Su una lista concatenata (ADT LinkedList) è possibile effettuare un algoritmo di
ricerca con complessità O(n), come nel caso dell’array non ordinato, tuttavia su
una lista concatenata non ha senso effettuare la ricerca dicotomica dal momento
che l’accesso a un elemento generico di una lista concatenata si ottiene
attraversando la catena di puntatori e non in maniera diretta come in un array o
in un ArrayList (accesso casuale).
Gli alberi binari di ricerca (ADT BST) permettono di ottenere accessi, inserimenti
e cancellazioni in tempi O(log n). Inoltre gli alberi binari BST sono già ordinati e
permettono di effettuare una scansione ordinata dell’insieme di valori in O(n) (che
è il risultato migliore in assoluto).
BST vs hashtable (hashmap)
http://www.geeksforgeeks.org/advantages-of-bst-over-hash-table/
http://cs.stackexchange.com/questions/270/hash-tables-versus-binary-trees
Hash Table supports following operations in O(1) time (se non ci sono collisioni, altrimenti
O(n)) .
1) Search
2) Insert
3) Delete
The time complexity of above operations in a self-balancing Binary Search Tree
(BST) (like Red-Black Tree, AVL Tree, Splay Tree, etc) is O(Logn).
So Hash Table seems to beating BST in all common operations. When should we prefer BST
over Hash Tables, what are advantages. Following are some important points in favor of
BSTs.
1. We can get all keys in sorted order by just doing “Inorder Traversal” of BST. This is not
a natural operation in Hash Tables and requires extra efforts.
2. Doing order statistics, finding closest lower and greater elements, doing range
queries are easy to do with BSTs. Like sorting, these operations are not a natural
operation with Hash Tables.
3. BSTs are easy to implement compared to hashing, we can easily implement our own
customized BST. To implement Hashing, we generally rely on libraries provided by
programming languages.
4. With BSTs, all operations are guaranteed to work in O(Logn) time. But with Hashing,
O(1) is average time and some particular operations may be costly, especially when
table resizing happens.
Realizzazione di alberi binari di ricerca BST
Un albero BST può essere realizzato mediante la seguente architettura di classi
– progetto Test1AlberoBST:
Dove:
//classe MioOggetto
public class MioOggetto implements Comparable<MioOggetto> {
String value;
public MioOggetto() {
value = "";
}
public MioOggetto(String value) {
this.value = value;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
@Override
public int compareTo(MioOggetto otherObject) {
return this.value.compareTo(otherObject.getValue());
}
@Override
public String toString(){
return value.toString();
}
}
//classe Nodo
public class Nodo {
private MioOggetto value;
private Nodo left; // figlio sinistro
private Nodo right;// figlio destro
public Nodo(MioOggetto valore) {
value = valore;
left = null;
right = null;
}
public MioOggetto getValue() {
return value;
}
public void setLeftChild(Nodo child) {
left = child;
}
public void setRightChild(Nodo child) {
right = child;
}
public Nodo getLeftChild() {
return left;
}
public Nodo getRightChild() {
return right;
}
}
//Classe TreeException
public class TreeException extends Exception {
TreeException() {
}
TreeException(String message) {
super(message);
}
}
//Classe AlberoBST
import java.util.ArrayList;
import java.util.List;
enum VisitCriteria {
PREORDER, POSTORDER, INORDER
};
public class AlberoBST {
private Nodo root;
public AlberoBST() {
root = null;
}
//Classe MainTest
import java.util.List;
/**
* Programma che testa la classe AlberoBST
* @author Gennaro
*/
public class MainTest {
public static void main(String[] args) {
AlberoBST albero2 = new AlberoBST();
/*
MioOggetto
MioOggetto
MioOggetto
MioOggetto
MioOggetto
oo1
oo2
oo3
oo4
oo5
=
=
=
=
=
new
new
new
new
new
MioOggetto("ciao");
MioOggetto("alto");
MioOggetto("dato");
MioOggetto("basso");
MioOggetto("brutto");
/*
albero2.inserisciValore(oo1);
albero2.inserisciValore(oo2);
albero2.inserisciValore(oo3);
albero2.inserisciValore(oo4);
albero2.inserisciValore(oo5);
*/
try {
//fai qualcosa con l’albero…
} catch (TreeException e) {
e.printStackTrace();
}
}
}
/**
*
* @param valore valore da inserire
* @return true se il valore è inserito, false altrimenti
*/
public boolean inserisciValore(MioOggetto valore) {
if (root != null)
return insert(valore, root);
else {
root = new Nodo(valore);
return true;
}
}
/**
* Metodo ricorsivo per l'inserimento di un valore in un nodo
* @param valore valore da inserire
* @param nodoCorrente nodo corrente
* @return true, se il valore da inserire è inserito correttamente, false
altrimenti.
*/
protected boolean insert(MioOggetto valore, Nodo nodoCorrente) {
if (valore.compareTo(nodoCorrente.getValue()) == 0) // già presente
return false;
if (valore.compareTo(nodoCorrente.getValue()) < 0) {
if (nodoCorrente.getLeftChild() == null) {
nodoCorrente.setLeftChild(new Nodo(valore));
return true;
} else
return insert(valore, nodoCorrente.getLeftChild());
}
else {// caso valore.compareTo(nodoCorrente.getValue())>0
if (nodoCorrente.getRightChild() == null) {
nodoCorrente.setRightChild(new Nodo(valore));
return true;
} else
return insert(valore, nodoCorrente.getRightChild());
}
}
/**
* Metodo per la ricerca di un valore nell'albero BST
* @param valore valore da ricercare
* @return true, se il valore da ricercare è presente
*/
public boolean ricercaValore(MioOggetto valore) {
return search(valore, root);
}
/**
* Metodo ricorsivo per la ricerca di un valore in un nodo
* @param valore valore da ricercare
* @param nodoCorrente nodo corrente
* @return true, se il valore è stato trovato, false altrimenti
*/
protected boolean search(MioOggetto valore, Nodo nodoCorrente) {
if (nodoCorrente == null) {
return false;
}
if (valore.compareTo(nodoCorrente.getValue()) < 0)
return search(valore, nodoCorrente.getLeftChild());
else {
if (valore.compareTo(nodoCorrente.getValue()) > 0)
return search(valore, nodoCorrente.getRightChild());
else
return true;
}
}
//La cancellazione di un elemento da un albero BST è l’operazione più complessa!
/**
* Metodo per cancellare un valore dall'albero
* @param valore valore da cancellare
* @throws TreeException se l'elemento non è presente
*/
public void cancellaValore(MioOggetto valore) throws TreeException {
if (valore.compareTo(root.getValue()) == 0) { //devo cancellare il nodo
radice
//appendo il sottoalbero sinistro come elemento più piccolo del
sottoalbero destro tramite il metodo insertTree(treeRoot, addedTreeRoot)
insertTree(root.getRightChild(), root.getLeftChild());
root = root.getRightChild();// elimina il padre
} else {
if (valore.compareTo(root.getValue()) < 0)
//elimino nel sottoalbero sinistro
delete(valore, root, root.getLeftChild());
else
//elimino nel sottoalbero destro
delete(valore, root, root.getRightChild());
}
}
/**
* Metodo ricorsivo per la cancellazione di un elemento dall'albero
* @param valore valore da rimuovere
* @param parent nodo padre
* @param nodoCorrente nodo corrente
* @throws TreeException se il valore non è presente
*/
protected void delete(MioOggetto valore, Nodo parent, Nodo nodoCorrente)
throws TreeException {
if (nodoCorrente == null)
throw new TreeException("Valore inesistente");
if (valore.compareTo(nodoCorrente.getValue()) < 0)
delete(valore, nodoCorrente, nodoCorrente.getLeftChild());
else {
if (valore.compareTo(nodoCorrente.getValue()) > 0)
delete(valore, nodoCorrente, nodoCorrente.getRightChild());
else { // nodoCorrente è il nodo che voglio cancellare
Nodo temp;
if (nodoCorrente.getRightChild() == null)
temp = nodoCorrente.getLeftChild();
// caso con nodoCorrente senza figlio destro
else {
// caso con nodocorrente che ha il figlio destro
parent
nodocorrente
3
1
6
8
8
10
10
6
14
2
13
4
14
4
1
7
13
7
2
insertTree(nodoCorrente.getRightChild(),
nodoCorrente.getLeftChild());
temp = nodoCorrente.getRightChild();
insertTree(nodoCorrente.getRightChild(),
nodoCorrente.getLeftChild());
}
if (parent.getLeftChild() == nodoCorrente)
parent.setLeftChild(temp);
else
parent.setRightChild(temp);
}
}
}
/**
* inserisce il sottoalbero addedTreeRoot come elemento più piccolo del
* sottoalbero che ha radice in treeRoot se treeRoot è non nullo
*
* @param treeRoot
* @param addedTreeRoot
*/
private void insertTree(Nodo treeRoot, Nodo addedTreeRoot) {
Nodo parent = null;
while (treeRoot != null) {
parent = treeRoot;
treeRoot = treeRoot.getLeftChild();
}
if (parent != null)
parent.setLeftChild(addedTreeRoot);
}
//altri metodi da implementare…
}//fine classe
Criteri di visita di un albero BST
Visita di alberi binari (BST)
Visita in ordine
Visita in pre-ordine e in post-ordine
Visita in ampiezza
Algoritmi di visita
Realizzazione di alberi binari di ricerca BST – criteri di visita.
progetto Test1AlberoBST
Partendo dal seguente codice di esempio:
/**
*
* @throws TreeException
*/
public void stampaInOrdine() throws TreeException {
if (root == null)
throw new TreeException("Albero vuoto");
else
inOrderPrint(root);
System.out.println();
}
protected void inOrderPrint(Nodo nodo) {
if (nodo != null) {
inOrderPrint(nodo.getLeftChild());
System.out.print(nodo.getValue() + " ");
inOrderPrint(nodo.getRightChild());
}
}
@Override
public String toString() {
return inOrderToString(root);
}
protected String inOrderToString(Nodo n) {
String left, center, right;
if (n != null) {
left = inOrderToString(n.getLeftChild());
center = n.getValue() + " ";
right = inOrderToString(n.getRightChild());
return left + center + right;
}
// else non serve
return "";
}
public void stampa() throws TreeException {
if (root == null)
throw new TreeException("Albero vuoto");
else
System.out.println(inOrderToString(root));
}
Esercizi
Esercizio 1: Scrivere i seguenti metodi per la classe AlberoBST:
public void stampa(VisitCriteria criteria) throws TreeException;
protected String postOrderToString(Nodo n);
protected String preOrderToString(Nodo n)
DEF: Livello di un albero binario. Si definisce livello di un nodo in un albero binario il numero di rami da
attraversare dalla radice fino al nodo. Ad esempio la radice ha livello 0, i due sottoalberi della radice hanno
livello 1 e così via.
DEF: Livello massimo di un albero binario. Si definisce livello massimo di un albero binario il massimo numero
di rami da attraversare, partendo dalla radice per raggiungere un nodo. Il livello massimo di un albero binario
identifica il nodo (foglia) più distante dalla radice.
DEF: Livello minimo di un albero binario. Si definisce livello minimo di un albero binario il minimo numero
di rami da attraversare partendo dalla radice per raggiungere una foglia dell’albero. Il livello minimo di un
albero identifica la foglia più vicina alla radice (e distinta da essa).
DEF: albero binario bilanciato. Un albero è bilanciato quando la differenza tra il ramo più corto e quello più
lungo è al più uno.
Esercizio 2: prendendo spunto dal seguente metodo
public int getLivelloMassimo() throws TreeException {
if (root == null)
throw new TreeException("Albero vuoto");
else
return postOrderLevelCountMax(root);
}
/**
*
* @param n
*
*/
private int postOrderLevelCountMax(Nodo n) {
int level1 =0;
int level2 =0;
if (n != null) {
if (n.getLeftChild() != null)
level1 = postOrderLevelCountMax(n.getLeftChild())+1;
if (n.getRightChild() != null)
level2 = postOrderLevelCountMax(n.getRightChild())+1;
return level1 > level2 ? level1 : level2;// prendo il massimo
} else
return 0;
}
Esercizio 3: Scrivere i seguenti metodi:
public int getLivelloMinimo() throws TreeException;
public boolean isBalanced() throws TreeException;
Esercizio 4: Scrivere il metodo che conta il numero di nodi di un albero:
public int getNumeroNodi() {
return getPostOrderNodeCount(root);
}
//come si scrive il metodo getPostOrderNodeCount ?
Esercizio 5: prendendo spunto dal seguente esempio
/**
*
* @return una Vista di tipo ArrayList degli oggetti dell'albero visitati in
ordine
* @throws TreeException
*/
public ArrayList<MioOggetto> asInOrderArrayListView() throws TreeException {
if (root == null)
throw new TreeException("Albero vuoto");
else {
ArrayList<MioOggetto> list = new ArrayList<MioOggetto>(
this.getNumeroNodi());
inOrderListFill(list, root);
return list;
}
}
/**
*
* @param l
*
lista da riempire con i dati (MioOggetto) dei nodi - l deve
*
essere non null
* @param n
*
nodo radice dell'albero. Gli oggetti inseriti nella lista sono
*
gli stessi inseriti nell'albero, passiamo i riferimenti
*/
protected void inOrderListFill(List<MioOggetto> l, Nodo n) {
if (n != null) {
inOrderListFill(l, n.getLeftChild());
l.add(n.getValue());
inOrderListFill(l, n.getRightChild());
}
}
Scrivere il metodo:
/**
*
* @param criteria
* @return una Vista di tipo ArrayList degli oggetti dell'albero visitati
secondo il criterio di visita specificato
* @throws TreeException
*/
public ArrayList<MioOggetto> asArrayListView(VisitCriteria criteria)
throws TreeException;
Esercizio 6: completare il metodo che bilancia un albero binario
/**
* Bilancia l'albero.
* @throws TreeException
*/
public void balance() throws TreeException {
if (root == null)
throw new TreeException("Albero vuoto");
else {
//creo la lista con i gli oggetti (MioOggetto) in ordine
ArrayList<MioOggetto> list = asArrayListView(VisitCriteria.INORDER);
//creo un nuovo albero bilanciato a partire dalla lista ordinata
//dell'albero restituito prendo il nodo radice e lo passo a root
root = buildBalancedTreeFromOrderedList(list).root;
}
}
/**
* Metodo che restituisce un nuovo albero binario di ricerca costruito con gli
oggetti presenti in una lista ordinata
*
* @param orderedList - deve essere non nullo
* @return
*/
protected AlberoBST buildBalancedTreeFromOrderedList(List<MioOggetto>
orderedList){
AlberoBST nuovoAlbero = new AlberoBST();
//creo un nuovo albero bilanciato a partire da list
buildBalancedTreeFromOrderedList(orderedList,nuovoAlbero,
0,orderedList.size()-1);
return nuovoAlbero;
}
Scrivere il metodo:
/**
* Metodo ricorsivo che crea un albero binario di ricerca a partire da una lista
ordinata di oggetti (MioOggetto)
*
* @param orderedList - deve essere non null
* @param albero - deve essere non null
* @param left - indice minimo della lista
* @param right - indice massimo della lista
*/
private void buildBalancedTreeFromOrderedList(List<MioOggetto> orderedList,
AlberoBST albero, int left, int right);
//suggerimento: sfruttare il fato che la lista è ordinata, selezionando di volta in volta l’elemento mediano
della lista e inserirlo nell’albero con il metodo “inserisciValore”.
Esercizio 7: Scrivere una versione efficiente del metodo che crea un albero binario di ricerca bilanciato.
/**
* Bilancia l'albero, usando un algoritmo efficiente di costruzione
* dell'albero bilanciato. A partire dall'albero di input si crea una lista
* ordinata facendo una visita in ordine dell'albero binario di ricerca. Con
* la lista ordinata così ottenuta si crea un nuovo albero binario di
* ricerca con un procedimento ricorsivo che prende l'elemento mediano della
* lista ordinata e lo inserisce nel nodo radice; gli elementi più piccoli
* dell'elemento mediano sono messi nel sottoalbero sinistro; gli elementi
* più grandi dell'elemento mediano sono messi nel sottoalbero destro, e
* così via fino a quando la sotto-lista non è vuota. In questo algoritmo non
* è utilizzato il metodo inserisciValore che prevede di effettuare dei
* confronti sugli elementi dell'albero per inserire un nuovo elemento, ma
* la costruzione dell'albero è ottenuta direttamente a mano a mano che si
* scorre ricorsivamente la lista ordinata.
*
* @throws TreeException
*/
public void efficientBalance() throws TreeException {
if (root == null) {
throw new TreeException("Albero vuoto");
} else {
// creo la lista con i gli oggetti in ordine
ArrayList<MioOggetto> list = asArrayListView(VisitCriteria.INORDER);
// dell'albero restituito prendo il nodo radice e lo passo a root
root = efficientBuildBalancedTreeFromSortedList(list).root;
}
}
/**
* Metodo che restituisce un nuovo albero binario di ricerca costruito con
* gli oggetti presenti in una lista ordinata
*
* @param sortedList - deve essere non nullo. La lista passata come argomento
può essere modificata: se nella lista ci sono duplicati, questi sono eliminati.
* @return un albero binario di ricerca bilanciato con gli elementi della lista
*/
protected AlberoBST efficientBuildBalancedTreeFromSortedList(List<MioOggetto>
sortedList) {
AlberoBST nuovoAlbero = new AlberoBST();
// elimino elementi duplicati nella lista
deleteDuplicateFromSortedList(sortedList);
// creo un nuovo albero bilanciato a partire da list
nuovoAlbero.root = efficientBuildBalancedTreeFromSortedList(sortedList, 0,
sortedList.size() - 1);
return nuovoAlbero;
}
Scrivere i metodi:
/**
* elimina elementi duplicati dalla lista ordinata; la lista in input deve
* essere ordinata
*
* @param theSortedList
*/
private void deleteDuplicateFromSortedList(List<MioOggetto> theSortedList);
e
/*
* Metodo ricorsivo che crea un albero binario di ricerca a partire da una
* lista ordinata di oggetti
*
* @param sortedList - deve essere non null e ordinata. Inoltre questa
* versione richiede che nella lista ordinata non ci siano duplicati
*
* @param left - indice minimo della lista
*
* @param right - indice massimo della lista
*/
private Nodo efficientBuildBalancedTreeFromSortedList(List<MioOggetto>
sortedList, int left, int right);
Esercizio 8: [Risolto] scrivere altri due costruttori per la classe AlberoBST. Il primo riceve in input una lista di
MioOggetto, non necessariamente ordinata, e a partire da questo crea un albero binario di ricerca usando il
metodo inserisciValore. Il secondo riceve in input due parametri: il primo è una lista di MioOggetto, non
necessariamente ordinata, e il secondo parametro è una variabile booleana che se true forza il costruttore a
creare un albero bilanciato.
public AlberoBST(List<MioOggetto> list) {
root = null;
// mischio gli elementi della lista per migliorare il bilanciamento
// dell'albero
Collections.shuffle(list);
for (MioOggetto elem : list) {
inserisciValore(elem);
}
}
public AlberoBST(List<MioOggetto> list, boolean forceBalance) {
if (forceBalance) {
// ordino la lista
Collections.sort(list);
// creo un albero bilanciato
root =efficientBuildBalancedTreeFromSortedList(list).root;
} else // creo un albero, ma non necessariamente bilanciato
{
root = new AlberoBST(list).root;
}
}
Si noti che nel costruttore che riceve in input una lista di valori si effettua una randomizzazione dell’input
prima di procedere alla costruzione dell’albero, mediante il metodo Collections.shuffle(list), per
evitare sbilanciamenti troppo elevati nel caso di liste parzialmente o totalmente ordinate.
Esercizio 9 - Scrivere i metodi:
/**
* Inserisce (aggiunge) una lista di oggetti all'albero. L'albero non è
necessariamente bilanciato
* @param listaValori la lista di oggetti da aggiungere all'albero
*/
public void inserisciListaValori(List<MioOggetto> listaValori);
/**
* Inserisce (aggiunge) una lista di oggetti all'albero.
* @param listaValori lista di oggetti da aggiungere all'albero
* @param forceBalance se true l'albero risultante è bilanciato, anche se
l'albero di partenza non lo era
*/
public void inserisciListaValori(List<MioOggetto> listaValori, boolean
forceBalance);
Esercizio 10 – Scrivere il diagramma delle classi del progetto Test1AlberoBST (usando IntelliJ IDEA) e
controllare che sia come il seguente.
Diagramma UML delle classi del progetto Test1AlberoBST
Classi Generiche
La stragrande maggioranza delle cose riportate in questi appunti relativamente alle classi generiche
sono prese dal tutorial:
https://docs.oracle.com/javase/tutorial/java/generics/index.html
Perché generiche?
Una classe “generica” è uno strumento che permette la definizione di un tipo parametrizzato, che
viene esplicitato in fase di compilazione secondo le necessità.
Vantaggi:
 Evita il casting da Object. I.e., invece di
String title = ((String) words.get(i)).toUppercase();
utilizzeremo String title = words.get(i).toUppercase();


Fornisce una migliore gestione del type checking durante la compilazione
Permette di creare algoritmi generici
https://docs.oracle.com/javase/tutorial/java/generics/why.html
Code that uses generics has many benefits over non-generic code:

Stronger type checks at compile time.
A Java compiler applies strong type checking to generic code and issues errors if the code violates type
safety. Fixing compile-time errors is easier than fixing runtime errors, which can be difficult to find.

Elimination of casts.
The following code snippet without generics requires casting:
 List list = new ArrayList();
 list.add("hello");
 String s = (String) list.get(0);
When re-written to use generics, the code does not require casting:
List<String> list = new ArrayList<String>();
list.add("hello");
String s = list.get(0);
// no cast

Enabling programmers to implement generic algorithms.
By using generics, programmers can implement generic algorithms that work on collections of different
types, which can be customized, and are type safe and easier to read.
Definizione di classi generiche
A generic type is a generic class or interface that is parameterized over types. The following Box class will be
modified to demonstrate the concept.
Begin by examining a non-generic Box class that operates on objects of any type. It needs only to provide two
methods: set, which adds an object to the box, and get, which retrieves it:
public class Box {
private Object object;
public void set(Object object) { this.object = object; }
public Object get() { return object; }
}
Since its methods accept or return an Object, you are free to pass in whatever you want, provided that it is not
one of the primitive types. There is no way to verify, at compile time, how the class is used. One part of the code
may place an Integer in the box and expect to get Integers out of it, while another part of the code may
mistakenly pass in a String, resulting in a runtime error.
A generic class is defined with the following format:
class name<T1, T2, ..., Tn> { /* ... */ }
The type parameter section, delimited by angle brackets (<>), follows the class name. It specifies the type
parameters (also called type variables)T1, T2, ..., and Tn.
To update the Box class to use generics, you create a generic type declaration by changing the code "public
class Box" to "public class Box<T>". This introduces the type variable, T, that can be used anywhere
inside the class.
With this change, the Box class becomes:
/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) { this.t = t; }
public T get() { return t; }
}
As you can see, all occurrences of Object are replaced by T. A type variable can be any non-primitive type you
specify: any class type, any interface type, any array type, or even another type variable.
This same technique can be applied to create generic interfaces.
Consider that in case you provide a non-primitive type as actual parameter to a method which expects a nonprimitive type Java compiler performs auto-boxing conversion:
https://docs.oracle.com/javase/tutorial/java/data/autoboxing.html
Autoboxing is the automatic conversion that the Java compiler makes between the primitive types and their
corresponding object wrapper classes.
The Java compiler applies autoboxing when a primitive value is:


Passed as a parameter to a method that expects an object of the corresponding wrapper class.
Assigned to a variable of the corresponding wrapper class.
Consider the following method:
public static int sumEven(List<Integer> li) {
int sum = 0;
for (Integer i: li)
if (i % 2 == 0)
sum += i;
return sum;
}
Converting an object of a wrapper type (Integer) to its corresponding primitive (int) value is called unboxing.
The Java compiler applies unboxing when an object of a wrapper class is:


Passed as a parameter to a method that expects a value of the corresponding primitive type.
Assigned to a variable of the corresponding primitive type.
Type Parameter Naming Conventions
By convention, type parameter names are single, uppercase letters. This stands in sharp contrast to the
variable naming conventions that you already know about, and with good reason: Without this convention, it would
be difficult to tell the difference between a type variable and an ordinary class or interface name.
The most commonly used type parameter names are:






E - Element (used extensively by the Java Collections Framework)
K - Key
N - Number
T - Type
V - Value
S,U,V etc. - 2nd, 3rd, 4th types
You'll see these names used throughout the Java SE API and the rest of this lesson.
Invoking and Instantiating a Generic Type
To reference the generic Box class from within your code, you must perform a generic type invocation, which
replaces T with some concrete value, such as Integer:
Box<Integer> integerBox;
You can think of a generic type invocation as being similar to an ordinary method invocation, but instead of
passing an argument to a method, you are passing a type argument — Integer in this case — to the Box class
itself.
The Diamond
In Java SE 7 and later, you can replace the type arguments required to invoke the constructor of a generic class
with an empty set of type arguments (<>) as long as the compiler can determine, or infer, the type arguments from
the context. This pair of angle brackets, <>, is informally called the diamond. For example, you can create an
instance of Box<Integer> with the following statement:
Box<Integer> integerBox = new Box<>();
For more information on diamond notation and type inference, see Type Inference.
Multiple Type Parameters
As mentioned previously, a generic class can have multiple type parameters. For example, the
generic OrderedPair class, which implements the generic Pair interface:
public interface Pair<K, V> {
public K getKey();
public V getValue();
}
public class OrderedPair<K, V> implements Pair<K, V> {
private K key;
private V value;
public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}
public K getKey() { return key; }
public V getValue() { return value; }
}
The following statements create two instantiations of the OrderedPair class:
Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");
The code, new OrderedPair<String, Integer>, instantiates K as a String and V as an Integer.
Therefore, the parameter types of OrderedPair's constructor are String and Integer, respectively. Due
to autoboxing, it is valid to pass a String and an int to the class.
As mentioned in The Diamond, because a Java compiler can infer the K and V types from the
declaration OrderedPair<String, Integer>, these statements can be shortened using diamond notation:
OrderedPair<String, Integer> p1 = new OrderedPair<>("Even", 8);
OrderedPair<String, String> p2 = new OrderedPair<>("hello", "world");
To create a generic interface, follow the same conventions as for creating a generic class.
Parameterized Types
You can also substitute a type parameter (i.e., K or V) with a parameterized type (i.e., List<String>). For
example, using the OrderedPair<K, V>example:
OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new
Box<Integer>(...));
Generic Methods
Generic methods are methods that introduce their own type parameters. This is similar to declaring a generic type,
but the type parameter's scope is limited to the method where it is declared. Static and non-static generic methods
are allowed, as well as generic class constructors.
The syntax for a generic method includes a type parameter, inside angle brackets, and appears before the
method's return type. For static generic methods, the type parameter section must appear before the method's
return type.
The Util class includes a generic method, compare, which compares two Pair objects:
public class Util {
public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
return p1.getKey().equals(p2.getKey()) &&
p1.getValue().equals(p2.getValue());
}
}
public class Pair<K, V> {
private K key;
private V value;
public Pair(K key, V value) {
this.key = key;
this.value = value;
}
public
public
public
public
void setKey(K key) { this.key = key; }
void setValue(V value) { this.value = value; }
K getKey()
{ return key; }
V getValue() { return value; }
}
The complete syntax for invoking this method would be:
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.<Integer, String>compare(p1, p2);
The type has been explicitly provided, as shown in bold. Generally, this can be left out and the compiler will infer
the type that is needed:
Pair<Integer, String> p1 = new Pair<>(1, "apple");
Pair<Integer, String> p2 = new Pair<>(2, "pear");
boolean same = Util.compare(p1, p2);
This feature, known as type inference, allows you to invoke a generic method as an ordinary method, without
specifying a type between angle brackets. This topic is further discussed in the following section, Type Inference.
Bounded Type Parameters
There may be times when you want to restrict the types that can be used as type arguments in a parameterized
type. For example, a method that operates on numbers might only want to accept instances of Number or its
subclasses. This is what bounded type parameters are for.
To declare a bounded type parameter, list the type parameter's name, followed by the extends keyword, followed
by its upper bound, which in this example is Number. Note that, in this context, extends is used in a general
sense to mean either "extends" (as in classes) or "implements" (as in interfaces).
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
public <U extends Number> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}
public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.set(new Integer(10));
integerBox.inspect("some text"); // error: this is still String!
}
}
By modifying our generic method to include this bounded type parameter, compilation will now fail, since our
invocation of inspect still includes aString:
Box.java:21: <U>inspect(U) in Box<java.lang.Integer> cannot
be applied to (java.lang.String)
integerBox.inspect("10");
^
1 error
In addition to limiting the types you can use to instantiate a generic type, bounded type parameters allow you to
invoke methods defined in the bounds:
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n)
{ this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
// ...
}
The isEven method invokes the intValue method defined in the Integer class through n.
Multiple Bounds
The preceding example illustrates the use of a type parameter with a single bound, but a type parameter can
have multiple bounds:
<T extends B1 & B2 & B3>
A type variable with multiple bounds is a subtype of all the types listed in the bound. If one of the bounds is a
class, it must be specified first. For example:
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
class D <T extends A & B & C> { /* ... */ }
If bound A is not specified first, you get a compile-time error:
class D <T extends B & A & C> { /* ... */ }
// compile-time error
Generic Methods and Bounded Type Parameters
Bounded type parameters are key to the implementation of generic algorithms. Consider the following method that
counts the number of elements in an array T[] that are greater than a specified element elem.
public static <T> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e > elem) // compiler error
++count;
return count;
}
The implementation of the method is straightforward, but it does not compile because the greater than operator (>)
applies only to primitive types such as short, int, double, long, float, byte, and char. You cannot use
the > operator to compare objects. To fix the problem, use a type parameter bounded by
the Comparable<T> interface:
public interface Comparable<T> {
public int compareTo(T o);
}
The resulting code will be:
public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T
elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0)
++count;
return count;
}
Generics, Inheritance, and Subtypes
As you already know, it is possible to assign an object of one type to an object of another type provided that the
types are compatible. For example, you can assign an Integer to an Object, since Object is one
of Integer's supertypes:
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger;
// OK
In object-oriented terminology, this is called an "is a" relationship. Since an Integer is a kind of Object, the
assignment is allowed. But Integer is also a kind of Number, so the following code is valid as well:
public void someMethod(Number n) { /* ... */ }
someMethod(new Integer(10));
someMethod(new Double(10.1));
// OK
// OK
The same is also true with generics. You can perform a generic type invocation, passing Number as its type
argument, and any subsequent invocation of add will be allowed if the argument is compatible with Number:
Box<Number> box = new Box<Number>();
box.add(new Integer(10));
// OK
box.add(new Double(10.1)); // OK
Now consider the following method:
public void boxTest(Box<Number> n) { /* ... */ }
What type of argument does it accept? By looking at its signature, you can see that it accepts a single argument
whose type is Box<Number>. But what does that mean? Are you allowed to pass in Box<Integer>
or Box<Double>, as you might expect? The answer is "no", because Box<Integer> and Box<Double> are
not subtypes of Box<Number>.
This is a common misunderstanding when it comes to programming with generics, but it is an important concept to
learn.
Box<Integer> is not a subtype of Box<Number> even though Integer is a subtype of Number.
Note: Given two concrete types A and B (for example, Number and Integer), MyClass<A> has no relationship
to MyClass<B>, regardless of whether or not A and B are related. The common parent
of MyClass<A> and MyClass<B> is Object.
Generic Classes and Subtyping
You can subtype a generic class or interface by extending or implementing it. The relationship between the type
parameters of one class or interface and the type parameters of another are determined by
the extends and implements clauses.
the Collections classes as an example, ArrayList<E> implements List<E>, and List<E>
extends Collection<E>. So ArrayList<String> is a subtype of List<String>, which is a subtype
of Collection<String>. So long as you do not vary the type argument, the subtyping relationship is preserved
between the types.
Using
A sample Collections hierarchy
Wildcards
In generic code, the question mark (?), called the wildcard, represents an unknown type. The wildcard can be
used in a variety of situations: as the type of a parameter, field, or local variable; sometimes as a return type
(though it is better programming practice to be more specific). The wildcard is never used as a type argument for a
generic method invocation, a generic class instance creation, or a supertype.
Upper Bounded Wildcards
You can use an upper bounded wildcard to relax the restrictions on a variable. For example, say you want to write
a method that works on List<Integer>, List<Double>, and List<Number>; you can achieve this by using
an upper bounded wildcard.
To declare an upper-bounded wildcard, use the wildcard character ('?'), followed by the extends keyword,
followed by its upper bound. Note that, in this context, extends is used in a general sense to mean either
"extends" (as in classes) or "implements" (as in interfaces).
To write the method that works on lists of Number and the subtypes of Number, such as Integer, Double,
and Float, you would specify List<? extends Number>. The term List<Number> is more restrictive
than List<? extends Number> because the former matches a list of type Number only, whereas the latter
matches a list of type Number or any of its subclasses.
Consider the following process method:
public static void process(List<? extends Foo> list) { /* ... */ }
The upper bounded wildcard, <? extends Foo>, where Foo is any type, matches Foo and any subtype
of Foo. The process method can access the list elements as type Foo:
public static void process(List<? extends Foo> list) {
for (Foo elem : list) {
// ...
}
}
In the foreach clause, the elem variable iterates over each element in the list. Any method defined in
the Foo class can now be used on elem.
The sumOfList method returns the sum of the numbers in a list:
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
Unbounded Wildcards
The unbounded wildcard type is specified using the wildcard character (?), for example, List<?>. This is called
a list of unknown type. There are two scenarios where an unbounded wildcard is a useful approach:


If you are writing a method that can be implemented using functionality provided in the Object class.
When the code is using methods in the generic class that don't depend on the type parameter. For
example, List.size or List.clear. In fact,Class<?> is so often used because most of the
methods in Class<T> do not depend on T.
Consider the following method, printList:
public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}
The goal of printList is to print a list of any type, but it fails to achieve that goal — it prints only a list
of Object instances; it cannot printList<Integer>, List<String>, List<Double>, and so on, because
they are not subtypes of List<Object>. To write a generic printList method, useList<?>:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
Lower Bounded Wildcards
The Upper Bounded Wildcards section shows that an upper bounded wildcard restricts the unknown type to be a
specific type or a subtype of that type and is represented using the extends keyword. In a similar way, a lower
bounded wildcard restricts the unknown type to be a specific type or a super type of that type.
A lower bounded wildcard is expressed using the wildcard character ('?'), following by the super keyword,
followed by its lower bound: <? super A>.
Note: You can specify an upper bound for a wildcard, or you can specify a lower bound, but you cannot specify
both.
Say you want to write a method that puts Integer objects into a list. To maximize flexibility, you would like the
method to work on List<Integer>, List<Number>, and List<Object> — anything that can
hold Integer values.
To write the method that works on lists of Integer and the supertypes of Integer, such as Integer, Number,
and Object, you would specify List<? super Integer>. The term List<Integer> is more restrictive
than List<? super Integer> because the former matches a list of type Integer only, whereas the latter
matches a list of any type that is a supertype of Integer.
The following code adds the numbers 1 through 10 to the end of a list:
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
Wildcards and Subtyping
As described in Generics, Inheritance, and Subtypes, generic classes or interfaces are not related merely because
there is a relationship between their types. However, you can use wildcards to create a relationship between generic
classes or interfaces.
Given the following two regular (non-generic) classes:
class A { /* ... */ }
class B extends A { /* ... */ }
It would be reasonable to write the following code:
B b = new B();
A a = b;
This example shows that inheritance of regular classes follows this rule of subtyping: class B is a subtype of
class A if B extends A. This rule does not apply to generic types:
List<B> lb = new ArrayList<>();
List<A> la = lb;
// compile-time error
Given that Integer is a subtype of Number, what is the relationship
between List<Integer> and List<Number>?
The common parent is List<?>.
Although Integer is a subtype of Number, List<Integer> is not a subtype of List<Number> and, in fact,
these two types are not related. The common parent of List<Number> and List<Integer> is List<?>.
In order to create a relationship between these classes so that the code can access Number's methods
through List<Integer>'s elements, use an upper bounded wildcard:
List<? extends Integer> intList = new ArrayList<>();
List<? extends Number> numList = intList; // OK. List<? extends Integer> is a
subtype of List<? extends Number>
Alberi Binari Generici
Classe AlberoBST ver.2: Progetto Test2AlberoBST
Creare un nuovo progetto con una versione generica delle classi MioOggetto, Nodo e Albero.
//Classe MioOggetto
// http://stackoverflow.com/questions/12234219/implementing-compareto-withgenerics-and-collection
public class MioOggetto<T extends Comparable<T>> implements
Comparable<MioOggetto<T>> {
T value;
public MioOggetto(T value) {
this.value = value;
}
public T getValue() {
return value;
}
public void setValue(T value) {
this.value = value;
}
@Override
public int compareTo(MioOggetto<T> elem) {
return this.value.compareTo(elem.getValue());
}
@Override
public String toString(){
return value.toString();
}
}
//classe Nodo
public class Nodo<T extends Comparable<T>> {
private MioOggetto<T> value;
private Nodo<T> left; // figlio sinistro
private Nodo<T> right;// figlio destro
public Nodo(MioOggetto<T> valore) {
value = valore;
left = null;
right = null;
}
public MioOggetto<T> getValue() {
return value;
}
public void setLeftChild(Nodo<T> child) {
left = child;
}
public void setRightChild(Nodo<T> child) {
right = child;
}
public Nodo<T> getLeftChild() {
return left;
}
public Nodo<T> getRightChild() {
return right;
}
}
//Classe AlberoBST
import java.util.ArrayList;
import java.util.List;
public class AlberoBST<T extends Comparable<T>> {
private Nodo<T> root;
public AlberoBST() {
root = null;
}
public boolean inserisciValore(MioOggetto<T> valore) {
if (root != null)
return insert(valore, root);
else {
root = new Nodo<>(valore);
return true;
}
}
protected boolean insert(MioOggetto<T> valore, Nodo<T> nodoCorrente) {
if (valore.compareTo(nodoCorrente.getValue()) == 0) // già presente
return false;
if (valore.compareTo(nodoCorrente.getValue()) < 0) {
if (nodoCorrente.getLeftChild() == null) {
nodoCorrente.setLeftChild(new Nodo<>(valore));
return true;
} else
return insert(valore, nodoCorrente.getLeftChild());
}
else {// caso valore.compareTo(nodoCorrente.getValue())>0
if (nodoCorrente.getRightChild() == null) {
nodoCorrente.setRightChild(new Nodo<>(valore));
return true;
} else
return insert(valore, nodoCorrente.getRightChild());
}
}
public boolean ricercaValore(MioOggetto<T> valore) {
return search(valore, root);
}
protected boolean search(MioOggetto<T> valore, Nodo<T> nodoCorrente) {
if (nodoCorrente == null) {
return false;
}
if (valore.compareTo(nodoCorrente.getValue()) < 0)
return search(valore, nodoCorrente.getLeftChild());
else {
if (valore.compareTo(nodoCorrente.getValue()) > 0)
return search(valore, nodoCorrente.getRightChild());
else
return true;
}
}
public void cancellaValore(MioOggetto<T> valore) throws TreeException {
if (valore.compareTo(root.getValue()) == 0) {
insertTree(root.getRightChild(), root.getLeftChild());
root = root.getRightChild();// elimina il padre
} else {
if (valore.compareTo(root.getValue()) < 0)
delete(valore, root, root.getLeftChild());
else
delete(valore, root, root.getRightChild());
}
}
protected void delete(MioOggetto<T> valore, Nodo<T> parent, Nodo<T>
nodoCorrente)
throws TreeException {
if (nodoCorrente == null)
throw new TreeException("Valore inesistente");
if (valore.compareTo(nodoCorrente.getValue()) < 0)
delete(valore, nodoCorrente, nodoCorrente.getLeftChild());
else {
if (valore.compareTo(nodoCorrente.getValue()) > 0)
delete(valore, nodoCorrente, nodoCorrente.getRightChild());
else { // nodoCorrente è il nodo che voglio cancellare
Nodo<T> temp;
if (nodoCorrente.getRightChild() == null)
temp = nodoCorrente.getLeftChild();
else {
temp = nodoCorrente.getRightChild();
insertTree(nodoCorrente.getRightChild(),
nodoCorrente.getLeftChild());
}
if (parent.getLeftChild() == nodoCorrente)
parent.setLeftChild(temp);
else
parent.setRightChild(temp);
}
}
}
/**
* inserisce il sottoalbero addedTreeRoot come elemento più piccolo del
* sottoalbero che ha radice in treeRoot se treeRoot è non nullo
*
* @param treeRoot
* @param addedTreeRoot
*/
private void insertTree(Nodo<T> treeRoot, Nodo<T> addedTreeRoot) {
Nodo<T> parent = null;
while (treeRoot != null) {
parent = treeRoot;
treeRoot = treeRoot.getLeftChild();
}
if (parent != null)
parent.setLeftChild(addedTreeRoot);
}
// e gli altri metodi da adattare…
}
Esercizio 8: Adattare tutti i metodi del progetto AlberoBST in modo da supportare il tipo generico T. Nel
main di prova si dovrà testare il corretto funzionamento della classe con diversi tipi base:
AlberoBST<String> albero2 = new AlberoBST<>();
MioOggetto<String> oo1 = new MioOggetto<>("ciao");
MioOggetto<String> oo2 = new MioOggetto<>("alto");
MioOggetto<String> oo3 = new MioOggetto<>("dato");
MioOggetto<String> oo4 = new MioOggetto<>("basso");
MioOggetto<String> oo5 = new MioOggetto<>("brutto");
albero2.inserisciValore(oo1);
albero2.inserisciValore(oo2);
albero2.inserisciValore(oo3);
albero2.inserisciValore(oo4);
albero2.inserisciValore(oo5);
//serie di test sull’albero
//prova con interi
AlberoBST<Integer> albero3 = new AlberoBST<>();
MioOggetto<Integer> io1 = new MioOggetto<>(10);
MioOggetto<Integer> io2 = new MioOggetto<>(5);
MioOggetto<Integer> io3 = new MioOggetto<>(3);
MioOggetto<Integer> io4 = new MioOggetto<>(30);
MioOggetto<Integer> io5 = new MioOggetto<>(8);
albero3.inserisciValore(io1);
albero3.inserisciValore(io2);
albero3.inserisciValore(io3);
albero3.inserisciValore(io4);
albero3.inserisciValore(io5);
//serie di test sull’albero
Verso un progetto migliore della classe AlberoBST
Con le classi Generiche, non ha molto senso usare la classe MioOggetto che era stata introdotta come classe
wrapper di un tipo base. A questo punto si può scrivere la classe AlberoBST assumendo che il tipo Nodo sia
costituito da un tipo base T…
Classe AlberoBST ver.3: progetto Test3AlberoBST
Obiettivi: classe generica, serializzabile, senza la classe MioOggetto. Creare anche una classe VoceDizionario
che permetta di rappresentare le voci di un dizionario di lingua (ad esempio di Inglese - Italiano, come
riportato nel codice d’esempio).
//Classe Nodo
import java.io.Serializable;
// http://stackoverflow.com/questions/12234219/implementing-compareto-withgenerics-and-collection
public class Nodo<T extends Serializable & Comparable<T>> implements
Serializable{
/**
*
*/
private static final long serialVersionUID = 657720013517038085L;
private T value;
private Nodo<T> left; // figlio sinistro
private Nodo<T> right;// figlio destro
public Nodo(T valore) {
value = valore;
left = null;
right = null;
}
public T getValue() {
return value;
}
public void setLeftChild(Nodo<T> child) {
left = child;
}
public void setRightChild(Nodo<T> child) {
right = child;
}
public Nodo<T> getLeftChild() {
return left;
}
public Nodo<T> getRightChild() {
return right;
}
}
//classe AlberoBST
import
import
import
import
import
import
import
import
import
import
java.io.File;
java.io.FileInputStream;
java.io.FileOutputStream;
java.io.IOException;
java.io.ObjectInputStream;
java.io.ObjectOutputStream;
java.io.Serializable;
java.util.ArrayList;
java.util.Collections;
java.util.List;
public class AlberoBST<T extends Serializable & Comparable<T>> implements
Serializable {
/**
*
*/
private static final long serialVersionUID = -8908773846012112953L;
private Nodo<T> root;
public AlberoBST() {
root = null;
}
public AlberoBST(List<T> lista) {
root = null;
// mischio gli elementi della lista per migliorare il bilanciamento
// dell'albero
Collections.shuffle(lista);
for (T elem : lista) {
inserisciValore(elem);
}
}
public AlberoBST(List<T> list, boolean forceBalance) {
if (forceBalance) {
// ordino la lista
Collections.sort(list);
// creo un albero bilanciato
root = buildBalancedTreeFromSortedList(list).root;
} else // creo un albero, ma non necessariamente bilanciato
{
root = new AlberoBST<>(list).root;
}
}
public boolean inserisciValore(T valore) {
if (root != null) {
return insert(valore, root);
} else {
root = new Nodo<>(valore);
return true;
}
}
protected boolean insert(T valore, Nodo<T> nodoCorrente) {
if (valore.compareTo(nodoCorrente.getValue()) == 0) // già presente
{
return false;
}
if (valore.compareTo(nodoCorrente.getValue()) < 0) {
if (nodoCorrente.getLeftChild() == null) {
nodoCorrente.setLeftChild(new Nodo<>(valore));
return true;
} else {
return insert(valore, nodoCorrente.getLeftChild());
}
} else {// caso valore.compareTo(nodoCorrente.getValue())>0
if (nodoCorrente.getRightChild() == null) {
nodoCorrente.setRightChild(new Nodo<>(valore));
return true;
} else {
return insert(valore, nodoCorrente.getRightChild());
}
}
}
public boolean ricercaValore(T valore) {
return search(valore, root);
}
protected boolean search(T valore, Nodo<T> nodoCorrente) {
if (nodoCorrente == null) {
return false;
}
if (valore.compareTo(nodoCorrente.getValue()) < 0) {
return search(valore, nodoCorrente.getLeftChild());
} else {
if (valore.compareTo(nodoCorrente.getValue()) > 0) {
return search(valore, nodoCorrente.getRightChild());
} else {
return true;
}
}
}
public void cancellaValore(T valore) throws TreeException {
if (valore.compareTo(root.getValue()) == 0) {
insertTree(root.getRightChild(), root.getLeftChild());
root = root.getRightChild();// elimina il padre
} else {
if (valore.compareTo(root.getValue()) < 0) {
delete(valore, root, root.getLeftChild());
} else {
delete(valore, root, root.getRightChild());
}
}
}
protected void delete(T valore, Nodo<T> parent, Nodo<T> nodoCorrente)
throws TreeException {
if (nodoCorrente == null) {
throw new TreeException("Valore inesistente");
}
if (valore.compareTo(nodoCorrente.getValue()) < 0) {
delete(valore, nodoCorrente, nodoCorrente.getLeftChild());
} else {
if (valore.compareTo(nodoCorrente.getValue()) > 0) {
delete(valore, nodoCorrente, nodoCorrente.getRightChild());
} else { // nodoCorrente è il nodo che voglio cancellare
Nodo<T> temp;
if (nodoCorrente.getRightChild() == null) {
temp = nodoCorrente.getLeftChild();
} else {
temp = nodoCorrente.getRightChild();
insertTree(nodoCorrente.getRightChild(),
nodoCorrente.getLeftChild());
}
if (parent.getLeftChild() == nodoCorrente) {
parent.setLeftChild(temp);
} else {
parent.setRightChild(temp);
}
}
}
}
/**
* inserisce il sottoalbero addedTreeRoot come elemento più piccolo del
* sottoalbero che ha radice in treeRoot se treeRoot è non nullo
*
* @param treeRoot
* @param addedTreeRoot
*/
private void insertTree(Nodo<T> treeRoot, Nodo<T> addedTreeRoot) {
Nodo<T> parent = null;
while (treeRoot != null) {
parent = treeRoot;
treeRoot = treeRoot.getLeftChild();
}
if (parent != null) {
parent.setLeftChild(addedTreeRoot);
}
}
// e altri metodi da aggiornare…
/**
* metodo d'istanza per la serializzazione - richiede che T sia anche
* Serializable. Solo se richiesto per la serializzazione T implementa anche
* Serializable
*
* @param pathToFile
* @throws java.io.IOException
*/
public void serialize(String pathToFile) throws IOException {
try (FileOutputStream fileOut = new FileOutputStream(pathToFile);
ObjectOutputStream out = new ObjectOutputStream(fileOut)) {
out.writeObject(this);
}
}
/**
*
* @param pathFromFile
* @return
* @throws IOException
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
public AlberoBST<T> deserialize(String pathFromFile) throws IOException,
ClassNotFoundException {
AlberoBST<T> albero;
try (FileInputStream fileIn = new FileInputStream(pathFromFile);
ObjectInputStream in = new ObjectInputStream(fileIn);) {
albero = (AlberoBST<T>) in.readObject();
}
return albero;
}
/**
*
* @param <T>
* @param albero - albero da serializzare
* @param f - file su cui effettuare la serializzazione
* @throws IOException
*/
public static <T extends Serializable & Comparable<T>> void
serialize(AlberoBST<T> albero, File f)
throws IOException {
try (FileOutputStream fileOut = new FileOutputStream(f);
ObjectOutputStream out = new ObjectOutputStream(fileOut);) {
out.writeObject(albero);
}
}
/**
*
* @param <T>
* @param f - file da cui deserializzare l'albero binario di ricerca
* @return l'albero binario di ricerca
* @throws IOException
* @throws ClassNotFoundException
*/
@SuppressWarnings("unchecked")
public static <T extends Serializable & Comparable<T>> AlberoBST<T>
deserialize(File f) throws IOException,
ClassNotFoundException {
AlberoBST<T> albero;
try (FileInputStream fileIn = new FileInputStream(f);
ObjectInputStream in = new ObjectInputStream(fileIn);) {
albero = (AlberoBST<T>) in.readObject();
}
return albero;
}
}
//classe VoceDizionario
import java.io.Serializable;
public class VoceDizionario implements Comparable<VoceDizionario>, Serializable
{
/**
*
*/
private static final long serialVersionUID = 4165917338641674197L;
private String voce;
private String significato;
public String getVoce() {
return voce;
}
public void setVoce(String voce) {
this.voce = voce;
}
public String getSignificato() {
return significato;
}
public void setSignificato(String significato) {
this.significato = significato;
}
public VoceDizionario(){
voce="";
significato="";
}
public VoceDizionario(String voce, String significato){
this.voce=voce;
this.significato=significato;
}
@Override
public int compareTo(VoceDizionario o) {
return this.getVoce().compareTo(o.getVoce());
}
@Override
public String toString(){
return "("+this.getVoce()+" = "+ this.getSignificato() +")";
}
}
Testare la classe AlberoBST con interi, stringhe e con alcune voci di dizionario, come ad esempio:
//classe MainTest
VoceDizionario v1 = new VoceDizionario("andare", "to go");
VoceDizionario v2 = new VoceDizionario("venire", "to come");
VoceDizionario v3 = new VoceDizionario("corpo del reato", "body of evidence");
VoceDizionario v4 = new VoceDizionario("studiare", "to study");
VoceDizionario v5 = new VoceDizionario("martello", "hammer");
VoceDizionario v6 = new VoceDizionario("spada", "sword");
VoceDizionario v7 = new VoceDizionario("penna", "pen");
VoceDizionario v8 = new VoceDizionario("macchina", "car");
List<VoceDizionario> list= new ArrayList<VoceDizionario>();
list.add(v1);
list.add(v2);
list.add(v3);
list.add(v4);
list.add(v5);
list.add(v6);
list.add(v7);
list.add(v8);
AlberoBST<VoceDizionario> albero4 = new AlberoBST<VoceDizionario>(list);
//effettuare oltre alle solite prove anche un test di serializzazione:
//prova serializzazione
try {
final File f = new
File(MainTest.class.getProtectionDomain().getCodeSource().getLocation().getPath(
));
String fileName = "albero.ser";
String percorso = f.getCanonicalPath() + File.pathSeparator + fileName;
System.out.println("Serializza albero4");
albero4.serialize(percorso);
AlberoBST<VoceDizionario> albero5;
albero5 = (new AlberoBST<VoceDizionario>()).deserialize(percorso);
List<VoceDizionario> lista5;
System.out.println("Stampa albero deserializzato in ordine - metodo
toArrayList(VisitCriteria.INORDER)");
lista5 = albero5.asArrayListView(VisitCriteria.INORDER);
System.out.println(lista5);
} catch (IOException | ClassNotFoundException | TreeException e) {
e.printStackTrace();
}
Diagramma UML del progetto Test3AlberoBST
Classe AlberoBST ver.4: progetto Test4AlberoBST
Obiettivi: ristrutturare la classe AlberoBST per avere la classe Nodo e l’enum VisitCriteria come inner classes
della classe AlberoBST.
Nella classe AlberoBST aggiungere i seguenti metodi:
/**
* Restituisce il nodo contenente il valore
*
* @param valore
* @return il nodo contenente il valore
*/
protected Nodo getNodo(T valore) {
return getNodo(valore, root);
}
private Nodo getNodo(T valore, Nodo nodoCorrente) {
if (nodoCorrente == null) {
return null;
}
if (valore.compareTo(nodoCorrente.getValue()) < 0) {
return getNodo(valore, nodoCorrente.getLeftChild());
} else {
if (valore.compareTo(nodoCorrente.getValue()) > 0) {
return getNodo(valore, nodoCorrente.getRightChild());
} else {
return nodoCorrente;
}
}
}
Progetto Dizionario: il benchmark definitivo
Lo scopo di questo progetto è quello di testare le prestazioni delle diverse strutture dati viste in questo corso
quando si ha a che fare con una grande quantità di dati e/o un elevato numero di operazioni di accesso,
inserimento o cancellazione.
Questo progetto utilizza un dizionario inglese – italiano in formato txt, chiamato inglese_italiano.txt. Il
formato del dizionario è molto semplice: la prima riga è la voce in inglese, la riga successiva è il significato in
italiano, e poi ancora voce e significato e così via. Il dizionario ha circa 250,000 voci ed è dunque costituito da
circa 500,000 righe di testo.
Verranno esaminale le prestazioni di ArrayList, Alberi binari di ricerca (BST) e di HashMap con riferimento a
operazioni di accesso casuale a voci del dizionario, di inserimento di nuove voci, oppure di ordinamento delle
voci del dizionario. Non verrà presa in considerazione la LinkedList dal momento che le sue caratteristiche la
rendono utile principalmente in quei contesti nei quali è richiesto un accesso sequenziale alla struttura dati
e si vuole avere un’occupazione ottimale di memoria. Le prestazioni di una LinkedList sarebbero sicuramente
inferiori a quelle di un ArrayList e delle altre strutture dati prese in considerazione.
Per creare le condizioni di test occorre creare un nuovo progetto AlberoBSTDizionario strutturato come
segue:
Package Dizionario
Package tree
Con le Classi AlberoBST e TreeException
Classe Dizionario
import it.onbit.ex.tree.AlberoBST;
import it.onbit.ex.tree.TreeException;
import
import
import
import
import
import
java.io.BufferedReader;
java.io.File;
java.io.FileReader;
java.io.IOException;
java.util.ArrayList;
java.util.List;
public class Dizionario extends AlberoBST<VoceDizionario> {
protected AlberoBST<VoceDizionario> getAlberoDizionario() {
return this;
}
private static final long serialVersionUID = -6020590390391470213L;
/**
* Crea un albero binario di ricerca con le voci del database
*
* @param database
*
- file che contiene le voci del dizionario
* @param forceBalancing
*
- se true l'albero binario di ricerca è bilanciato
*/
public Dizionario(File database, boolean forceBalancing) {
List<VoceDizionario> myList = buildEntryListFromDatabase(database);
// creo un albero binario di ricerca bilanciato con le voci del database
this.inserisciListaValori(myList, forceBalancing);
}
/**
* Crea un albero binario di ricerca bilanciato con le voci della lista
*
* @param list
*/
public Dizionario(List<VoceDizionario> list) {
super(list, true);
}
public boolean inserisciVoce(VoceDizionario vd) {
return inserisciValore(vd);
}
public boolean cercaVoce(VoceDizionario vd) {
return ricercaValore(vd);
}
public boolean cercaVocePerEntry(String entry) {
VoceDizionario v = new VoceDizionario(entry, "");
return ricercaValore(v);
}
public VoceDizionario getVoce(String entry) {
VoceDizionario v = new VoceDizionario(entry, "");
AlberoBST<VoceDizionario>.Nodo tmp = getNodo(v);
if (tmp!=null)
return tmp.getValue();
else
return null;
}
public String getSignificato(String entry) {
VoceDizionario v = new VoceDizionario(entry, "");
AlberoBST<VoceDizionario>.Nodo tmp = getNodo(v);
if (tmp!=null)
return tmp.getValue().getSignificato();
else
return "";
}
public boolean eliminaVoce(VoceDizionario vd) {
boolean flag = false;
if (ricercaValore(vd)) {
try {
cancellaValore(vd);
flag = true;
} catch (TreeException e) {
e.printStackTrace();
}
}
return flag;
}
public boolean eliminaVocePerEntry(String entry) {
boolean flag = false;
VoceDizionario v = new VoceDizionario(entry, "");
if (ricercaValore(v)) {
try {
cancellaValore(v);
flag = true;
} catch (TreeException e) {
e.printStackTrace();
}
}
return flag;
}
/**
* Il database è un file in formato testo con la seguente struttura: una
* linea è la voce in inglese, la linea successiva è la traduzione in
* italiano
*
* @param dataBase
* @return
*/
protected List<VoceDizionario> buildEntryListFromDatabase(File dataBase) {
//BufferedReader fileReader = null;
List<VoceDizionario> listaDizionario = new ArrayList<>();
String voce;
String significato;
try (BufferedReader fileReader = new BufferedReader(new
FileReader(dataBase))){
// Read the file line by line
while ((voce = fileReader.readLine()) != null && (significato =
fileReader.readLine()) != null) {
listaDizionario.add(new VoceDizionario(voce, significato));
}
} catch (IOException e) {
e.printStackTrace();
}
return listaDizionario;
}
}
ArrayList vs. AlberoBST vs. HashMap
Scrivere la classe MainTest con i seguenti metodi (da implementare):
/**
* Metodo statico ricorsivo che restituisce la VoceDizioanrio che contiene
l'entry specificata
* @param orderedList la lista ordinata di VoceDizionario
* @param entry l'entry di cui si vuole la VoceDizionario corrispondente
* @param left indice sinistro della ricerca dicotomica
* @param right indice destro della ricerca dicotomica
* @return la VoceDizionario che contiene l'entry specificata, null se l'entry
non è presente nella lista
*/
public static VoceDizionario ricercaDicotomica(List<VoceDizionario> orderedList,
String entry, int left, int right);
/**
* Metodo statico che inserisce una VoceDizionario nella lista ordinata,
mantenendo l'ordinamento della lista
* @param orderedList la lista di VoceDIzionario
* @param vd la voce da inserire
* @param left indice sinistro
* @param right indice destro
*/
public static void inserisciInOrdineInListaOrdinata(List<VoceDizionario>
orderedList, VoceDizionario vd, int left, int right);
public static void main(String[] args);
//il main apre il file inglese_italiano.txt
//effettua i seguenti test:
TEST 1: confronto tra i tempi di accesso di una lista non ordinata e un albero
bilanciato; la lista è scandita sequenzialmente
TEST 2: confronto tra i tempi di accesso di una lista ordinata e un albero
bilanciato; la lista ordinata è scandita con criterio di ricerca dicotomica
TEST 3: confronto tra i tempi di inserimento in una lista ordinata e un albero
(non) bilanciato; La lista ordinata è mantenuta ordinata ad ogni inserimento.
L'inserimento è fatto in due passi:
Passo 1: applicando la ricerca dicotomica si determina la posizione dove si
dovrebbe inserire la nuova voce
Passo 2: la nuova voce è inserita nella lista nella posizione stabilita al passo
1
Le voci inserite sono generate in modo random
TEST 4: confronto tra i tempi di accesso in una hashmap e in un albero
bilanciato
TEST 5: confronto tra i tempi di inserimento in una hashmap e in un albero (non)
bilanciato
TEST 6: confronto tra i tempi di ordinamento degli elementi di una hashmap e di
un albero (non) bilanciato
Suggerimento: usare la funzione nanoTime per calcolare quanto tempo impiega un metodo, come nel
seguente esempio:
import java.util.Random;
import java.util.concurrent.TimeUnit;
//nel main…
String fileName = "inglese_italiano.txt";
String percorso =
MainTest.class.getProtectionDomain().getCodeSource().getLocation().getPath() +
File.separatorChar + fileName;
File fileDatabase = new File(percorso);
// test 1 - tempo di accesso di un elemento casuale
// confrontiamo le prestazioni di un albero bilanciato e di una
// lista ordinata
System.out.println("***********************************************");
System.out
.println("TEST 1: confronto tra i tempi di accesso di una lista non
ordinata e un albero bilanciato");
System.out.println("la lista è scandita sequenzialmente");
Dizionario diz = new Dizionario(fileDatabase, true);
// Ottengo l'albero del dizionario
AlberoBST<VoceDizionario> al = diz.getAlberoDizionario();
System.out.println("livello massimo albero " + al.getLivelloMassimo());
System.out.println("livello minimo albero " + al.getLivelloMinimo());
System.out.println("numero nodi albero " + al.getNumeroNodi());
System.out.println("l'albero è " + (al.isBalanced() ? "bilanciato" : "non
bilanciato") + "\n");
// Creo una lista con le voci del dizionario
List<VoceDizionario> l = al.asArrayListView(AlberoBST.VisitCriteria.INORDER);
// mischio gli elementi nella lista
Collections.shuffle(l);
Random rand = new Random();
int testSize = 200000;
long estimatedTimeList = 0;
long estimatedTimeTree = 0;
for (int k = 0; k < testSize; k++) {
// scelgo un indice a caso
int randomNum = rand.nextInt(l.size());
// ottengo una voce del dizionario a caso
VoceDizionario vRandom = l.get(randomNum);
// parte il test sull'albero bilanciato
long startTimeTree = System.nanoTime();
diz.cercaVoce(vRandom);
//dizBalanced.getVoce(vRandom.getVoce());
long endTimeTree = System.nanoTime();
estimatedTimeTree += endTimeTree - startTimeTree;
// parte il test sulla lista non ordinata
long startTimeList = System.nanoTime();
l.contains(vRandom);
//l.get(l.indexOf(vRandom));
long endTimeList = System.nanoTime();
estimatedTimeList += endTimeList - startTimeList;
}// end of for
System.out.println("Tempo di accesso complessivo sulla lista (per " + testSize +
" accessi): "
+ TimeUnit.MICROSECONDS.convert(estimatedTimeList, TimeUnit.NANOSECONDS)
+ " us");
System.out.println("Tempo di accesso complessivo sull'albero (per " + testSize +
" accessi): "
+ TimeUnit.MICROSECONDS.convert(estimatedTimeTree, TimeUnit.NANOSECONDS)
+ " us");
System.out.format("Tempo di accesso albero/ tempo accesso lista: %.3f%% ",
(1.0 * estimatedTimeTree / estimatedTimeList) * 100);
System.out.println("\nVince
"+(estimatedTimeTree<estimatedTimeList?"l'albero":"la lista"));
//to be continued…
I test andrebbero fatti un elevato numero di volte:
Test1, Test2 e Test4: nell’ordine 100,000 volte
Test3 e Test5: nell’ordine di 10,000 volte
Test6: 1 volta
Di seguito sono riportati alcuni risultati, da usare come riferimento per i propri benchmark.
Si osservi che i tempi assoluti dipendono dal processore e in genere dall’HW utilizzato. I valori percentuali
non lo sono. Si osservi che i valori che si ottengono dai test possono essere variabili da test a test, ma
l’andamento statistico non varia.
***********************************************
TEST 1: confronto tra i tempi di accesso di una lista non ordinata e un albero bilanciato
la lista è scandita sequenzialmente
livello massimo albero 17
livello minimo albero 16
numero nodi albero 249932
l'albero è bilanciato
Tempo di accesso complessivo sulla lista (per 200000 accessi): 9105347 us
Tempo di accesso complessivo sull'albero (per 200000 accessi): 267359 us
Tempo di accesso albero/ tempo accesso lista:
2,936%
Vince l'albero
***********************************************
TEST 2: confronto tra i tempi di accesso di una lista ordinata e un albero bilanciato
la lista ordinata è scandita con criterio di ricerca dicotomica
Tempo di accesso complessivo sulla lista (per 200000 accessi): 126169 us
Tempo di accesso complessivo sull'albero (per 200000 accessi): 246853 us
Tempo di accesso lista/ tempo accesso albero:
51,111%
Vince la lista
***********************************************
TEST 3: confronto tra i tempi di inserimento in una lista ordinata e un albero (non) bilanciato
La lista ordinata è mantenuta ordinata ad ogni inserimento. L'inserimento è fatto in due passi:
passo 1: applicando la ricerca dicotomica si determina la posizione dove si dovrebbe inserire la
nuova voce
passo 2: la nuova voce è inserita nella lista nella posizione stabilita al passo 1
Le voci inserite sono generate in modo random
numero elementi lista: 249932
Tempo di inserimento complessivo sulla lista ordinata (per 10000 inserimenti): 296268 us
Tempo di inserimento complessivo sull'albero (per 10000 inserimenti): 16454 us
Tempo di inserimento albero/ tempo inserimento lista ordinata:
5,554%
Vince l'albero
Dopo 10000 inserimenti l'albero del dizionario si trova nelle seguenti condizioni:
livello massimo albero 21
livello minimo albero 16
numero nodi albero 259932
l'albero è non bilanciato
Dopo 10000 inserimenti la lista ordinata si trova nelle seguenti condizioni:
numero elementi lista: 259932
***********************************************
TEST 4: confronto tra i tempi di accesso in una hashmap e in un albero bilanciato
livello massimo albero 17
livello minimo albero 16
numero nodi albero 249932
l'albero è bilanciato
numero elementi nella mappa: 249932
Tempo di accesso complessivo sulla mappa (per 200000 accessi): 21708 us
Tempo di accesso complessivo sull'albero (per 200000 accessi): 254161 us
Tempo di accesso mappa/ tempo accesso albero:
8,541%
Vince la mappa
***********************************************
TEST 5: confronto tra i tempi di inserimento in una hashmap e in un albero (non) bilanciato
numero elementi nella mappa: 249932
Tempo di inserimento complessivo sulla mappa (per 10000 inserimenti): 3531 us
Tempo di inserimento complessivo sull'albero (per 10000 inserimenti): 17986 us
Tempo di inserimento mappa/ tempo inserimento albero:
19,634%
Vince la mappa
Dopo 10000 inserimenti l'albero del dizionario si trova nelle seguenti condizioni:
livello massimo albero 21
livello minimo albero 16
numero nodi albero 259932
l'albero è non bilanciato
Dopo 10000 inserimenti la hash map si trova nelle seguenti condizioni:
numero elementi hash map: 259932
***********************************************
TEST 6: confronto tra i tempi di ordinamento degli elementi di una hashmap e di un albero (non)
bilanciato
numero elementi nella mappa: 259932
numero elementi nell'albero: 259932
Tempo per ottenere una lista ordinata da una mappa: 335475 us
Tempo per ottenere una lista ordinata da un albero: 12326 us
Tempo di ordinamento albero/ tempo ordinamento mappa:
3,674%
Vince l'albero
Tipologie di Alberi binari di ricerca
https://en.wikipedia.org/wiki/Binary_search_tree#Types
There are many types of binary search trees. AVL trees and red-black trees are both forms of selfbalancing binary search trees. A splay tree is a binary search tree that automatically moves frequently
accessed elements nearer to the root. In a treap (tree heap), each node also holds a (randomly chosen)
priority and the parent node has higher priority than its children. Tango trees are trees optimized for fast
searches.
Alberi nelle API di Java
Nella API di Java ci sono due strutture dati che implementano il concetto di albero: TreeMap e TreeSet.
You choose a map in the situation where you have keys and you want to associate each key with a value and later
query (or change) the associated value of each key. You choose a set when you want to have a set of values and
you want to add and remove items from that set and check whether a given item is included in that set.
L’albero binario AlberoBST che abbiamo implementato è una sorta di TreeSet.
TreeMap<K,V>
public class TreeMap<K,V>
extends AbstractMap<K,V>
implements NavigableMap<K,V>, Cloneable, Serializable
A Red-Black tree based NavigableMap implementation. The map is sorted according to the natural ordering of its
keys, or by a Comparator provided at map creation time, depending on which constructor is used.
This implementation provides guaranteed log(n) time cost for the containsKey, get, put and remove operations.
Algorithms are adaptations of those in Cormen, Leiserson, and Rivest's Introduction to Algorithms.
Note that the ordering maintained by a tree map, like any sorted map, and whether or not an explicit comparator is provided,
must be consistent with equals if this sorted map is to correctly implement the Map interface.
(See Comparable or Comparator for a precise definition of consistent with equals.) This is so because
the Map interface is defined in terms of the equals operation, but a sorted map performs all key comparisons using
its compareTo (or compare) method, so two keys that are deemed equal by this method are, from the standpoint of
the sorted map, equal. The behavior of a sorted map is well-defined even if its ordering is inconsistent with equals; it
just fails to obey the general contract of the Map interface.
TreeSet<E>
public class TreeSet<E>
extends AbstractSet<E>
implements NavigableSet<E>, Cloneable, Serializable
A NavigableSet implementation based on a TreeMap. The elements are ordered using their natural ordering, or by
a Comparator provided at set creation time, depending on which constructor is used.
This implementation provides guaranteed log(n) time cost for the basic operations ( add, remove and contains).
Note that the ordering maintained by a set (whether or not an explicit comparator is provided) must be consistent with
equals if it is to correctly implement the Set interface. (See Comparable orComparator for a precise definition
of consistent with equals.) This is so because the Set interface is defined in terms of the equals operation, but
a TreeSet instance performs all element comparisons using its compareTo (or compare) method, so two
elements that are deemed equal by this method are, from the standpoint of the set, equal. The behavior of a set is welldefined even if its ordering is inconsistent with equals; it just fails to obey the general contract of the Set interface.