ADT Mappa
Una mappa è un contenitore di elementi del tipo (k,v) dove k è la chiave e
v è il suo corrispondente valore
●
ogni elemento (k,v) viene detto entrata (entry) della mappa
●
entrate multiple con la stessa chiave non sono permesse
Le chiavi (il mezzo per accedere agli elementi) hanno lo scopo
di rendere efficiente la ricerca
Strutture Dati
ADT Mappa
Metodi fondamentali
–
get(k): se la mappa M ha una entrata con chiave k, restituisce il
valore associato alla chiave; atrimenti, restituisce null
–
put(k, v): se la chiave k non è già in M inserisce l'entrata (k, v)
nella mappa M e restituisce null, altrimenti rimpiazza con v il
valore esistente e restituisce il vecchio valore associato a k
remove(k): se la mappa M ha una entrata con chiave k, la
rimuove da M e restituisce il valore ad essa associato; altrimenti,
restituisce null
–
Strutture Dati
ADT Mappa
Metodi fondamentali
–
size(), isEmpty()
–
keys(): restituisce una collezione iterabile delle chiavi in M
(keys().iterator restituisce un iteratore sulle chiavi)
values(): restituisce una collezione iterabile dei valori in M
(values().iterator() restituisce un iteratore sui valori)
entries(): restituisce una collezione iterabile delle entrate chiavevalore di M (entries().iterator() restituisce un iteratore
sulle entrate)
–
–
Strutture Dati
ADT Mappa
Interfaccia
public interface Map<K, V> {
public int size();
public boolean isEmpty();
public V put(K key, V value) throws InvalidKeyException;
public V get(K key) throws InvalidKeyException;
public V remove(K key) throws InvalidKeyException;
public Iterable<K> keys();
public Iterable<V> values();
public Iterable<Entry<K,V>> entries();
}
Strutture Dati
ADT Mappa
Implementazione mediante lista doppiamente conc.
L'implementazione mediante lista non ordinata è molto semplice:
9 v1
6 v2
5 v3
entrate
Strutture Dati
8 v4
ADT Mappa
Implementazione mediante lista doppiamente conc.
lista S
9 v1
6 v2
5 v3
8 v4
iteratore sulle
posizioni in S
Algorithm get(k):
foreach position p in S.positions()) do
if p.element().getKey() = k
then return p.element().getValue()
return null {non esiste alcuna entrata con chiave k}
Strutture Dati
ADT Mappa
Implementazione mediante lista doppiamente conc.
Algorithm put(k,v):
foreach position p in S.positions() do
if p.element().getKey() = k then
t := p.element().getValue()
S.set(p, (k,v))
return t
{restituisce il vecchio valore}
S.addLast((k,v))
n := n + 1 {incrementa la variabile che indica il numero di entrate}
return null
{non esisteva alcuna entrata con chiave k}
Strutture Dati
ADT Mappa
Implementazione mediante lista doppiamente conc.
Algorithm remove(k):
foreach position p in S.positions() do
if p.element().getkey() = k then
t := p.element().getvalue()
S.remove(p)
n := n – 1
{decrementa il numero di entrate}
return t
{restituisce il valore rimosso}
return null
{non esiste alcuna entrata con chiave k}
Strutture Dati
ADT Mappa
Implementazione mediante lista doppiamente conc.
Complessità:
● put richiede il tempo necessario per verificare che la chiave non sia già
nella mappa (O(n) nel caso pessimo) (poiché la lista non è ordinata
possiamo inserire la nuova entrata all'inizio o alla fine della lista)
● get e remove richiedono tempo O(n) (nel caso pessimo la chiave non
viene trovata) si scandisce l'intera lista
L'implementazione mediante lista non ordinata è conveniente solo per
mappe di piccola taglia o mappe in cui le operazioni più frequenti sono le
put, mentre ricerche e rimozioni sono più rare (per esempio, record delle
login a una workstation)
Strutture Dati
ADT Mappa
Implementazione mediante tabella hash
Uno dei modi più efficienti per implementare una Mappa è di usare
una tabella hash
Una tabella hash per un dato tipo di chiave è composta da
–
un array (chiamato bucket array)
–
una funzione hash h
Strutture Dati
Tabella hash
Bucket array
Bucket array (ciascuna cella è un contenitore di coppie (k,v))
0
1
(k1,d) (k2,a)
Strutture Dati
2
3
4
(k3,f) (k4,p)
5
6
7
8
(k5,w) (k6,g)
9
10
(k7,s)
Tabella hash
Bucket array (semplice soluzione)
Le chiavi sono tutte distinte e scelte nell'intervallo [0, n − 1]:
–
abbiamo bisogno di un bucket array di dimensione n
–
l'entrata con chiave k verrà inserita nella k-esima cella dell'array
–
tutte le operazioni richiedono tempo O(1)
Bucket array (ciascuna cella è un contenitore di coppie (k,v))
0
(0,d)
Strutture Dati
1
(1,a)
2
3
4
(3,f)
(4,p)
5
6
7
8
(7,w) (8,g)
9
10
(10,s)
Tabella hash
Bucket array
Esempio: tabella hash per memorizzare informazioni (telefono,
indirizzo, n. esami superati, ...) per ogni studente iscritto
Bucket array
...
...
possiamo usare la matricola come chiave:
lo studente con matricola xxxxxx viene memorizzato nella cella
xxxxxx del bucket array
ci serve un array di dimensione 1.000.000!
Strutture Dati
Tabella hash
Bucket array
Problemi:
1) lo spazio sarà sempre proporzionale a n (numero di tutte le chiavi
possibili)
➢ se n è molto più grande del numero di chiavi realmente
presenti si ha un notevole spreco di memoria
numero chiavi: n = 1.000.000
numero studenti iscritti: N = 500
2) non sempre le chiavi sono interi nell'intervallo [0, n − 1]
Strutture Dati
Tabella hash
Funzione hash
Una funzione hash h associa chiavi di tipo arbitrario ad interi in un
fissato intervallo [0, N − 1]
Esempio:
h(x) = x mod N
è una semplice funzione hash per chiavi intere
L'intero h(x) viene chiamato valore hash della chiave x
Strutture Dati
Tabella hash
Funzione hash
In generale una funzione hash opera in due fasi:
hash code: trasforma oggetti arbitrari (stringhe, posizioni, ...) in interi
(risolve il problema 2 della diapositiva n. 14)
compressione: trasforma interi in un intervallo arbitrariamente grande in
interi dell'intervallo [0, N − 1]
(risolve il problema 1 della diapositiva n. 14)
Strutture Dati
Tabella hash
Funzione hash
oggetti arbitrari
n

