Corso JAVA
Lezione n° 11
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Le Strutture Dati
Per struttura dati, in informatica si intende una struttura logica atta a contenere un certo numero
di dati, nella quale è possibile inserire dati, toglierli, cercarli, ecc. Una semplice struttura
dati l’abbiamo gia vista: l'array.
Esso contiene un certo numero di dati che possono essere qualsiasi cosa, da byte ad oggetti
complessi; si possono fare inserzioni, ricerche, cancellazioni e, come abbiamo potuto
vedere anche in uno dei nostri esercizi delle lezioni scorse, volendo può anche essere
ordinato.
Per vedere se in un array grande N è presente un dato X, occorre visitare tutto l'array e fare dei
confronti con ogni elemento dell'array, capite bene che questa è una operazione abbastanza
complessa in termini di n° di operazioni.
Esistono delle strutture dati che questa ricerca la fanno impiegando un solo accesso alla
struttura, ad esempio le tabelle hash.
La scelta delle strutture dati da usare in un programma è una scelta abbastanza complicata,
infatti si sceglie una struttura anziché un'altra in base all'uso che ne deve essere fatto nel
programma, ad esempio è difficile che in un database molto grande in cui si fanno continue
ricerche si usi un array come struttura dati, perché ogni ricerca costerà tanto in termini di
tempo.
2
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Le Strutture Dati (2)
Ovviamente non considerate il caso in cui il numero di elementi da controllare siano poche
decine o qualche centinaia, in quel caso l’incidenza delle strutture dati e minimo;
considerate il caso in cui gli elementi siano in un numero pari a qualche miliardo di
elementi, in cui un singolo accesso in memoria realizzato grazie ad una tabella hash, deve
essere paragonato a qualche miliardo di accessi per fare confronti, in un array tradizionale.
Java mette a disposizione degli utenti tutta una gamma di strutture dati che è in grado di gestire
autonomamente, lasciando all’utente il solo compito di usarle.
Per la scelta della struttura dati più consona al nostro scopo, useremo il buon senso, perché per
sceglierla in modo rigoroso occorrono delle conoscenze molto specifiche, tra cui i dettagli
realizzativi delle strutture, studiate in corsi universitari quali Algoritmi e Strutture Dati , e
Fondamenti di Complessità.
In questa lezione vedremo brevemente quali sono le classi che Java mette a disposizioni per
implementare queste strutture dati e se necessario verranno fatti degli accenni riguardo i
tempi di inserzioni, ricerche, eccetera..., ma sempre evitando di entrare in discussioni
troppo formali che vi risulterebbero forse incomprensibili.
3
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
BitSet
La prima struttura dati che vediamo oggi è il BITSET.
La classe java.util.BitSet implementa vettori di bit che aumentano se necessario. Ogni
componente del BitSet è individuato da un intero non negativo ed è associato ad un valore
booleano, memorizzato in un bit, a cui si può accedere individualmente.
Una volta che abbiano istanziato un oggetto Bitset, viene creato un insieme di bit lungo 2 32-1e
tutti questi bit sono settati a false.
I metodi più comuni che si utilizzano su di un oggetto Bitset sono i seguenti:

void set(int i);

void clear(int i);
che rispettivamente pongono a true o a false il bit di indice i.
 void set(int i, int j);

void clear(int i, int j);
che rispettivamente pongono a true o a false i bit tra l'indice i compreso e l’indice j escluso.
4
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
BitSet (2)
boolean get(int i);
che restituisce il valore del bit di indice i.

int nextSetBit(int i);
che restituisce il valore dell'indice del primo bit true dopo quello di indice i-1.

int length();
che restituisce la grandezza logica del Bitset, ovvero il bit più alto settato a true, più uno.

int size();
che restituisce il numero di bit attuali del Bitset, è il bit più alto che può essere posto a uno
senza ampliare il Bitset.

cardinality()
che restituisce il numero di bit settati a true nel Bitset.

String toString(),
che trasforma la Bitset in una stringa.

5
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Esempio di utilizzo di BitSet
Vediamo adesso un semplice programma che fa uso di un BitSet: il problema del crivello di
Eratostene.
Il crivello di Eratostene è un antico procedimento per il calcolo delle tabelle di numeri primi
fino ad un certo numero n prefissato. Esso deve il nome al matematico Eratostene di
Cirene, che ne fu l'ideatore, ed è a tutt'oggi utilizzato come algoritmo di calcolo dei numeri
primi da molti programmi per computer; pur non essendo un algoritmo straordinariamente
efficiente, infatti, è in compenso piuttosto semplice da tradurre in un qualsiasi linguaggio
di programmazione.
Il procedimento è il seguente:

