appunti Java
pag.154
11. Strutture dati
Nella soluzione dei problemi sono già stati utilizzati diversi tipi di strutture dati
ma fino ad ora non si è discusso in modo sistematico delle loro caratteristiche e
specificità d'uso. Una prima definizione approssimata di struttura dati potrebbe
essere la seguente:
Una
struttura
dati
è
un
"contenitore"
di
informazioni che si caratterizza per i suoi
attributi "logici", quali Modalità di accesso,
Dimensionamento,
Organizzazione
dei
suoi
componenti, o per gli attributi "fisici", quali i
Tempi di accesso ad un componente e la Persistenza
delle informazioni .
Un array ha i seguenti attributi "logici":
1. Modalità di accesso
ƒ sequenziale se per trovare una informazione lo si deve scorrere dalla
prima componente fino a quella desiderata;
ƒ indicizzato se si conosce la posizione del dato cercato e si usa
l'indice per accedervi direttamente.
2. Dimensionamento
ƒ statico di norma, infatti un array una volta dimensionato nel
programma non può essere "fatto crescere".
3. Organizzazione dei componenti
ƒ disordinata se questi vengono inseriti senza un particolare ordine;
ƒ ordinata se gli Oggetti inseriti sono dotati di una particolare relazione
d’ordine che viene mantenuta dalla struttura: ordine Alfabetico,
ordine di Grandezza crescente, decrescente ecc.;
ƒ in ogni caso un array è sempre aderente alla struttura della memoria
RAM in quanto ogni locazione di questa ha un indirizzo numerico e
l'array è memorizzato in celle adiacenti (sequenza) a cui si fa
corrispondere un indice intero da 0..n.
ƒ con componenti duplicati o non duplicati a seconda del programma
che gestisce la struttura.
Ma un array è dotato anche dei seguenti attributi "fisici":
4. Persistenza delle informazioni
ƒ "breve" o "volatile" in quanto la memoria conserva i dati solo fin che
il programma è in esecuzione o il computer è alimentato da corrente.
Si dice che è una struttura dati Volatile (e non permanente come
invece è il file memorizzato su supporto magnetico).
5. Tempi di accesso ad un componente
ƒ "molto veloci" in quanto il suo supporto è la memoria RAM (ordine di
grandezza dei nanosecondi 10-9 s.). In contrasto con i file di caratteri
o byte che hanno per supporto un disco che sono "lenti" (ordine di
grandezza dei millisecondi 10-3 s.).
In sostanza i parametri per valutare una struttura dati potrebbero essere:
appunti Java
Accesso
ƒ sequenziale
ƒ indicizzato
pag.155
Dimensionamento Organizzazione
Persistenza
ƒ volatile
ƒ statico
ƒ ordinata
ƒ permanente
ƒ dinamico
ƒ disordinata
ƒ con duplicati
ƒ senza duplicati
Tempi di accesso
ƒ lento (10-3)
ƒ veloce(10-9)
Se si analizzano le strutture dati a noi note e si classificano usando gli attributi
discussi si ottiene la seguente tabella comparativa:
Accesso
Array mono o
sequenziale e
pluridimensionale indicizzato
File di caratteri o
byte
sequenziale e
indicizzato
Stream o Filtro di
caratteri o byte
sequenziale e
indicizzato
String o
StringBuffer
sequenziale e
indicizzato
Dimensionamento Organizzazione
statico
disordinata o
ordinata
con duplicati
dinamico
disordinata o
ordinata
con duplicati
dinamico
disordinata o
ordinata
con duplicati
dinamico in java
disordinata o
statico Pascal o C ordinata
con duplicati
Persistenza
volatile
Tempo di acc.
veloce
permanente
lento
volatile
lento o veloce
volatile
veloce
11.1 Strutture dati di uso comune
E' pratica consolidata di chi progetta programmi quella di utilizzare tipi di
strutture dati diverse a seconda del problema da risolvere in modo che queste
siano aderenti alla specifica situazione. Si è quindi consolidata in informatica la
formalizzazione di alcune strutture dati di uso frequente quali liste, pile, code
insiemi, alberi, grafi e altre ancora.
I linguaggi più moderni forniscono queste strutture dati, con le relative operazioni
formalizzate, per sollevare di una parte di lavoro di codifica il programmatore di
applicazioni. In Java le strutture dati di utilità sono contenute nel package
java.util (in parte in java.io visto in precedenza) e ciascuna di esse è una classe
che appartiene ad una ben strutturata gerarchia. Si studieranno in seguito
alcune delle classi più importanti del package java.util.
11.2 Liste
Una delle strutture dati di più largo uso in programmazione è la Lista che si
potrebbe definire come un Abstract Data Type (Tipo di Dato Astratto) nel seguente
modo:
Una
lista
è
una
struttura
dati
sequenziale
costituita da zero o più oggetti.
La definizione ci informa che:
ƒ Una lista può essere vuota;
ƒ Se non è vuota ha un primo elemento (detto anche testa della lista) e un
ultimo elemento;
appunti Java
ƒ
ƒ
ƒ
pag.156
Ogni suo elemento, esclusi il primo e l'ultimo, è dotato di un predecessore e
di un successore;
Può essere virtualmente illimitata (su un computer la lista è forzatamente
limitata dalle dimensioni della memoria)
Si rappresenta, come una successione, nel seguente modo { a1, a2, ... , an}
Tra le strutture dati studiate la String è una tipica Lista di caratteri, come pure
un File di caratteri o di byte. Il file è stato rappresentato come una sequenza di
dati nel seguente modo:
ATT
INI
......
EOF
Analogamente al File si potrebbe immaginare la rappresentazione grafica di una
lista come una struttura costituita da Nodi; tali nodi contengono Oggetti (le
informazioni) collegati tra loro da "link o reference" mediante i quali è possibile
passare da un nodo al suo successore (predecessore) come nella figura.
iniz
Object
Object
Object
Object
null
Si nota che una lista deve essere sempre dotata di un "link" particolare che indica
la sua "testa" (iniz) e deve essere dotata di un "segno" particolare (null) che ne
indica la conclusione.
A differenza del file questa struttura, memorizzata in memoria di lavoro, è volatile.
Ogni Object deve essere dotati di uno o più link che indicano il suo successore
(e/o predecessore); tale link sono rappresentati graficamente con una freccia.
Una struttura dati di questo tipo (una classe in Java) di quali operazioni deve
essere dotata ?
Ragionando sull'Abstract Data Type Lista si dovrebbe disporre di :
Costruttore/i
Inserzione, Cancellazione e Ricerca:
‰ Aggiunta di un oggetto in testa
‰ Aggiunta di un oggetto in coda
‰ Aggiunta di un oggetto in una posizione qualsiasi
‰ Cancellazione di un oggetto noto
‰ Cancellazione di un oggetto di posizione nota
‰ Ricerca dell'esistenza di un oggetto noto
‰ Determinazione della posizione di un oggetto noto
Concatenazione:
‰ Aggiungere una Lista in coda a una Lista data
Altri metodi:
‰ Operatore per leggere l'oggetto di testa
‰ Operatore per leggere l'oggetto in coda
‰ Operatore per leggere un oggetto di posizione nota
‰ Operatore per stabilire se la lista è Vuota o meno
‰
appunti Java
‰
‰
pag.157
Operatore per conoscerne la dimensione
Operatore per sapere se durante la lettura si è giunti al termine della
Lista (deve consentire lo scorrimento in sequenza dal primo all'ultimo
elemento);
Se si analizza la struttura di Lista e la si classifica usando gli attributi discussi si
ottiene la seguente tabella comparativa:
ListaSequenziale
Accesso
sequenziale
Dimensionamento Organizzazione
dinamico
disordinata o
ordinata
con duplicati
Persistenza
volatile
Tempo di acc.
veloce
La classe LinkedList del package java.util rappresenta pienamente tale ADT. Ecco
di seguito i suoi metodi:
Classe
Costruttori
Metodi
LinkedList
Identificatori:
LinkedList();
Invocazione:
LinkedList L =new LinkedList();
Effetti:
Crea un oggetto Lista identificato da L vuoto.
Identificatore
void add(Object);
void add(Index, Object);
void addFirst(Object);
boolean remove(Object);
Object remove(Index);
void clear();
boolean contains(Object);
int indexOf(Object);
boolean addAll(Collection);
Object getFirst();
Object getLast();
Object get(Index);
boolean isEmpty();
int size();
Iterator iterator();
ListIterator listIterator():
Invocazione:
Pckage e
Gerarchia
Effetti::
java.util
java.Object
java.util.AbstractCollection
java.util.LinkedList
Si noti che i metodi iterator() e listIterator() sono metodi particolari che hanno la
funzione di creare un Oggetto iteratore (più esattamente si tratta di una
interfaccia) che serve per "scorrere" la lista.
Un Iterator e un ListIterator differiscono solo lievemente. Il primo è dotato di due
metodi e consente di scorrere la sequenza solo dalla testa alla fine, mentre il
secondo possiede altri due metodi che consentono lo scorrimento a ritroso.
appunti Java
pag.158
Interface
Iterator
Come si ottiene un
iterator
esempio
Metodi
Identificatore
Invocazione:
Effetti::
Pckage e
Gerarchia
java.util
Interface
ListIterator
Come si ottiene un
ListIterator
esempio
Metodi
Identificatore
Invocazione:
Effetti::
Pckage e
Gerarchia
java.util
Usando un opportuno metodo che ottiene un
Iteratore da una Classe di tipo "struttura dati"
come ad esempio la LinkedList, un Vector o un
Set
LinkedList L=new LinkedList(); ....
Iterator iter = L.iterator();
boolean hasNext();
Object next();
LinkedList L= ......;
......;
Iterator iter=L.iterator(); // crea un iteratore
while (iter.hasNext()) {
// iter non è finito ?
Object o = iter.next(); // leggi l'oggetto
.....;
}
Scorre l'iteratore ottenuto dalla lista dal primo
all'ultimo oggetto.
java.Object
java.util.Iterator
Usando un opportuno metodo che ottiene un
Iteratore da una Classe di tipo "struttura dati"
come ad esempio la LinkedList, un Vector o un
Set
LinkedList L=new LinkedList(); ....
ListIterator iter = L.listIterator();
boolean hasNext();
Object next();
boolean hasPrevious();
Object previous();
ListIterator iter=L.listIterator();
// Attenzione ! Il puntatore è sulla testa della
// lista e la chiamata:
boolean B = iter.hasPrevious();
// restituisce false e quindi non si può scorrere a
// ritroso se prima non si è usato next()
Scorre l'iteratore ottenuto dalla lista dal primo
all'ultimo oggetto e viceversa.
java.Object
java.util.ListIterator
Per verificare la differenza tra l'interfaccia Iterator e ListIterator si propone un
esempio che indica come fare per creare e scorrere una lista dal primo all'ultimo
elemento.
esempio 1. "Si desidera realizzare una lista nella quale inserire alcuni Oggetti
Integer, String e Boolean e quindi stamparli in sequenza." Richiesta:
Realizzare un main() che prima inserisca i dati e poi li stampi tramite un ciclo.
appunti Java
pag.159
Codifica:
import java.util.*;
public class cap11_es_01 {
public static void main(String args[])
{
LinkedList L=new LinkedList(); // (1)
Integer I=new Integer(99);
L.add(I);
// (2)
String S=new String ("Banana");
L.add(S);
// (3)
Boolean B=new Boolean(true);
L.add(B);
// (4)
I=new Integer(3); L.add(I);
I=new Integer(17); L.add(I);
B=new Boolean(false); L.add(B);
System.out.println("Lista di "+L.size()+" Oggetti:");
Iterator it=L.iterator();
while (it.hasNext())
System.out.println(""+it.next());
System.out.println("Fine esecuzione.");
}
}
//
//
//
//
(5)
(6)
(7)
(8)
Esecuzione:
l’output della finestra sarà:
Lista di 6 Oggetti:
99
Banana
true
3
17
false
Fine esecuzione.
Commento al codice:
La nota (1) mostra il costruttore, le (2, 3, 4) la costruzione e l'inserimento di
oggetti, la (5) invoca il metodo che restituisce l'Iteratore it con il quale si
utilizzano i metodi it.hasNext() (6) e it.next() (7) per realizzare il ciclo di scansione
della Lista. Il metodo hasNext() esegue il test per verificare se si è giunti alla fine
della lista e next() acquisisce in lettura l'oggetto e sposta il puntatore all’elemento
successivo.
Sostituendo alle ultime righe le seguenti (6', 7', 8', 9', 10')si ottiene prima un
identico risultato (la stampa diretta) e quindi la stampa inversa della lista.
ListIterator lit=L.listIterator();
while (lit.hasNext())
System.out.println(""+lit.next());
System.out.println("Fine lettura diretta.");
while (lit.hasPrevious())
System.out.println(""+lit.previous());
System.out.println("Fine lettura inversa.");
// (6')
// (7')
// (8')
// (9')
// (10')
appunti Java
pag.160
esempio 2. "progettare una sottoclasse di LinkedList chiamata Lista che
implementi il solo metodo di inserimento ordinato di Stringhe in modo che ciascuna
stringa sia collocata in ordine lessicografico." Richieste:
Il metodo oltre a mantenere l'ordine deve inserire anche stringhe ripetute che
dovranno essere consecutive. Il main() di prova dovrà stampare la lista con un
iteratore per verificare il corretto funzionamento del metodo.
L’esercizio chiede di progettare la classe Lista sottoclasse di LinkedList come
mostrato dalla figura sottostante. La sottoclasse da progettare non ha una
validità generale nel senso che non consente di inserire in modo ordinato Oggetti
qualunque ma solo oggetti di tipo String. Si tratta di un esempio molto
particolare.
Codifica della sottoclasse:
import java.util.*;
public class Lista extends LinkedList {
public void put(String S) {
int i=0; boolean trovato=false, esiste=false;
while (i<size() && !trovato && !esiste) {
(0)
String A=(String) get(i);
if (A.compareTo(S)>0) trovato=true;
(1)
else if (A.compareTo(S)==0) esiste=true; (2)
else i++;
(3)
}
add(i,S);
(4)
}
LinkedList
Lista
}
Codifica del main:
import java.util.*;
public class cap11_es_02 {
public static void main(String args[]) {
Lista L=new Lista();
(5)
String S[]={"fico","banana","mela","fico","zucca","pera","pera"};
for (int i=0; i<S.length; i++)
L.put(S[i]);
(6)
System.out.println("\nLista di "+L.size()+" Oggetti:"); (7)
Iterator it=L.iterator();
(8)
while (it.hasNext())
(9)
System.out.println(""+it.next());
(10)
System.out.println("\nFine esecuzione.");
}
}
Output:
Lista di 7 Oggetti:
banana
fico
fico
mela
pera
pera
zucca
Fine esecuzione.
Commento al codice:
appunti Java
pag.161
Nel metodo put() la condizione (0) evidenzia che l’inserimento ordinato può essere
eseguito solo dopo una ricerca che determina la posizione di inserimento della
stringa nella lista. La ricerca ha termine in tre casi distinti (1) se ho trovato una
stringa che segue alfabeticamente quella da inserire; (2) se ho trovato una stringa
identica a quella da inserire; (3) se sono arrivato al termine.
La nota (4) evidenzia il metodo usato per inserire la stringa nella popsizione
corretta. Le note da (5) a (10) mostrano che la sottoclasse Lista è autorizzata ad
usare i metodi della classe genitrice che ha ereditato.
11.3 Vettori dinamici
Tra le altre strutture dati di vasto uso in programmazione vi è sicuramente
l'array; ma anche in java si tratta di una struttura dati sostanzialmente statica.
Anche se può essere dimensionato dinamicamente durante l'esecuzione, la sua
dimensione non è più modificabile pena la perdita delle informazioni allocate fino
a quel momento.
Un vettore dotato di indice e capace di crescere dinamicamente come la Lista
sarebbe ideale per risolvere problemi che devono trattare i dati sia in sequenza
(lista) che in modo indicizzato (array). Una struttura con queste caratteristiche
deve forzatamente sacrificare "sull'altare della dinamicità " un poco di efficienza
ma la sua costruzione è possibile e in effetti tale struttura "ibrida" è presente in
java.util sotto forma di Classe con i seguenti metodi:
Classe
Costruttori
Metodi
Vector
Identificatori:
Invocazione:
Vector();
Vector(Collection c);
Vector(int initCapacity);
Vector(int initCapacity, int capacityIncr);
Vector V = Vector();
Effetti:
Crea un oggetto Vector identificato da V vuoto.
Identificatore
void add(Object);
void add(Index, Object);
void insertElementAt(Index, Object);
void setElementAt(Index, Object);
boolean remove(Object);
void removeElementAt(Index);
void clear();
boolean contains(Object);
int indexOf(Object);
boolean addAll(Collection);
Object elementAt(Index);
Object get(Index);
boolean isEmpty();
int size();
int capacity()
Invocazione:
Pckage e
Gerarchia
Effetti::
java.util
java.Object
java.util.AbstractCollection
java.util.Vector
appunti Java
pag.162
Se si analizza le struttura di tipo Vector e la si classifica usando gli attributi
discussi si ottiene la seguente tabella comparativa:
Accesso
sequenziale e
indicizzato
Vector
Dimensionamento Organizzazione
dinamico
disordinata o
ordinata
con duplicati
Persistenza
volatile
Tempo di acc.
veloce
per verificare come operare per creare e scorrere un Vector dal primo all'ultimo
elemento proponiamo un esempio.
esempio 3. "Si desidera realizzare un Vector nel quale inserire alcuni Oggetti
Integer, String e Boolean e quindi stamparli in sequenza." Richiesta:
Realizzare un main() che prima inserisca i dati e poi li stampi in un ciclo.
Codifica:
import java.util.*;
public class cap11_es_03 { public static void main(String args[]) {
Vector V=new Vector(); // (1)
Integer I=new Integer(99);
V.add(I);
// (2)
String S=new String ("Banana");
V.add(S);
// (3)
Boolean B=new Boolean(true);
V.add(B);
// (4)
I=new Integer(3); V.add(0,I);
// (5)
I=new Integer(17); V.setElementAt(I,1);
// (6)
B=new Boolean(false); V.add(B);
System.out.println("Capacità="+V.capacity()+" Dimensione="+V.size()); // (7)
int i=0;
// (8)
while (i<V.size()) {
// (9)
System.out.println("V["+i+"]="+V.get(i));
// (10)
i++;
}
System.out.println("Fine esecuzione.");
}
}
Esecuzione:
l’output della finestra sarà:
Capacità=10 Dimensione=5
V[0]=3
V[1]=17
V[2]=Banana
V[3]=true
V[4]=false
Fine esecuzione.
Commento al codice:
La nota (1) mostra il costruttore, le (2, 3, 4) la costruzione e l'inserimento in coda
di oggetti, la (5) invoca il metodo che inserisce un elemento nella componente di
posizione 0, la (6) utilizza il metodo di sovrascrittura sull'Oggetto di posizione 1,
(l'Integer 99 viene sovrascritto in modo distruttivo). Le (7 8, 9, 10) utilizzano i
metodi necessari per realizzare il ciclo di scansione del Vector utilizzando l'indice
di scorrimento.
appunti Java
pag.163
11.4 Collezioni e Insiemi
Si discuteranno dal punto di vista astratto due strutture dati la Collezione e
l'Insieme per vedere le proprietà di cui godono. In particolare un insieme (di
oggetti) deve rispettare la nozione che conosciamo dalla matematica e consentire
l'esecuzione corretta delle operazione note.
Un insieme è una collezione o aggregato di oggetti
definiti e distinti tra loro che si dicono elementi
dell'insieme.
Alla nozione di insieme sono associate simboli e operazioni in particolare si
utilizzano i simboli di:
appartenenza [ x ε D ]
‰ insieme vuoto [ Φ ]
‰ sottoinsieme [ A ⊆ B]
‰ rappresentazione dell'insieme [ { a,b,c,d})
Ci attendiamo poi che si possano eseguire le operazioni di:
‰ Unione
[ ∪ ]
‰ Intersezione
[ ∩ ]
‰ Differenza.
‰
Ragionando sull'Abstract Data Type Insieme si dovrebbe disporre di :
Costruttore/i
Inserzione, Cancellazione e Ricerca:
‰ Aggiunta di un oggetto all'insieme (solo se non è già presente)
‰ Cancellazione di un oggetto noto
‰ Ricerca dell'esistenza di un oggetto noto (appartenenza)
‰ Sottoinsieme
Operazioni:
‰ Unione
‰ Intersezione
‰ Differenza
Altri metodi:
‰ Cardinalità dell'insieme (la sua dimensione attuale)
‰ Operatore per stabilire se si tratta di un insieme Vuoto
‰ Operare lo scorrimento (in qualche modo) di tutti gli oggetti per
ottenerne una rappresentazione tabulare.
‰
La nozione di Insieme utilizza il termine Collezione o Aggregato di oggetti, e si
potrebbe pensare che una Collezione sia un termine che designa una entità più
generale (o generica) del concetto di insieme. In cosa può consistere tale
diversità? Che cosa hanno in comune Collezione e l'Insieme?
Potremmo dire che sia la Collezione che l'Insieme hanno la proprietà comune di
essere entrambi "contenitori" di oggetti senza ordine (non esiste un primo o un
ultimo elemento di un insieme, esso può contenere oggetti non confrontabili).
appunti Java
pag.164
Contemporaneamente potremmo dire che la differenza tra le due entità consiste
nel fatto che l'insieme NON ammette elementi ripetuti mentre una Collezione
può ammetterli.
Potremmo definire tale struttura dati nel seguente modo:
la Collezione è una raccolta di oggetti distinti o
duplicati. In altri termini si potrebbe dire che un
Insieme è una Collezione che non ammette oggetti
duplicati.
Ma quali sono le operazioni che ci attendiamo di poter eseguire su una entità
(struttura dati) di questo tipo ?
Nella seconda definizione si nota che esiste una relazione di generalizzazione tra
Insieme e Collezione (un insieme è una collezione particolare) che potrebbe
rendere applicabile il concetto di ereditarietà tipico della progettazione di classi.
Molte delle operazioni che valgono per una Collezione potrebbero valere anche per
un Insieme.
Insieme
Collection
Accesso
non sequenziale
non indicizzato
non sequenziale
non indicizzato
Dimensionamento Organizzazione
dinamico
disordinata
senza duplicati
dinamico
disordinata
con duplicati
Persistenza
volatile
Tempo di acc.
veloce
volatile
veloce
Si analizzerà di seguito la struttura della gerarchia delle Classi e Interfacce di
java.util, per progettare se necessario gli ADT che non ci soddisfano
completamente.
alcune classi e interfacce del package java.util
Object
Collection
Iterator
ListIterator
AbstractCollection
Set
List
LinkedList
Coda
Vector
HashSet
Stack
Insieme
TreeSet
java.util (parte)
appunti Java
pag.165
Lasciando al lettore l'analisi delle altre Classi del package java.util quali Date,
Calendar, Time molto utili nella programmazione di applicazioni, si cercherà di
interpretare la "filosofia" con cui le sole classi in grassetto nella gerarchia
disegnata sono state progettate.
La prima cosa da notare è la gerarchia di Classi:
AbstractCollection, LinkedList, Vector, HashSet.
e di Interfacce
Collection, List e Set.
Il progettista ha deciso che le caratteristiche comuni a tutte le strutture dati
dinamiche sono "raggruppate" nella classe AbstractCollection che implementa
l'intefaccia Collection.
In altri termini tutte le strutture dati dinamiche sono collezioni. In altre parole
possono essere "pensate" come raccolte indifferenziate di oggetti duplicati o meno.
Un HashSet è una Collection cosi come lo è una LinkedList o un Vector.
La seconda cosa da notare è che non tutti gli ADT Collection sono Liste. Il
progettista ha suddiviso le Collection in strutture dati sequenziali (LinkedList,
Vector) e per queste ha realizzato l'interfaccia List e in strutture senza duplicati
(HashSet e TreeSet) e per queste ha realizzato l'interfaccia Set.
In altre parole una LinkedList e un Vector sono una List, un HashSet è un Set.
Tutte sono AbstractCollection e quindi Collection.
Se si esamina la classe HashSet, o l'interfaccia Set, sotto sono elencati costruttori
e metodi, si nota che le operazioni non sono del tutto coincidenti con quelle attese
per una struttura dati di tipo Insieme.
Classe
Costruttori
HashSet
Identificatori:
Invocazione:
Effetti:
Metodi
Identificatore
HashSet();
HashSet(Collection c);
HashSet A = new HashSet();
Nel primo caso Crea un oggetto HashSet
identificato da A vuoto. Nel secondo crea un set da
un Vector eliminando i duplicati.
void add(Object);
boolean remove(Object);
boolean removeAll(Collection);
void clear();
boolean equals(HashSet);
boolean contains(Object);
boolean addAll(Collection);
boolean isEmpty();
int size();
Invocazione:
Effetti::
Pckage e
Gerarchia
java.util
java.Object
java.util.AbstractCollection
java.util.HeshSet
appunti Java
pag.166
Mancano le operazioni "dirette" di Unione e Intersezione e Differenza anche se
esistono operazioni di concatenamento tra Collection e di cancellazione di
elementi o collezioni. Questa insoddisfazione sarà "risolta" progettando una classe
Insieme come sottoclasse di HashSet nella quale siano implementate le
operazioni elencate in precedenza per gli insiemi.
Per quanto riguarda la Collection, l'interfaccia esistente in java.util può essere
soddisfacente in quanto consente tutte le operazioni di inserimento, ricerca e
cancellazione e mantiene anche elementi duplicati.
11.4.1 La sottoclasse Insieme
Si progetterà di seguito la classe insieme come sottoclasse di HashSet e si farà in
modo che questa sia ancora una Collection. La gerarchia del paragrafo
precedente mostra la collocazione ereditaria della classe Insieme. Di seguito si è
indicato lo schema della classe con i metodi richiesti.
HashSet
Insieme
Insieme()
Insieme(Collection c);
Insieme unione(Insieme i);
Insieme intersezioine(Insieme i);
Insieme differenza(Insieme i);
boolean contiene(Object o);
void metti(Object o);
boolean togli(Object o);
boolean vuoto();
int cardin();
boolean sottins(Insieme i);
boolean identico(Insieme i);
void svuota();
Iterator iterator();
La codifica è lasciata come esercizio al lettore esercizio 11.09.
Proponiamo un esempio di utilizzo della Classe Insieme progettata.
esempio 4. "Assegnare a un insiemi i numeri da 20 a 29 e ad un secondo insieme i
numeri 15 a 24 e stamparli. Ottenere Unione, Intersezione e le due differenze e
stamparle ancora". Richiesta : realizzare il solo main().
appunti Java
pag.167
Codifica:
import java.util.*;
public class cap11_es_04 {
public static void main(String args[])
{
Insieme L=new Insieme(),L1=new Insieme();
for (int i=20; i<30; i++) L.metti(new Integer(i));
// (1)
for (int i=15; i<25; i++) L1.metti(new Integer(i));
// stampare con Iterator
System.out.println("\nL1 ha "+L1.cardin()+" Oggetti:");
Iterator it=L1.iterator();
// (2)
while (it.hasNext()) System.out.print(""+it.next()+",");
// stampare la Collection con toString()
System.out.println("\nL ha "+L.cardin()+" Oggetti:");
System.out.println(L);
// (3)
// unione
Insieme C=L1.unione(L);
System.out.println("\nL1 + L="+C);
// intersezione
C=L1.intersezione(L);
System.out.println("\nL1 * L="+C);
// prima differenza
C=L.differenza(L1);
System.out.println("\nL - L1="+C);
// seconda differenza
C=L1.differenza(L);
System.out.println("\nL1 - L="+C);
}
}
Esecuzione:
l’output della finestra sarà:
L1 ha 10 Oggetti:
20,21,22,23,24,25,26,27,28,29,
L ha 10 Oggetti:
[15, 16, 17, 18, 19, 20, 21, 22, 23, 24]
L1 + L=[15, 16, 17, 18,..,25, 26, 27, 28, 29]
L1 * L=[20, 21, 22, 23, 24]
L – L1=[15, 16, 17, 18, 19]
L1 – L=[25, 26, 27, 28, 29]
Le note (1) (2) (3) evidenziano i metodi utilizzati per costruire l’insieme, stamparlo
usando un iteratore oppure il metodo toString() di ogni Collection.
esempio 5. "Immettere in un insieme i numeri da 1 a 25 e stamparlo, eliminare
tutti i numeri dispari e stamparlo ancora". Richiesta : realizzare il solo main().
Codifica:
import java.util.*;
public class cap11_es_05 {
public static void main(String args[])
{
Insieme L=new Insieme();
for (int i=1; i<26; i++) L.metti(new Integer(i));
// stampa
System.out.println("\nL ha "+L.cardin()+" Oggetti:");
System.out.println(L);
// Eliminazione
appunti Java
pag.168
for (int d=1; d<26; d=d+2) {
Integer A=new Integer(d);
L.togli(A);
}
// stampa
System.out.println("\nL ha "+L.cardin()+" Oggetti:");
System.out.println(L);
}
}
Esecuzione:
l’output della finestra sarà:
L ha 25 Oggetti:
[1,2,3,..,20,21,22,23,24,25]
L ha 12 Oggetti
[2,4,6,8,10,12,14,16,18,20,22,24]
11.4.2 L'interfaccia Collection
Se si guarda la cima della piramide delle Classi java.util si nota che tutte le
strutture date implementano l’interfaccia Collection e quindi possono essere
trattate tutte come Collezioni di oggetti. Tutte le Classi che sono Collection
debbono implementare i seguenti metodi:
Interface
Collection
Classi che imple- Identificatori:
mentano
l’interfaccia Collection
Metodi
Identificatore
Package
java.util
Class AbstractCollection
Si ricorda che Abstract collection è genitrice di
tutte le struture studiate: LinkedList, Vector,
HasSet ecc.
boolean add(Object);
boolean addAll(Collection);
boolean remove(Object);
boolean removeAll(Collection);
void clear();
boolean equals(Collection);
boolean contains(Object);
boolean isEmpty();
int size();
esempio 6: “Dopo aver creato una oggetto Vector di Numeri che contenga
sicuramente duplicati, ottenere rapidamente, usando i metodi di Collection, da
questo un Insieme senza duplicati.”
Richieste:
Nel main() generare un vector con duplicati, stamparlo, trasformarlo in Insieme e
stamparlo di nuovo.
appunti Java
pag.169
Codifica:
import java.util.*;
public class cap11_es_06 {
public static void main(String args[])
{
Vector A=new Vector();
A.add(new Integer(11));
A.add(new Integer(11));
A.add(new Integer(23));
A.add(new Double(3.1));
A.add(new Double(9.9));
A.add(new Double(9.9));
System.out.print("Contiene "+A.size()+" elementi\n Contiene="+A);
Insieme I=new Insieme(A);
System.out.print("\nContiene "+I.size()+" elementi\nContiene="+I+"\n");
}
}
Output:
Contiene 6 elementi
Contiene=[11, 11, 23, 3.1, 9.9, 9.9]
Contiene 4 elementi
Contiene=[11, 23, 9.9, 3.1]
11.5 Pila e Coda
Due ulteriori strutture dati usate in programmazione sono la Coda e la Pila (in
inglese Queue e Stack). Queste due strutture dati sono di norma utilizzate come
“intermediari” tra un processo produttore di Dati e un processo consumatore. In
qualche modo sono analoghe al concetto di Stream o di Filtro visto nel capitolo 9.
Infatti uno stream o un filtro sono entità che mettono in comunicazione due
processi.
Allo stesso modo pila e coda sono utilizzate in programmazione per collegare un
processo produttore e uno consumatore:
Produttore
Consumatore
Coda
X Y Z T
Consumatore
Produttore
X
Y
Z
Pila
Us esempio di processo che usa una Coda è un qualsiasi programma di Stampa;
questo è un processo produttore di caratteri ad alta velocità mentre una
stampante è un processo consumatore di caratteri molto lento. La Coda di
Stampa è l’intermediario che consente di accumulare i caratteri prodotti dal
programma e pronti per la stampa in attesa che il processo di stampa li consumi
alla sua ridotta velocità. In questo caso la coda funge da “filtro sincronizzatore”
dei due processi.
Programma
Coda di stampa
X Y Z T
Stampante
appunti Java
pag.170
Così una Pila è usata per mantenere processi “sospesi” in attesa che altri processi
“abbiano completato il lavoro”. Si immagini un “programma P” che chiama una
“procedure P1” la quale a sua volta chiama una “procedure P2” e così via come in
figura.
Processo P
40
10 istr. 1
20 istr. 2
30 chiama P1
40 istr. 3
50 fine P.
40
Processo P1
get Pila
160 istr. xx
170 chiama P2
180 fine P1
180
Processo P2
put Pila
260 istr. yy
get Pila 270 istr. zz
280 fine P2
put Pila
180
40
40
In questo esempio la Pila degli indirizzi di programma consente di mantenere
l’ordine corretto di esecuzione; il processo principale P viene sospeso (e si
memorizza l’indirizzo di rientro 40) in attesa che venga svolto il processo P1, ma
questo incontra la chiamata di P2, P1 è sospeso (si memorizza l’indirizzo di
rientro 180) in attesa che P2 abbia termine. Quando P2 ha termine il controllo del
processo ritorna a P1 e quando questo termina il controllo ritorna la processo
principale P. Questo lavoro è svolto da una Pila che come in figura accumula gli
indirizzi a cui i processi sospesi e debbono poi essere riavviati.
Dagli esempi mostrati si nota che le strutture dati Pila e Coda hanno in lettura
un comportamente molto simile a Stream, ma molto diverso rispetto a List, Vector
o Insieme. Infatti Pila, Coda, Stream sono strutture nelle quali la lettura è
distruttiva, nel senso che leggendo il dato lo si cancella-consuma. List, Vector e
Set cono contenitori senza lettura distruttiva, infatti consentono di leggere le
informazioni più volte senza eliminarle.
11.5.1 l’ADT Pila
Si potrebbe dare le seguente definizione dell’ADT:
Si dice
Pila una struttura dati sequenziale che consente
l’input e l’output di un dato da uno stesso lato della
sequenza.
appunti Java
pag.171
La figura mostra graficamente uno Stack:
Push Dato
Pop Dato
Dato n
….
Dato 2
Dato 1
In sostanza l’ultimo elemento che entra nella coda è anche il primo elemento che
ne esce. La Pila prende il nome di struttura (LIFO) Last In First Out. Oltre a
questo si deve ricordare che la lettura di un dato è distruttiva.
Queste sono le operazioni che caratterizzano l’ADT Pila:
‰
‰
‰
‰
‰
Costruttore/i
Aggiunta di un oggetto in testa
Lettura distruttiva dell’oggetto di testa
Lettura NON distruttiva dell’oggetto di testa
Operatore per stabilire se La Pila è vuota
In Java esiste la Classe Stack come sottoclasse di Vector con i seguenti metodi
Classe
Costruttori
Stack
Identificatori:
Stack();
Invocazione:
Stack A = new Stack();
Effetti:
Nel primo caso Crea un oggetto Stack identificato
da A vuoto.
void push(Object);
Object pop();
Object peek();
boolean isEmpty();
java.Object
java.util.AbstractCollection
java.util.Vector
java.util.Stack
Metodi
Identificatore
Pckage e
Gerarchia
java.util
esempio 7. “creare e stampare uno Stack di Integer”
Codifica:
import java.util.*;
public class cap11_es_07 {
public static void main(String args[])
{
Stack A=new Stack();
for (int i=0; i<7; i++)
A.push(new Integer(i));
while (!A.isEmpty())
System.out.println(A.pop());
}
}
appunti Java
pag.172
Esecuzione:
l’output della finestra sarà:
6
5
4
3
2
1
0
esempio 8. “usare una Pila per verificare se una sequenza di parentesi tonde
aperte e chiuse è legittima”
Indicazioni: una sequenza corretta di parentesi è quella usata per scrivere
espressioni aritmetiche rispettando l’ordine di priorità. La correttezza è
indipendente dal contenuto. Es. la sequenza (()())() è corretta la sequenza (()()() è
errata perche manca una parentesi chiusa. In ogni caso non basta che il numero
di parentesi aperte sia uguale a quelle chiuse, infatti nell’esempio ())(() le parentesi
pur essendo equilibrate non sono corrette.
Un algoritmo per verificare la correttezza della sequenza, facendo uso di uno
Stack, potrebbe essere il seguente:
inserisci la sequenza di parentesi in uno StringBuffer Sb;
1. crea uno Stack Vuoto St;
2. Fintanto che (Sb non è vuoto <e> non c’è errore)
3.
preleva una parentesi da Sb;
4.
Se è una ‘(‘ Allora inseriscila nello Stack;
5.
Altrimenti Se lo Stack non è Vuoto
Allora preleva la ‘(‘
6.
Altrimenti c’e un Errore
7. Se (Stack è Vuoto <e> non c’e errore) Allora la sequenza è Corretta
8.
Altrimenti la sequenza è Errata
Codifica:
import java.util.*;
public class cap11_es_08 {
public static void main(String args[])
{
StringBuffer Sb=new StringBuffer(args[0]); // input args[0]
Stack St=new Stack();
boolean err=false;
while (Sb.length()!=0 && !err) {
char Ch=Sb.charAt(0);Sb=Sb.deleteCharAt(0);
if (Ch=='(') St.push(new Character(Ch));
else if (!St.isEmpty()) St.pop();
else err=true;
}
if (!err && St.isEmpty()) System.out.println("OK");
else System.out.println("ERRATA");
}
}
appunti Java
pag.173
11.5.2 l’ADT Coda
Si potrebbe dare le seguente definizione di Coda:
Si dice Coda una struttura dati sequenziale che consente
l’input di un dato da un capo e l’output dal capo opposto.
La figura mostra graficamente una Coda:
Put Dato
Get Dato
Dato n
…..
Dato 2 Dato 1
In sostanza il primo elemento che entra nella coda è anche il primo elemento che
ne esce. La Coda prende il nome di struttura (FIFO) First In First Out. Oltre a
questo si deve ricordare che la lettura di un dato è distruttiva.
Operazioni che caratterizzano l’ADT Coda:
‰ Costruttore/i
‰ Aggiunta di un oggetto in coda
‰ Lettura distruttiva dell’oggetto di testa
‰ Lettura NON distruttiva dell’oggetto di testa
‰ Operatore per stabilire se la Coda è vuota
in Java NON esiste una tale struttura ma la si può facilmente progettare come
sottoclasse di LinkedList. Di seguito si fornisce il diagramma della classe Coda
progettata come sottoclasse di LinkedList.
LinkedList
Coda
+ Coda()
+ put(Object) : void
+ get( ) : Object
+ peek( ) : Object
+ isEmpty( ) : boolean
La progettazione della sottoclasse Coda è lasciata come esercizio vedi 11.18
appunti Java
pag.174
esempio 9 :“creare e stampare una CODA di Integer”
Codifica:
import java.util.*;
public class cap11_es_09 {
public static void main(String args[])
{
Coda A=new Coda();
for (int i=0; i<5; i++)
A.put(new Integer(i));
while (!A.isEmpty())
System.out.println(A.get());
}
}
Esecuzione:
l’output della finestra sarà:
0
1
2
3
4
esempio 10. “Date due code di parole ordinate, fonderle in una terza coda
mantenendo l’ordine lessicografico”.
Richieste: nel main() creare le due code ordinate partendo da due Array di
stringhe ordinate usando gli opportuno metodi di coda.
Realizzare poi un metodo statico fusione() con gli opportuni parametri e
richiamarlo.
Codifica:
import java.util.*;
public class cap11_es_10 {
public static Coda fondi(Coda a, Coda b) {
Coda C=new Coda();
while (!a.isEmpty() && !b.isEmpty()) {
String Sa=(String) a.peek();
String Sb=(String) b.peek();
if (Sa.compareTo(Sb)<=0) {C.put(Sa); a.get();}
else { C.put(Sb); b.get();}
}
if (a.isEmpty()) C.addAll(b); else C.addAll(a);
return C;
}
public static void main(String args[])
{
String[] S1={"asino", "cavallo", "somaro"};
String[] S2={"cane", "cavalla", "leone", "liocorno"};
Coda A=new Coda();
for (int i=0; i<S1.length; i++)
A.put(new String(S1[i]));
System.out.println("Coda A="+A);
Coda B=new Coda();
for (int i=0; i<S2.length; i++)
B.put(new String(S2[i]));
System.out.println("Coda B="+B);
Coda C=fondi(A,B);
System.out.println("Coda A+B="+C);
}
}
appunti Java
pag.175
Esecuzione:
l’output della finestra sarà:
Coda A=[asino, cavallo, somaro]
Coda B=[cane, cavalla, leone, liocorno];
Coda A+B=[asino, cane, cavalla, cavallo, leone, liocorno, somaro]
11.6 Alberi Binari
Definizione di Albero Binario
1. Un albero binario è un Nodo vuoto (null) chiamato Radice
2. Oppure è un Nodo Radice dotato di due alberi binari
detti sottoalberi sinistro e destro.
La difinizione è ricorsiva in quanto la seconda riga definisce un albero binario
usando la parola albero binario. Aiutiamoci con alcuni esempi di alberi binari:
Albero (A)
Albero (C)
Albero (B)
null
33
33
null
null
38
20
99
36
null
null
Il caso (A) soddisfa alla definizione (1). I casi (B) (C) alla definizione di riga (2) e
riga (1). Infatti (B) ha una Nodo Radice (def. 2) e due sottoalberi Vuoti (def. 1).
Un albero si caratterizza per il numero di Livelli o Profondità. La profondità di
(A) e (B) è 0 quella di (C) è 2. Si ricorda che il livello della radice è il livello 0 e i
successivi sono i livelli 1, 2,..n.
Le Foglie di un Albero sono i nodi terminali. L’albero (C) ha tre foglie 20, 36, 99.
L’albero (B) ha una sola foglia 33 che coincide con la Radice.
I Rami sono i collegamenti tra Nodi.
11.6.1 Generazione, Visita e Ricerca in un albero Binario
Un albero Binario di norma memorizza nei nodi informazioni con Chiavi non
Ripetute o Univoche.
Si dice Campo Chiave di una informazione il campo che la caratterizza
Univocamente.
Ad esempio se le informazioni da memorizzare in un nodo sono Cognomi (non
ripetuti) di persone e le relative età, il campo Chiave diviene il Cognome, ciascun
nodo conterrà tutta l’informazione ma il campo chiave è il solo Cognome.
Il nodo avrà la seguente struttura:
appunti Java
pag.176
chiave
Cognome
età
Se invece le informazioni da memorizzare sono Cognomi e Nomi che si possono
ripetere, occorre definire un campo chiave diverso; per esempio il Codice Fiscale
che ha la caratteristica di essere univoco per ogni cittadino.
Il nodo avra la seguente struttura:
chiave
Cod.Fiscale
Cognome
Nome
età
Il campo chiave univoco è essenziale sia per generare correttamente l’albero sia
per ricercare le informazioni in modo rapido.
La visitazione di un albero, la lettura dei suoi nodi, può essere eseguita in varie
modalità le due principali sono la visita in Profondità e quella in Ampiezza.
Per esempio la visita in profondità con il metodo SRD (Sinistra Radice Destra) per
l’albero (C) genera la seguente sequenza di lettura:
Albero (C)
33
20, 33, 36, 38, 99
38
20
99
36
Una visita in ampiezza la seguente:
Albero (C)
33
33, 20, 38, 36, 99
38
20
36
99
In seguito si tratterà di un solo tipo di albero binario, in particolare di Alberi
Binari Ordinati ed Equilibrati, generati e scanditi in modalità SRD.
(a) Generare un albero in modalità SRD significa che ogni Nodo ha nel suo
sottoalbero sinistro solo CHIAVI Minori della radice e viceversa nel sottoalbero
Destro.
(b) La lettura di un tale albero produce sempre un elenco con Chiavi Ordinate in
modo Crescente.
(c) Se l’albero è Equilibrato, ovvero la differenza tra la maggiore e la minore delle
sue profondità non è mai superiore ad UNO, il tempo per la ricerca di una Chiave
è molto rapida e non impiega mai un numero di confronti tra nodi superiore
log 2 N dove N è il numero totale dei Nodi.
appunti Java
pag.177
L’albero C soddisfa tette le condizioni (a), (b),(c).
Se si deve eseguire una ricerca su un numero molto alto di elementi e si
confrontano le velocità di ricerca in una Lista Sequenziale rispetto ad un Albero,
si possono notare immediatamente i vantaggi offerti dal secondo:
Nr. Elementi
8
Lista Sequenziale
Numero max.ricerche
8
Albero Binario Bilanciato
Numero max.ricerche
log 2 8 =3
128
128
log 2 128 =7
1.024
1.024
log 2 1024 =10
1.048.576
1.048.576
log 2 1048576 =20
I vantaggi elencati in (b) e (c), ordinamento immediato delle chiavi e ricerca
proporzianale al logaritmo si “pagano” rispetto ad altre strutture dati, in fase di
generazione e mantenimento dell’equilibrio dell’albero.
Nella sintesi finale cercheremo di completare una tabella di confronto tra tutte le
strutture dati trattate introducendo anche il concetto di efficienza e velocità di
una struttura dati rispetto alle operazioni di Inserimento di un Nodo,
Cancellazione di un Nodo, Ricerca di Un Nodo, Ordinamento delle chiavi e
accesso alla struttura.
11.6.2 La Classe TreeSet
L’ADT Albero binario e le sue Operazioni:
‰ Costruttore/i
‰ Aggiunta di un oggetto
‰ Cancellazione di un oggetto
‰ Ricerca dell’esistenza di un Oggetto
‰ Operatore per stabilire se l’albero è vuoto
‰ Modalità di visita completa dei nodi
Esaminiamo la Classe TreeSet. La prima annotazione da fare è che si tratta
ancora di una struttura di tipo Set che ne implementa l’interfaccia come HashSet
infatti come questo ammette solo chiavi non ripetute e quindi è ancora un Set.
Classe
Costruttori
TreeSet
Identificatori:
Invocazione:
Effetti:
Metodi
Identificatore
TreeSet();
TreeSet(Collection c);
TreeSet A = new TreeSet();
Nel primo caso Crea un
identificato da A vuoto.
boolean add(Object);
void clear();
boolean contain(Objects);
boolean isEmpty();
boolean remove(Objects);
Iterator iterator()
oggetto
TreeSet
appunti Java
Pckage e
Gerarchia
pag.178
java.util
java.Object
java.util.AbstractCollection
java.util.TreeSet
esempio 11. “generare l’albero di Integer di figura (C) inserendo i numeri in ordine
casuale e stamparlo con e senza Iterator”
Richiesta: realizzare il solo main().
Codifica:
import java.util.*;
public class cap11_es_11 {
public static void main(String args[])
{
TreeSet A=new TreeSet();
A.add(new Integer(99));
A.add(new Integer(20));
A.add(new Integer(36));
A.add(new Integer(38));
A.add(new Integer(33));
Iterator it=A.iterator();
System.out.println("Albero A="+A);// usa toString()predefinito
while (it.hasNext())
System.out.println(it.next());
// Ciclo con Iterator
}
}
Output:
Albero a=[20, 33, 36, 38, 99]
20
33
36
38
99
Per verificare che la ricerca di un elemento in un albero bilanciato è più rapida
rispetto ad una lista, mentre la generazione è più lenta realizzeremo due
programmi per confrontare le due strutture.
esempio 12. “Si vuole realizzare un programma che confronti la velocità di ricerca
di una chiave in un albero rispetto ad una lista di Double di grandi dimensioni”
Indicazioni: Generare prima un albero e una lista di 70.000 Double con una
funzione random() e testare il tempo di ricerca, generare ancora un albero e una
lista di 700.000 double e testare ancora il tempo di ricerca di una chiave.
Richieste:
Il main() dopo la generazione delle due strutture dati deve chiamare la funzione
contains(Double) con la stessa chiave e rilevare il tempo di inizio e fine della
chiamata stampando il tempo trascorso.
Codifica:
import java.util.*;
public class cap11_es_12 {
public static TreeSet Genera_tree(long N) {
TreeSet A=new TreeSet();
long i=0;
Double NUM=null;
appunti Java
pag.179
double num=0;
while (i<N) {
num=(Math.random()*2*N);
NUM=new Double(num);
if (A.add(NUM)) i++;
}
return A;
}
public static LinkedList Genera_list(long N) {
LinkedList A=new LinkedList();
long i=0;
Double NUM=null;
double num=0;
while (i<N) {
num=(Math.random()*2*N);
NUM=new Double(num);
if (A.add(NUM)) i++;
}
return A;
}
public static void main(String args[])
{
TreeSet A=Genera_tree(70000);
LinkedList L=Genera_list(70000);
// Chiave da cercare
Double Key=new Double((70000/2)*0.33333);
long t=System.currentTimeMillis();
A.contains(Key);
long tf=System.currentTimeMillis();
t=tf-t;
long s=System.currentTimeMillis();
L.contains(Key);
long sf=System.currentTimeMillis();
s=sf-s;
System.out.println("N=70.000\nRicerca Albero ="+t+" Lista="+s);
A=Genera_tree(700000);
L=Genera_list(700000);
// Chiave da cercare
Key=new Double((700000/2)*0.33333);
t=System.currentTimeMillis();
A.contains(Key);
tf=System.currentTimeMillis();
t=tf-t;
s=System.currentTimeMillis();
L.contains(Key);
sf=System.currentTimeMillis();
s=sf-s;
System.out.println("N=700.000\nRicerca Albero ="+t+" Lista="+s);
}
}
Output:
N
70.000
700.000
Albero
0 ms (*)
0 ms (*)
Lista
Circa 40 ms (**)
Circa 360 ms(**)
appunti Java
pag.180
(*) il tempo di ricerca nell’albero è sempre inferiore al millisecondo e non è
possibile rilevare tempi inferiori.
(**) i tempi possono variare a seconda del computer utilizzati e della sua velocità
in ogni caso il rapporto è circa 10/1 nei due casi.
esempio 13. “Si vuole realizzare un programma che confronti la velocità di
generazione di in un albero rispetto ad una lista di Double di grandi dimensioni”
Indicazioni: Generare prima un albero e una lista di 70.000 Double con una
funzione random() e testare il tempo impiegato, generare ancora un albero e una
lista di 700.000 double e testare ancora il tempo di generazione.
Richieste:
Il main() deve rilevare il tempo necessario per la generazione delle due strutture
dati rilevando il tempo di inizio e fine della chiamata e stampando il tempo
trascorso.
Codifica:
import java.util.*;
public class cap11_es_13 {
public static TreeSet Genera_tree(long N) {
TreeSet A=new TreeSet();
long i=0;
Double NUM=null;
double num=0;
while (i<N) {
num=(Math.random()*2*N);
NUM=new Double(num);
if (A.add(NUM)) i++;
}
return A;
}
public static LinkedList Genera_list(long N) {
LinkedList A=new LinkedList();
long i=0;
Double NUM=null;
double num=0;
while (i<N) {
num=(Math.random()*2*N);
NUM=new Double(num);
if (A.add(NUM)) i++;
}
return A;
}
public static void main(String args[])
{
// 70.000
long t=System.currentTimeMillis();
TreeSet A=Genera_tree(70000);
long tf=System.currentTimeMillis();
t=tf-t;
long s=System.currentTimeMillis();
LinkedList L=Genera_list(70000);
long sf=System.currentTimeMillis();
s=sf-s;
System.out.println("N=70.000\nGenerazione Albero ="+t+" Lista="+s);
// 700.000
t=System.currentTimeMillis();
A=Genera_tree(700000);
appunti Java
pag.181
tf=System.currentTimeMillis();
t=tf-t;
s=System.currentTimeMillis();
L=Genera_list(700000);
sf=System.currentTimeMillis();
s=sf-s;
System.out.println("N=700.000\nGenerazione Albero ="+t+" Lista="+s);
}
}
Output:
N
70.000
700.000
Albero
Circa 550 ms
Circa 9200 ms
Lista
Circa 500 ms
Circa 6600 ms
Anche in questo caso si nota che il rapporto tra i tempi di generazione della lista è
circa 10/1, nell’albero tale rapporto tende ad aumentare fino a 20/1 a causa del
maggior tempo necessario per riequilibrare un albero via via crescente.