hash code
... -2 -1 0 1 2 ...
funzione di compressione
0 1 2 ... N - 1
Strutture Dati
Tabella hash
Funzione hash
Quando implementiamo una mappa con una tabella hash, lo scopo è di
memorizzare l'entrata (k, v) all'indice i = h(k) dell'array
Esempio
●
Usiamo una tabella hash per una
mappa che memorizza entrate
(matricola, studente)
●
La tabella usa un array di taglia
N = 1,000 con funzione hash
h(k) = le ultime tre cifre di k
Strutture Dati
025-6612001
481-1715002
151-2250004
…
(k , v)
0 ∅
1
2
3 ∅
4
997 ∅
998
999 ∅
100-599998
Funzione hash
Collisione
Cosa succede se dobbiamo inserire una entrata con chiave
567-1160004 ?
0 ∅
1
2
3 ∅
4
025-6612001
481-1715002
151-2250004
…
997 ∅
998
999 ∅
100-599998
h(x) = le ultime tre cifre di x
Strutture Dati
Funzione hash
Collisione
Cosa succede se dobbiamo inserire una entrata con chiave
567-1160004 ?
0 ∅
1
2
3 ∅
4
025-6612001
481-1715002
151-2250004
Si verifica una collisione:
due chiavi distinte hanno lo stesso
valore hash, e quindi sono associate
alla stessa cella
…
997 ∅
998
999 ∅
k1 = 567-1160004
100-599998
h(x) = le ultime tre cifre di x
Strutture Dati
k2 = 151-2250004
h(k1 ) = h(k2 )
Funzione hash
Trattamento delle collisioni
Separate Chaining
Ciascuna cella A[i] ospita una lista Li contenente tutte le entrate (k,v)
tali che h(k) = i
0 ∅
1
2
3 ∅
4
025-6612001
481-1715002
151-2250004
…
997 ∅
998
999 ∅
Strutture Dati
100-599998
567-1160004
Funzione hash
Trattamento delle collisioni
Linear Probing
Quando si tenta di inserire una entrata (k,v) in una cella A[i] già
occupata (cioè tale che h(k) = i), si prova con la cella
A[(i+1)] mod N, se anche questa è occupata si passa alla cella
A[(i+2)] mod N e così via fino a trovarne una vuota
0 ∅
1
2
3 ∅
4
5
6 ∅
…
Strutture Dati
025-6612001
481-1715002
151-2250004
588-5550005
567-1160004
Funzione hash
Trattamento delle collisioni
Linear Probing
Quando si tenta di inserire una entrata (k,v) in una cella A[i] già
occupata (cioè tale che h(k) = i), si prova con la cella
A[(i+1)] mod N, se anche questa è occupata si passa alla cella
A[(i+2)] mod N e così via fino a trovarne una vuota
0 ∅
1
2
3 ∅
4
5
6 ∅
…
Strutture Dati
025-6612001
481-1715002
151-2250004
588-5550005
567-1160004
Funzione hash
Trattamento delle collisioni
Linear Probing
Quando si tenta di inserire una entrata (k,v) in una cella A[i] già
occupata (cioè tale che h(k) = i), si prova con la cella
A[(i+1)] mod N, se anche questa è occupata si passa alla cella
A[(i+2)] mod N e così via fino a trovarne una vuota
0 ∅
1
2
3 ∅
4
5
6
…
Strutture Dati
025-6612001
481-1715002
151-2250004
588-5550005
567-1160004