Si scrivono tutti i naturali a partire da 2 fino n in un elenco detto setaccio.

Poi si cancellano (setacciano) tutti i multipli del primo numero del setaccio (escluso lui
stesso).

Si prosegue così fino ad arrivare in fondo.

I numeri che restano sono i numeri primi minori od uguali a n.
È come se si utilizzassero dei setacci a maglie via via più larghe: il primo lascia passare solo i
multipli di 2, il secondo solo i multipli di 3, e così via.
6
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Esempio di utilizzo di BitSet
/** * Calcola il numero di primi minori di n */
import java.util.Bitset;
int Crivello_Erastotene(int n)
{
BitSet primi = new BitSet(n);
primi.set(2,n);
int i=1;
while (i*2<n) {
i = primi.nextSetBit(i+1);
for(int k=i*2; k<n; k+=i)
primi.clear(k);
}
return primi.cardinality();
}
7
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Vector
Prima di introdurre questa struttura dati vediamo quali sono gli svantaggi degli array, ovvero
della struttura dati che più si avvicina ad un vector, che ci permetterà quindi di apprezzare i
vantaggi dell’utilizzo dei vector.
Il grosso difetto di fondo di un array è che bisogna conoscere a priori il numero massimo di dati
da inserire.
Se, per esempio, dobbiamo gestire un archivio destinato a crescere nel tempo è del tutto
inefficiente poiché potremmo trovarci un giorno a dover correggere direttamente il codice.
Oltre a questo problema l'array ha anche degli altri aspetti negativi relativi all'utilizzo della
memoria di cui non ci occuperemo sia perché esulano dai nostri scopi sia perché nei PC
moderni che dispongono di grosse quantità di memoria non siamo mai portati ad
occuparcene.
Vi basti sapere che l'array è la struttura dati indicata se il numero dei dati è conosciuto a priori
(oppure è conosciuto il loro massimo valore, ad esempio nella gestione dell'anagrafica di
una piccola azienda si può tranquillamente stimare un valore per eccesso) ed è
relativamente basso.
8
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Vector (2)
Per ovviare agli inconvenienti visti prima, la teoria delle strutture dati ci offre varie alternative,
una delle quali è implementata in Java tramite la classe Vector che offre, rispetto all'array, i
seguenti vantaggi:

La sua dimensione può variare dinamicamente

E' possibile inserire e cancellare elementi in qualunque posizione senza lasciare "buchi"
tramite l'invocazione di opportuni metodi

Gli elementi del vector possono essere anche disomogenei.
Per quanto riguarda l'efficienza del vector è doveroso precisare che esso è meno performante
dell'array se vengono effettuate ripetute operazioni di inserimento e cancellazione e che il
suo utilizzo deve essere limitato a insiemi non eccessivamente numerosi, per i quali è
necessario usare strutture dati complesse o, nei casi estremi, una base di dati.
N.B.: I Vector possono contenere solamente oggetti, no tipi fondamentali.
9
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Vector (3)
L'implementazione dei vettori è basata sugli array.
Al momento della creazione di un vettore, viene utilizzato un array di dimensioni
predefinite; successivamente, se la capacità dell'array non è più sufficiente, viene
creato un nuovo array più grande nel quale vengono copiati tutti gli elementi del
vecchio.
I principali costruttori di un vector sono 3:
 Vector (), che costruisce un vettore grande 10, e con possibilità di incremento
pari a zero.
 Vector (int Capacità_Iniziale), costruisce un vettore grande quando
Capacità_Iniziale, con possibilità di Incremento_Capacità pari a zero.
 Vector (int Capacità_Iniziale, int Incremento_Capacità), costruisce un vettore
grande Capacità_Iniziale, con la possibilità di incremento pari a
Incremento_Capacità.
10
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Vector (4)
I metodi più comuni che si utilizzano su di un oggetto Vector sono i seguenti:

int capacity ();
che restituisce la capacita' del vettore.

int size ();
che restituisce il numero di elementi contenuti nel vettore.

Object elementAt (int index);
che restituisce l'elemento di indice index.

