appunti java – Capitolo 11
pag.
1
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 – Capitolo 11
Accesso
§ sequenziale
§ indicizzato
pag.
Dimensionamento Organizzazione
Persistenza
§ statico
§ ordinata
§ volatile
§ dinamico
§ disordinata
§ permanente
§ con duplicati
§ senza duplicati
2
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 sequenziale e
caratteri o byte
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 – Capitolo 11
§
§
§
pag.
3
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:
q Aggiunta di un oggetto in testa
q Aggiunta di un oggetto in coda
q Aggiunta di un oggetto in una posizione qualsiasi
q Cancellazione di un oggetto noto
q Cancellazione di un oggetto di posizione nota
q Ricerca dell'esistenza di un oggetto noto
q Determinazione della posizione di un oggetto noto
Concatenazione:
q Aggiungere una Lista in coda a una Lista data
Altri metodi:
q Operatore per leggere l'oggetto di testa
q Operatore per leggere l'oggetto in coda
q Operatore per leggere un oggetto di posizione nota
q Operatore per stabilire se la lista è Vuota o meno
q
appunti java – Capitolo 11
q
q
pag.
4
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.
Package e
Gerarchia
java.util
java.Object
java.util.AbstractCollection
java.util.LinkedList
Si riportano costruttori e metodi:
Classe
LinkedList
Costruttori
LinkedList();
Metodi
Invocazione:
LinkedList L = new LinkedList();
Effetti:
Crea un oggetto Lista identificato da L vuoto.
void add(Object);
Appende l’elemento alla fine della lista
void add(Index, Object);
Appende l’elemento nella posizione
specificata
void addFirst(Object);
Inserisce l’oggetto intesta
boolean remove(Object);
Rimuove l’oggetto specificato
Object remove(Index);
Rimuove l’elemento specificato in
Index e restituisce l’oggetto Lista
modificato
void clear();
Rimuove tutti gli elementi della lista
boolean contains(Object);
Verifica se l’oggetto è presente nella
lista
int indexOf(Object);
Restituisce
l’indice
della
prima
occorrenza dell’elemento specificato,
-1 se non è presente
boolean addAll(Collection); Appende alla fine della lista gli
elementi della Collection
Object getFirst();
Restituisce il I° elemento della lista
Object getLast();
Restituisce l’ultimo elemento della
lista
Object get(Index);
Restituisce l’ elemento della posizione
specificata
Verifica se la lista è vuota
boolean isEmpty();
int size();
Restituisce la dimensione della lista
Iterator iterator();
Crea un oggetto Iterator per scandire
la lista
ListIterator listIterator(): Crea un oggetto ListIterator per
scandire la lista in entrambi i versi
appunti java – Capitolo 11
pag.
5
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.
Package e
Gerarchia
java.util
java.Object
java.util.Iterator
Interface
Iterator
Creazione di un Si invoca un metodo che genera un Iteratore a partire da una Classe di
iterator
tipo "struttura dati" come ad esempio LinkedList, Vector, Set, …
Metodi
Esempio
LinkedList L=new LinkedList(); ....
Iterator iter = L.iterator();
Restituisce True se si hanno ancora elementi
boolean hasNext();
Restituisce il successivo elemento della lista
Object next();
Esempio:
LinkedList L= ......;
......;
Iterator iter=L.iterator(); // crea un iteratore
while (iter.hasNext()) {
// iter non è finito ?
Object o = iter.next(); // leggi l'oggetto
.....;
}
Effetti: Scorre l'iteratore ottenuto dalla lista dal
primo all'ultimo oggetto.
Interface
ListIterator
Creazione di un Si invoca un metodo che genera un Iteratore a partire da una Classe di
ListIterator
tipo "struttura dati" come ad esempio LinkedList, Vector, Set, …
Metodi
Esempio
LinkedList L=new LinkedList(); ....
ListIterator iter = L.listIterator();
boolean hasNext();
Restituisce True se si hanno ancora elementi
Object next();
Restituisce il successivo elemento della lista
boolean hasPrevious(); Restituisce True se esiste un elementoche lo
precede
Object previous();
Restituisce l’ elemento precedente della lista
Esempio:
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()
Effetti: Scorre l'iteratore ottenuto dalla lista
dal primo all'ultimo oggetto e viceversa.
appunti java – Capitolo 11
pag.
6
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 in un ciclo.
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)
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 successivo.
Sostituendo alle ultime righe le seguenti (5', 6, 7, 8, 9)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.");
// (5')
// (6')
// (7')
// (8')
// (9')
appunti java – Capitolo 11
pag.
7
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 – Capitolo 11
pag.
8
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 posizione
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:
una tale struttura "ibrida" esiste ed è presente in java.util sotto forma di Classe.
Di seguito si riportano la gerarchia e la tabella contenente costruttori e metodi
principali della classe Vector.
Package e
Gerarchia
Classe
Costruttori
java.util
java.Object
java.util.AbstractCollection
java.util.Vector
Vector
Vector();
Vector(Collection c);
Vector(int initCapacity);
Alloca l’oggetto predisponendo
una capacità pari a 10. Qualora
sia necessario la capacità viene
incrementata ancora di 10.
Alloca l’oggetto predisponendo
una capacità pari al valore
specificato in initCapacity.
Qualora
sia
necessario
la
capacità viene aumentata sempre
dello stesso valore specificato in
Vector(int
initCapacity,
int initCapacity.
capacityIncr);
Alloca l’oggetto predisponendo
una capacità pari al valore
specificato in initCapacity.
Qualora
sia
necessario
la
capacità viene aumentata del
valore specificato in CapacityIncr.
Invocazione:
Vector V = new Vector();
Effetti:
Crea un oggetto Vector identificato da V vuoto.
appunti java – Capitolo 11
Metodi
N.B.: la
descrizione è
consultabile
nella tabella
della classe
LinkedList
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);
pag.
9
Appende l’oggetto alla fine
Rimuove la
dell’oggetto
I°
occorrenza
Appende la Collection alla fine
Object elementAt(Index);
Object get(Index);
boolean isEmpty();
int size();
int capacity()
Se si analizza le struttura di tipo Vector e la si classifica usando gli attributi
discussi si ottiene la seguente tabella comparativa:
Vector
Accesso
sequenziale e
indicizzato
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:
appunti java – Capitolo 11
pag.
10
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.
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 ]
q insieme vuoto [ Φ ]
q sottoinsieme [ A Í B]
q rappresentazione dell'insieme [ { a,b,c,d} ]
Ci attendiamo poi che si possano eseguire le operazioni di:
q Unione
[ È ]
q Intersezione
[ Ç ]
q Differenza.
q
Ragionando sull'Abstract Data Type Insieme si dovrebbe disporre di :
Costruttore/i
Inserzione, Cancellazione e Ricerca:
q Aggiunta di un oggetto all'insieme (solo se non è già presente)
q Cancellazione di un oggetto noto
q Ricerca dell'esistenza di un oggetto noto (appartenenza)
q Sottoinsieme
Operazioni:
q Unione
q Intersezione
q Differenza
q
appunti java – Capitolo 11
pag.
11
Altri metodi:
q
q
q
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).
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.
appunti java – Capitolo 11
pag.
12
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)
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.
appunti java – Capitolo 11
pag.
13
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.
Di seguito si riportano la gerarchia e la tabella contenente costruttori e metodi
principali della classe HashSet.
Package e
Gerarchia
java.util
java.Object
java.util.AbstractCollection
java.util.HeshSet
Classe
HashSet
Costruttori
HashSet();
HashSet(Collection c);
Metodi
Invocazione: HashSet A = new HashSet();
HashSet B = new HashSet(V);
Effetti:Il primo caso crea un oggetto HashSet identificato da A vuoto.
Il secondo crea un oggetto HashSet da un Vector V 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();
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
appunti java – Capitolo 11
pag.
14
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.10.
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 da 15 a 24 e stamparli. Ottenere Unione, Intersezione e le due differenze e
stamparle ancora". Richiesta : realizzare il solo main().
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);
appunti java – Capitolo 11
pag.
15
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
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
appunti java – Capitolo 11
pag.
16
[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,
appartengono al package e implementano i metodi sottoelencati:
Interface Collection
Classi che imple- Identificatori:
mentano
l’interfaccia Collection
Metodi
Package
Identificatori:
Class AbstractCollection
Si ricorda che Abstract collection
è genitrice di tutte le strutture
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();
java.util
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 – Capitolo 11
pag.
17
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 – Capitolo 11
pag.
18
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
Processo P1
get Pila
10 istr. 1
20 istr. 2
30 chiama P1
40 istr. 3
50 fine P.
160 istr. xx
170 chiama P2
180 fine P1
180
Processo P2
260 istr. yy
get Pila 270 istr. zz
280 fine P2
put Pila
40
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 – Capitolo 11
pag.
19
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:
q
q
q
q
q
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
Stack
Identificatori:
Stack();
Invocazione:
Stack A = new Stack();
Effetti:
Crea un oggetto Stack identificato da A vuoto.
Metodi
Identificatore
Pckage e
Gerarchia
java.util
void push(Object);
Object pop();
Object peek();
boolean isEmpty();
java.Object
java.util.AbstractCollection
java.util.Vector
java.util.Stack
Classe
Costruttori
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 – Capitolo 11
pag.
20
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 – Capitolo 11
pag.
21
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:
q Costruttore/i
q Aggiunta di un oggetto in coda
q Lettura distruttiva dell’oggetto di testa
q Lettura NON distruttiva dell’oggetto di testa
q 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 – Capitolo 11
pag.
22
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 – Capitolo 11
pag.
23
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 – Capitolo 11
pag.
24
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 – Capitolo 11
pag.
25
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
proporzionale 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:
q Costruttore/i
q Aggiunta di un oggetto
q Cancellazione di un oggetto
q Ricerca dell’esistenza di un Oggetto
q Operatore per stabilire se l’albero è vuoto
q 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();
Nell’esempio crea un oggetto TreeSet identificato
da A vuoto.
boolean add(Object);
void clear();
boolean contain(Objects);
boolean isEmpty();
boolean remove(Objects);
Iterator iterator()
appunti java – Capitolo 11
Pckage e
Gerarchia
java.util
pag.
26
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 – Capitolo 11
pag.
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(**)
27
appunti java – Capitolo 11
pag.
28
(*) 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 – Capitolo 11
pag.
29
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.
appunti java- Capitolo 11
pag.
30
11.7 Sintesi sugli attributi delle strutture dati
Non rimane infine che costruire una tabella di sintesi che metta a confronto tutte le strutture Dati studiate. La tabella sarà più dettagliata di
quella originaria in quanto si cercherà di dare conto anche dei diversi tempi di inserimento/generazione di una struttura, cancellazione e ricerca
di un dato.
Struttura
Dati
Array
(semi stat.)
Mod.
Accesso
Sequen.
Indiciz.
Dim.nto
Organizzazione
Statico(*)
Lista
(LinkedList)
Sequen.
Dinamico
Vector
Sequen.
Indiciz.
Dinamico
Albero
(TreeSet)
Insieme
(HashSet)
File Testo
Iterator
Con Key
Iterator
Con Key
Sequen.
Dinamico
Dinamico
Disordinata
Ordinata(u)
Duplicati
Disordinata
Ordinata(u)
Duplicati
Disordinata
Ordinata(u)
Duplicati
Ordinata
Senza Dup.
Senza Dup.
Dinamico
Disodinata
Perm.te
File Dati
Sequen.
Indiciz.
Dinamico
Disordinata
Ordinata(u)
Duplicati
Perm.te
Sequen.
Dinamico
Volatile
Sequen.
Dinamico
Ord. Di ins.
Duplicati
Ord. Di ins.
Duplicati
Strutture
Filtro
Coda
Pila
(Stack)
Persistenza
Volatile
Lettura Dato
Volatile
Ripetibile
Veloce (10-8) [°]
Volatile
Ripetibile
Veloce (10-8) [°]
Volatile
Ripetibile
Veloce (10-8) [°]
Ripetibile
Veloce (10-8) [°]
Ripetibile
Lenta (10-3) [°]
Ripetibile
Lenta (10-3) [°]
Volatile
Volatile
Ripetibile
Veloce (10-8) [°]
Distruttiva
Veloce (10-8) [°]
Distruttiva
Veloce (10-8) [°]
Inserimento
Cancell. Dato
Dato
In coda
Ovunq. ma lento
In mezzo (Lento)
Sovrascr.
In coda
Ovunq. rapido
In mezzo
Ricerca
un Dato
O(n/2)
O(lgn) (u)
Generaz.
struttura
Rapida
O(n/2)
Rapida
O(n/2)
O(lgn) (u)
Rapida
In coda
In mezzo
Sovrascr.
In Ordine
Ovunq. V. media O(lgn)
V. media
Senza Ordine
Ovunq. rapido
V. media
In Coda
Ricop. lentissima O(n/2)
Molto Lenta
In Coda
Sovrascr.
Ricop. lentissima O(n/2)
O(lgn) (u)
Molto Lenta
In Coda
In Testa rapida
Rapida
In Testa
In Testa rapida
Rapida
Ovunq. rapido
O(lgn)
(*) l’array in java è allocato dinamicamente ma non può crescere in modo dinamico.
(u) significa che per ottenere l’ordinamento o una ricerca di complessità logaritmica è necessario un algoritmo dell’utente.
appunti java- Capitolo 11
pag.
31
[°] la velocità di accesso 10 -8 indica una tipica velocità di lettura in RAM, 10 -3 una lettura su supporto esterno.
·
·
Rapido, Semi Rapido, Lento, Molto Lento, Lentissimo, Si riferiscono rispettivamente: Rapido=velocità di semp lice scrittura in Ram, Semi
Rapido=scrittura in RAM con risistemazione di parte della struttura, Lenta=riscrittura in RAM di buona parte della struttura, Molto
Lento=scrittura di un dato su dispositivo magnetico in sequenza, Lentissima = Riscrittura completa della struttura su disp. esterno.
O(n/2) e O(lgn) significano rispettivamente che gli algoritmi di ricerca di un dato sono proporzionale a n/2 o al logn.
appunti java- Capitolo 11
pag. 32
11.E – Esercizi
Esercizi su Liste
1. Si desidera progettare un programma che "Acquisisca il suo input da un testo contenuto in
una Stringa e inserisca ciascuna parola del testo in una Lista”.
Indicazioni: Fare uso si uno StreamTokenizer per isolare le Parole del testo.
Richieste:
·
·
Progettare una classe opportuna ScanPar_01 che ottenga le funzionalità desiderate;
Testarne il funzionamento con un main() opportuno.
2. Generalizzare la classe progettata nell’esercizio 1) e chiamarla ScanPar_02 in modo che “a
partire da un Testo contenuto indifferentemente sia in una Stringa che in un File di testo,
ottenga sempre la lista delle parole”. La classe dovrebbe fornire anche la funzionalità
aggiuntiva restituire un array di parole.
3. Generalizzare la classe progettata nell’esercizio 1) e chiamarla ScanPar_03 in modo che
“restituisca le parole anche in ordine Lessicografico sia in un array che in una lista ”.
4. Generalizzare la classe progettata nell’esercizio 1) e chiamarla ScanPar_04 e progettarne
una seconda di nome Nodo in modo che “restituisca un elenco Ordinato di Oggetti Nodo
costituito delle parole contenute nel testo con a fianco il numero di volte con cui ogni parola
compare”.
5. Generalizzare la classe progettata nell’esercizio 1) e chiamarla ScanPar_05 inserendo un
metodo che “consenta di stampare l’indice analitico di una parola assegnata del testo
scandito”. Per indice analitico si intende la restituzione della parola con a fianco i numeri
delle righe sulle quali la parola si trova nel testo.
Es. se sul problema 5 si cerca la parola testo si deve ottenere:
testo : 3, 5, 6
Esercizi sui Vector
6. Progettare una classe Misto che “Generi un Vector casuale sia per la distribuzione che per i
valori delle componenti che devono essere Integer o Double in un range identico ” . Es. se si
chiedono 5 elementi questi saranno casualmente Integer o Double e avranno valori Integer
nell’intervallo [Min-Max] e Double nello stesso intervallo [Min.00000 – Max]
7. Generalizzare la classe del precedente esercizio 6) implementando un metodo che
restituisca il Vector anche ordinato in modo crescente.
8. Generalizzare la classe del precedente esercizio 7) implementando “ un metodo di ricerca
Dicotomina di un valore Integer o Double assegnato ”. Richieste: Se il numero è presente ne
restituisce la posizione se no la posizione corretta di inserimento.
9. Dopo aver generato un Vector Ordinato di grandi dimensioni [5.000, 10.000, 20.000
numeri] usando le classi precedenti, si desidera realizzare un main() che confronti i tempi
a) di Ordinamento del vettore, b) di Ricerca Dicotomica della classe( 8) c) di ricerca interna
alla Classe Vector.
Esercizi su Insiemi
10. “Progettare e implementare la sottoclasse Insieme della classe HashTree come indicato nel
capitolo”.
appunti java- Capitolo 11
pag. 33
11. Per testare la classe 10) “Generare un Insieme con i numeri da 1 a 25 e stamparlo,
eliminare tutti i numeri multipli di 2 e 3 e stamparlo ancora "
12. Per testare la classe 10) "Generare due insiemi con le parole dei due testi distinti contenuti
in due stringhe, stamparli, ottenere Unione, Intersezione e le due Differenze e stamparle
ancora"
13. Costrure una classe che "che restituisca in un Insieme i numeri primi compresi tra 2 e N,
con N scelto dall’utente. Si utilizzi l’algoritmo di Eratostene. "
14. Costrure una classe che "che restituisca in un Insieme i numeri primi compresi tra 2 e N,
con N scelto dall’utente. Si utilizzi un algoritmo DIVERSO da quello di Eratostene "
15. Costruire un programma “che confronti la velocità di esecuzione dei due algoritmi
precedenti”
Esercizi su collezioni
16. “Progettare un programma main con il minimo numero di righe di codice che consenta di
ottenere un Insieme e mostrarlo, partendo da una Lista di 20 elementi Integer generarati
casualmente nel range [0..9] e quindi con elementi ripetuti ”.
17. “Progettare un programma main con il minimo numero di righe di codice che consenta di
ottenere un Vector, partendo da un Insieme di elementi Integer generarati casualmente nel
range [0..9]. Stampare il Vector ottenuto dopo aver aggiunto due Integer uguali a 9 ”.
Esercizi su Pile, Code e Liste
18. “Progettare una classe Coda come illustrato nel relativo paragrafo del testo ”.
19. “Progettare un programma main che utilizzi uno Stack per ottenere una lista Decrescente da
una lista Crescente di Numeri”.
20. “Progettare una Classe che ordini in modo Crescente una LinkedList qualsiasi di Double e
restituisca la Lista Ordinata.” Vincoli: Si utilizzi l’algoritmo di Fusione descritto di
seguito.
Primo : se si dispone di due liste ordinate in modo crescente è sempre possibile
fonderle per ottenere una lista ordinata. Procedura L3=fondi(L1, L2);
L1=[2, 6, 8]
L1=[2, 6, 8]
L1=[6, 8]
L1=[8]
L1=[8]
L1=[8]
L1=[]
L2=[1,6, 7]
L2=[6, 7]
L2=[6, 7]
L2=[6, 7]
L2=[7]
L2=[ ]
L2=[ ]
L3=[ ]
L3=[1 ]
L3=[1, 2]
L3=[1, 2, 6 ]
L3=[1, 2, 6, 6 ]
L3=[1, 2, 6, 6, 7 ]
L3=[1, 2, 6, 6, 7,8 ]
Secondo : Se una lista ha più di un elemento e non è ordinata dividerla in due liste L1,L2
=dimezza(L);
Terzo : applica alla lista L il seguente algoritmo ricorsivo che restituisce la lista ordinata
LF=MergeSort(L);
public static LinkedList MergeSort(LinkedList L) {
if (L.size()>1) {
LinkedList K[]=dimezza(L);
K[0]=MergeSort(K[0]);
K[1]=MergeSort(K[1]);
L=fondi(K[0],K[1]);
}
return L;
appunti java- Capitolo 11
pag. 34
}
Richieste :
a. costruire una classe Fusione che contenga il metodo statico MergeSort() e i metodi
statici privati fondi() e dimezza();
b. realizzare il main di prova che richiami un metodo statico genera(N)
che restituisce una lista casuale di Double e quindi applichi
MergeSort().
21. “Progettare una Classe che ordini in modo Crescente un Vector qualsiasi di Double
restituendo il risultato in un Vector .” Vincoli: Si utilizzi l’algoritmo Bubble Sort.
22. “Progettare una main che confronti la velocita dei due ordinamenti precedenti realizzati su
List e Vector casuali di grandi dimensioni 5.000 o 10.000 elementi”.
23. “Progettare una Classe che consenta di disporre di un metodo per Valutare un’espressione
aritmetica qualsiasi che abbia gli operatori +,-,*,/,^ usando uno Stack”. Indicazioni:
L’algoritmo da realizzare è il seguente:
si supponga di avere la seguente stringa di caratteri che rapresenta un’espressione aritmetica:
3+4/2-7*2^2 = 3+4/2-7*4 = 3 + 2 – 28 = 23.
Si noterà che per VALUTARE l’espressione si devono eseguire le singole operazioni rispettando le
priorità degli operatori. L’operatore con massima priorità è la potenza ^ seguono a pari merito * e
/ e quindi la priorità più bassa riguarda gli operatori + e - . Come realizzare il rispetto delle
priorità degli operatori ?
Se rispettare la priorità significa rimandare le operazioni a priorità inferiore allora si può utilizzare
lo Stak il cui funzionamento simila un rinvio temporaneo.
Ecco le operazioni logiche da svolgere con l’uso di due Stak, uno di char e il
secondo di numeri.
)ESP)ressione
3+4/2-7*2^2
+4/2-7*2^2
4/2-7*2^2
/2-7*2^2
2-7*2^2
-7*2^2
7*2^2
*2^2
2^2
^2
2
StakNum
[ empty ]
[3]
StackOp
[ empty ]
[ empty ]
Ris
0
0
[3]
[+]
0
[+]
[/, +]
[/, +]
[-, +]
0
0
0
0
[-, +]
[*, -, +]
[*, -, +]
[^, *, -, +]
[^, *, -, +]
[*, -, +]
0
0
0
0
0
0
[4, 3
[4, 3
[2, 4,
[2, 3
]
]
3]
]
[7, 2, 3 ]
[7, 2, 3 ]
[2, 7, 2, 3 ]
[2, 7, 2, 3 ]
[2, 2, 7, 2, 3 ]
[4, 7, 2, 3 ]
Algoritmo
Inizializza Oggetti
Fintanto che (ESP Non è vuota)
(A) Leggi un operando e mettilo
nello SN
(B) Leggi un Op.re confrontalo
con l’Op.reSO Se (SO vuoto o
Op.reSO<=Op.re) impila l’Op.re,
altrimenti
Fintanto
che
(Op.reSO
>
Op.re)
estrai
l’Op.reSO, estrai Due operandi
dallo SN esegui l’operazione e
impila il risultato nello SN.
Poi impila l’Op.re Letto
(A)
(B)
(A)
(B) – ha prior. Inferiore a /
quindi esegue e impila
(A)
(B)
(A)
(B)
(A)
(C)ESP è vuota:
Fintanto che (SO non à vuoto)
preleva Op.reSO, preleva 2 Num
da SN, esegui operazione e
Impila in SN (2^2)=4
appunti java- Capitolo 11
[28, 2, 3 ]
[-26, 3 ]
[-23 ]
[]
pag. 35
[
[
[
[
-, +]
+]
]
]
0
0
0
-23
(C)
7*4=28
(C)
2-28=-26
(C)
3-26=-23
(D) SO è vuoto metti in SN in
Ris
24. “Generalizzare la classe precedente introducendo nell’espressione aritmetica anche le
parentesi tonde che indicano una ulteriore priorità”. Indicazioni: ripetere l’algoritmo
precedente con la seguente variante opportunamente collocata; immettere le parentesi
aperte sempre in SO, se si in contra una parentesi chiusa eseguire tutte le operazioni
impilate fino alla parentesi aperta.
Esercizi su Alberi
25. “Costruire un programma che acquisisca da tastiera (o da file di testo usando streamTokenizer) parole in
ordine casuale, le inserisca in un Albero e stampi l’albero per verificarne l’ordinamento Lessicografico ”.
Richiesta: realizzare il tutto anche in un solo main().
26. “Realizzare un programma come quello dell’esercizio 3) utilizzando un Albero”.
27. “Realizzare un programma come quello dell’esercizio 7) utilizzando un Albero”.