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