void setElementAt (Object obj, int index);
che sostituisce l’oggetto obj all'oggetto della posizione index.

void insertElementAt (Object obj, int index);
che inserisce l’oggetto obj nella posizione index e sposta tutti gli elementi, da index in poi, di
una posizione.

void addElement (Object obj);
che aggiunge l’oggetto obj dopo l'ultimo elemento.
11
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Vector (5)
void removeElementAt (int index);
che rimuove l'oggetto presente nella posizione index e sposta all’indietro di una posizione tutti
gli elementi successivi a quello rimosso.

boolean removeElement (Object obj);
che rimuove l’oggetto obj se presente restituendo true, altrimenti restituisce false.

int indexOf (Object elemento);
che cerca la prima occorrenza del dato elemento nel vettore, e ne restituisce l'indice. Nel caso
in cui l’elemento non sia presente nel vettore restituisce –1.

boolean isEmpty(),
che restituisce true se il vettore è vuoto, false altrimenti.

Object lastElement(),
che restituisce l’ultimo elemento del vettore.

12
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Vector VS. Array
Array
13
Vector
// Nessun pacchetto richiesto
import java.util.Vector;
String[] elenco = new String[5];
Vector elenco = new Vector(5);
for (int i=0 ; i<elenco.length ; i++) {
{ elenco[i] = "Numero "+i; }
for (int i=0 ; i<elenco.capacity() ; i++)
{ elenco.addElement("Numero "+i); }
System.out.println(elenco[2]);
System.out.println(elenco.elementAt(2));
for (int i=0 ; i<elenco.length ; i++)
{ System.out.println(elenco[i]); }
for (int i=0 ; i<elenco.size() ; i++)
{System.out.println(elenco.elementAt(i));}
elenco[2] = "Mario Rossi";
elenco.setElementAt("Mario Rossi",2);
for (int i=elenco.length-1 ; i>2 ; i--)
{ elenco[i] = elenco[i-1]; }
elenco[2] = "Mario Verdi";
elenco.insertElementAt("Mario Verdi",2);
for (int i=3 ; i<elenco.length ; i++)
{ elenco[i-1] = elenco[i]; }
elenco[elenco.length-1] = null;
elenco.removeElementAt(2);
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Tipi primitivi in un vettore
I numeri in Java sappiamo che non sono oggetti, quindi non è possibile inserirli direttamente in
un vettore; Per memorizzare una sequenza di numeri interi, numeri in virgola mobile, o
valori di tipo boolean in un vettore, si devono usare le classi involucro.
Le istanze di queste classi sono usate per incapsulare valori primitivi all'interno di un oggetto.
Questo ci permette di trattare in maniera omogenea tipi primitivi ed oggetti. Gli oggetti
involucro possono naturalmente essere inseriti in un vettore.
Ci sono classi involucro per tutti i tipi primitivi. Normalmente hanno un nome simile al
corrispondente tipo primitivo, ma iniziano con maiuscola:
Bignum, Boolean, Byte, Character, Double, Float, Integer, Long, Number, Short
Ciascuna di queste classi ha (oltre ai soliti metodi toString e equals):

un costruttore con parametro di tipo primitivo (ad esempio Character(char c) o
Integer(int value));

un costruttore con parametro String (ad esempio Integer(String s));

un metodo <tipo>Value che produce il valore di tipo primitivo corrispondente (ad esempio
intValue());
14
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Esempio: La classe Integer
La classe Integer è la classe involucro per numeri interi.
Ha un costruttore che crea un oggetto di tipo Integer a partire da un valore di tipo int:
Integer numero = new Integer(30);
Viceversa, il metodo intValue restituisce il valore di tipo int memorizzato nell'oggetto di tipo Integer:
int n = numero.intValue();
Ecco come si può aggiungere un numero intero a un vettore:
Vector dati = new Vector();
// Istanzio un vettore;
int n = 30;
Integer numero = new Integer(n); //Istanzio un oggetto Integer inserendo all’interno il ns. intero (30)
dati.addElement(numero);
//Aggiungo l’oggetto Integer appena istanziato al Vector dati
Per recuperare il numero, si deve convertire con un cast il riferimento restituito dal metodo elementAt in
modo da ottenere un oggetto di tipo Integer (il metodo restituisce un Object), per poi invocare il metodo
intValue:
Integer numero = (Integer)dati.elementAt(0);
int n = numero.intValue();
15
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
La Pila - Stack
La pila (stack) è una struttura dati astratta molto semplice, ma dal diffuso impiego.
Intuitivamente, una pila è un sequenza di zero o più elementi in cui è possibile
aggiungere o togliere elementi soltanto ad un estremo della sequenza detto cima
(top) della pila.
Politica di accesso:
LIFO (Last In, First Out)
[ultimo elemento inserito, primo elemento rimosso]
Di solito una pila viene rappresentata graficamente come una sequenza verticale di
"caselle" contenenti i suoi elementi. La casella più in alto è la cima (top).
C
<A, B, C>
B
A
pila di tre elementi vista come sequenza e sua rappresentazione grafica
16
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Operazioni su di uno Stack
Le operazioni definite su una pila sono le seguenti:
 Inserimento di un elemento in cima alla pila (normalmente chiamata push)
 Estrazione dell'elemento in cima alla pila (chiamata pop)
 Lettura dell'elemento in cima alla pila, senza eliminarlo (chiamata top o peek)
 Controllo se la pila è vuota oppure no (predicato isEmpty)
 Svuotamento della pila (chiamata clear)
Abbiamo detto che lo Stack è una struttura dati astratta, questo perché è possibile
utilizzare diverse strutture dati, facendole funzionare come una pila.
Abbiamo detto che Stack è una struttura dati astratta, ma capite bene che, per il
motivo precedente Stack è più propriamente una interfaccia!
17
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Interfaccia Stack
Quindi se vogliamo utilizzare una struttura dati secondo i concetti della pila, dobbiamo
implementare con quella struttura dati la seguente interfaccia:
public interface Stack
Specifica dei metodi per la gestione della pila, con riferimento alle classi di Java Collections
API disponibili in java.util.*.
******************* METODI PUBBLICI **********************
Object push( Object x);
//Inserisce x in cima alla pila
Object pop( );
//Estrae dalla cima della pila
Object peek( );
//Restituisce l'elemento in cima
boolean isEmpty( );
//Verifica se la pila e' vuota
void clear( );
//Svuota la pila
****************** ECCEZIONI ******************************
pop e peek generano java.util.EmptyStackException con pila vuota .
18
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
La Coda - Queue
Una coda (queue) è un sequenza di zero o più elementi in cui è possibile aggiungere elementi
soltanto ad un estremo della sequenza, detto fondo (back), ed è possibile toglierli soltanto
dall'altro estremo, detto testa (front).
Politica di accesso:
FIFO (First In, First Out)
[primo elemento inserito, primo elemento rimosso]
Rappresentiamo una coda come una sequenza di caselle contenenti i suoi elementi, nell'ordine
in cui sono stati inseriti. La casella più a sinistra è la testa (front), quella più a destra è il
fondo (back).
<A, B, C, D, E>
A B C D E
front
back
coda di cinque elementi vista come sequenza e sua rappresentazione grafica
19
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Operazioni su di una Queue
Le operazioni definite su una coda sono le seguenti :
 Inserimento di un elemento in fondo alla coda (chiamata enqueue)
 Estrazione dell'elemento in testa alla coda (chiamata dequeue)
 Lettura dell'elemento in testa alla coda, senza eliminarlo (chiamata front)
 Controllo se la coda è vuota oppure no (predicato isEmpty)
 Svuotamento della coda (chiamata clear)
Si noti che pile e code hanno essenzialmente le stesse operazioni,
ma con significato (e quindi nome) diverso!
Ovviamente anche la Coda è un’interfaccia!
20
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Interfaccia Queue
Quindi se vogliamo utilizzare una struttura dati secondo i concetti della pila, dobbiamo
implementare con quella struttura dati la seguente interfaccia:
public interface Queue
Specifica dei metodi per la gestione della coda, per analogia con la classe Stack di Java
Collections API disponibile in java.util.*.
********************* METODI PUBBLICI ***********************
Object enqueue( Object x);
//Inserisce x in fondo alla coda
Object dequeue( );
//Estrae dalla testa della coda
Object front( );
//Restituisce l'elemento in testa
boolean isEmpty( );
//Verifica se la coda e' vuota
void clear( );
//Svuota la coda
******************** ECCEZIONI ******************************
dequeue e front generano EmptyQueueException con coda vuota
21
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Implementazione dell’interfaccia
Stack con Vector - VectorStack
import java.util.Vector;
import java.util.EmptyStackException;
public class VectorStack implements Stack
{
protected Vector theVector;
public VectorStack( )
//Costruttore dello Stack
{
theVector = new Vector( );
}
public Object push( Object x )
{
if ( x == null ) throw new IllegalArgumentException( );
theVector.addElement( x );
return x;
}
22
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
Implementazione dell’interfaccia
Stack con Vector - VectorStack (2)
public Object pop( )
{
if ( isEmpty( ) ) throw new EmptyStackException( );
return theVector.removeElementAt( theVector.size( )-1 );
}
public Object peek( )
{
if ( isEmpty( ) ) throw new EmptyStackException( );
return theVector.get(theVector.size( )-1 );
}
public boolean isEmpty( )
{
return theVector.isEmpty( );
public void clear( )
{
theVector.clear( );
23
}
Istituto Statale di Istruzione Superiore “F. Enriques”
}
Corso di Programmazione in Java – Lezione n° 11
HashTable
Prima di terminare la lezione faremo un accenno anche ad un’altra struttura dati: le tabelle hash.
Una tabella hash è un insieme di coppie < chiave, valore >; tramite la chiave ed una funzione hash è possibile
recuperare il valore associato alla chiave, in particolare si recupera l’indirizzo di memoria dove il valore
ha inizio.
Ovviamente una funzione hash è una funzione univoca che associa ad ogni chiave un solo valore.
Come per i Vector anche in questo caso sia la chiave che il valore associato devono essere oggetti, pertanto
valgono le stesse considerazioni che sono state fatte per i vector.
La principale caratteristica
dell’Hashtable consiste nel fatto che
l’accesso alle informazioni una volta
ottenuta la chiave è molto veloce,
rispetto ad un Vector.
Ovviamente ogni struttura dati ha i
propri vantaggi e i propri svantaggi.
24
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
HashTable (2)
metodi più importanti implementati da Hashtable sono:

hashtable.size()
che restituisce un intero contenente il numero di elementi inseriti in hashtable;

hashtable.isEmpty ():
che restituisce un valore booleano che indica se l’hashtable è vuota o meno (il valore true
segnala che l’hashtable è vuota);

hashtable.get (Object key):
che restituisce l'oggetto a cui è associata la chiave key, se la chiave non viene trovata restituisce
null;

hashtable.put (Object key, Object elem):
che inserisce nell’hashtable l'elemento elem e gli associa la chiave key, se chiave è già stata
inserita viene sostituito l'oggetto a lei associato e viene restituito, altrimenti viene restituito
null;
25
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
HashTable (3)
metodi più importanti implementati da Hashtable sono:
 hashtable.remove (Object key)):
che rimuove da hashtable l'elemento associato a key, se la chiave non esiste viene
restituito null.
Ad un oggetto di tipo Hashtable può venire associato un oggetto di tipo
Enumeration, che permette di scorrere la collezione di oggetti, utilizzando i
metodi:
enumeration.hasMoreElements ():
che restituisce il valore booleano true se ci sono ancora elementi da scorrere;
enumeration.nextElement():
che si sposta all'elemento successivo.
•
•
26
Istituto Statale di Istruzione Superiore “F. Enriques”
Corso di Programmazione in Java – Lezione n° 11
In Conclusione
In questa lezione abbiamo visto alcune strutture dati, ed alcune interfacce che
permettono di modificare il comportamento di tali strutture dati.
Ovviamente ne esistono molte altre: Liste, Liste a doppia concatenazione, Alberi,
ecc. e capite bene che descriverle tutte sarebbe un lavoraccio.
La cosa che vi deve interessare è che tutte, + o -, hanno metodi per inserire, togliere,
recuperare le informazioni contenute e metodi per gestire la struttura stessa.
Ogni volta che si deve scegliere una struttura dati se ne devono valutare i vantaggi e
gli svantaggi e capire quali influiranno maggiormente nella nostra applicazione;
ovviamente è possibile recuperare tutte le informazioni che servono consultando
la documentazione del Java Development Kit, dove sono descritte tutte queste
classi.
Per il resto del nostro corso sarà sufficiente conoscere le strutture dati che abbiamo
visto in questa lezione.
27
Istituto Statale di Istruzione Superiore “F. Enriques”
Scarica

Lezione n° 11