Primo approccio (semplice): Indirizzamento Diretto Assumiamo che: • l’universo delle chiavi sia U = {0, 1, . . . , u − 1} • per ogni coppia di elementi x e y valga key[x] 6= key[y] • Ogni elemento x verrá memorizzato nella locazione T [key[x]] di un array, T [0 . . . u − 1] che inizialmente ha T [i] = N U LL, ∀i Gli algoritmi: S EARCH(T, k) return (T [k]) Complessitá: Θ(1) I NSERT(T, x) T [key[x]] = x D ELETE(T, x) T [key[x]] = N U LL Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 1/34 Problemi con l’approccio appena descritto: • Se l’universo delle possibili chiavi U = {0, . . . , u − 1} é molto grande, potremmo non avere memoria sufficiente per memorizzare l’array T [0 . . . u − 1]. • In piú, anche se avessimo memoria sufficiente, molta risulterebbe essere sprecata se l’insieme S degli elementi che andremmo effettivamente a memorizzare fosse piccolo. Esempi: In una tabella di login di un computer con 100 utenti, dove le username sono di lunghezza 10 e consistono di caratteri minuscoli, vale che |U | = 2610 , mentre |S| = 102 (e quindi |S| ≪ |U |). In una tavola dei simboli usata durante la compilazione di un programma con 1000 variabili di lunghezza fino a 32 caratteri ASCII, avremmo |U | ≈ 9432 , mentre |S| = 103 Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 2/34 Introduzione allo Hashing Per introdurre lo Hashing, rivediamo il metodo di indirizzamento diretto, in cui ogni elemento di chiave k viene memorizzato nella locazione T [k] T 0 1 4 S 2 (chiavi usate) 3 2 3 6 4 5 9 5 U 8 (universo delle chiavi) 1 0 7 6 7 8 9 Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 3/34 Introduzione allo Hashing: indirizzamento indiretto Idea: fissare la dimensione della tabella T ad m = O(|S|)(≪ |U |). In questo caso, peró, l’elemento x ∈ S con chiave key[x] = k non puó essere memorizzato in T [k] (in quanto k potrebbe essere > delle dimensioni della tabella). Useremo allora una funzione h : k ∈ U → h(k) ∈ {0, . . . , m − 1} (funzione di hash), e memorizzeremo l’elelemento x in nella locazione T [h(key[x])] 0 T S (chiavi usate) k1 k3 h(k1 ) h(k2 ) k2 k4 h(k3 ) U (universo delle chiavi) h(k4 ) m−1 Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 4/34 Esempio: |U | = 100, |S| = 10, h : k ∈ U → h(k) = k (mod 10) T 0 1 S (chiavi usate) 36 9 42 13 76 2 3 4 5 6 U (universo delle chiavi) 7 8 9 • Usando la funzione di hash h : k ∈ U → h(k) = k (mod 10) abbiamo ridotto la memoria necessaria da 100 (= |U |) locazioni a 10 locazioni, ma adesso si possono verificare collisioni, ovvero si possono presentare due chiavi k1 6= k2 per cui h(k1 ) = h(k2 ) Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 5/34 Ricapitolando • Se U rappresenta l’universo di tutte le possibili chiavi, ed S l’insieme delle chiavi effettivamente usate, con |S| ≪ |U |, possiamo usare una funzione di hash h : k ∈ U → h(k) ∈ {0, . . . , m − 1}, ( h calcolabile in tempo O(1)), per memorizzare le chiavi in una tabella T con solo m locazioni di memoria (tipicamente m ≪ |U |). Problema: Possono occorrere delle collisioni se si presentano due chiavi k1 6= k2 per cui h(k1 ) = h(k2 ) • Un tale problema (l’occorrenza di collisioni) é inevitabile se |S| > m, pertanto occorre prevedere delle tecniche efficienti per risolvere i conflitti generati dalle collisioni occorse. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 6/34 Metodi per risoluzione delle collisioni Vi sono due metodi per la risoluzione di collisioni: Concatenazione: Si mantiene una lista linkata (esterna alla tabella T ) composta da tutti gli elementi che la funzione di hash indirizza alla stessa locazione dell’array T (una lista per ogni locazione ). Questo metodo é molto semplice da gestire. Usa allocazione dinamica della memoria. Indirizzamento Aperto: Si memorizzano tutte le chiavi nella tabella T . Quando accade una collisione, si usa di nuovo la funzione di hash per trovare una nuova locazione libera per memorizzare l’elemento nella tabella T . Facile da implementare e non usa allocazione dinamica della memoria. Crea peró altri problemi, che vedremo in seguito ... Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 7/34 Risoluzione di collisioni mediante Concatenazione Idea di base: memorizzare in un unica lista linkata tutti gli elementi che la funzione di hash assegna alla stessa locazione di memoria t della tabella T . La locazione t punterá alla testa di una tale lista. T S (chiavi usate) k1 k2 k3 k5 0 k1 k2 k5 k3 k6 k6 k4 U (universo delle chiavi) k4 m−1 Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 8/34 Risoluzione di collisioni mediante Concatenazione Algoritmi: C HAINED -H ASH -S EARCH(T, k) cerca l’elemento con chiave k in T [h(k)] Complessitá: O(lunghezza della lista) C HAINED -H ASH -I NSERT(T, x) inserisci x nella lista T [h(key[x])] Complessitá: O(1) C HAINED -H ASH -D ELETE(T, x) cancella x dalla lista T [h(key[x])] Complessitá: O(1) se la lista é doppiamente linkata, altrimenti occorre trovare il predecessore di x per eseguire la cancellazione. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 9/34 Esempio di hash con concatenazione m = 9, h(k) = k (mod 9), S = {5, 10, 19, 33, 20, 15, 12, 17, 28} T 0 1 28 2 20 3 12 19 10 4 5 6 5 15 33 7 8 17 Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 10/34 Analisi di Hashing con concatenazione Il caso peggiore per la ricerca di un elemento in una tabella hash con concatenazione occorre quando tutte le chiavi di S sono state indirizzate dalla funzione h nella stessa locazione di T . In tal caso, il tempo richiesto da S EARCH(T, k) é Θ(n), dove n = |S|. Il caso medio dipende da come sono state “ben” (ovvero uniformemente) distribuite le n chiavi tra le m locazioni di T . Data una tabella di taglia m, ed n elementi da memorizzarvi, definiamo il fattore di carico come α = n/m. La nostra analisi sará in termini di α. Ai fini dell’analisi, faremo la cosiddetta assunzione di hashing semplice ed uniforme, il che significa che ogni elemento di S ha la stessa probabilitá (= 1/m) di essere indirizzato dalla funzione h in una qualunque locazione di T , indipendentemente dagli altri elementi. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 11/34 Analisi di Hashing con concatenazione: Ricerca senza successo In una tabella hash in cui le collisioni sono risolte mediante concatenazione, la ricerca di un elemento non presente nella tabella (ricerca senza successo) prende tempo Θ(1 + α), in media, sotto l’ipotesi di hashing semplice ed uniforme Prova: • La assunzione di hashing semplice ed uniforme implica che ogni chiave ha eguale probabilitá di essere indirizzata ad ognuna delle m locazioni della tabella. • Il tempo medio per la ricerca senza successo della chiave k é pertanto pari al tempo medio per scorrere una qualsiasi delle m liste. • Il tempo medio per scorrere una qualsiasi delle m liste é pari al numero medio di elementi in essa, che é n/m = α. • Quindi, il tempo medio per la ricerca senza successo della chiave k é Θ(1 + α) (includendo il tempo per il calcolo di h(k)). Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 12/34 Analisi di Hashing con concatenazione: Ricerca con successo In una tabella hash in cui le collisioni sono risolte mediante concatenazione, la ricerca di un elemento presente nella tabella (ricerca con successo) prende tempo Θ(1+α), in media, sotto l’ipotesi di hashing semplice ed uniforme Conseguenze: Se il fattore di carico α = n/m é O(1) (costante), allora il tempo di ricerca medio in una tabella hash, sotto l’ipotesi di hashing semplice ed uniforme, é O(1)! Un analogo risultato vale anche per i tempi di inserzione e di cancellazione in una tabella hash. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 13/34 Osservazioni • Nelle situazioni pratiche in cui vengono eseguite solo le operazioni di S EARCH(T, k), I NSERT(T, x), e D ELETE(T, x), il metodo basato sulle tabelle hash é talmente veloce che, generalmente, esso é il metodo prevalentemente impiegato. • In generale, l’hashing non viene usato nelle situazioni in cui i dati devono essere memorizzati su memorie secondarie, oppure nelle situazioni in cui é importante anche l’ordine relativo degli elementi memorizzati nella struttura dati (ad esempio, se S EARCH(T, k) restituisce il valore NIL, vorremmo poter sapere in ogni caso chi é l’elemento con la chiave piú prossima a k). In tali situazioni si preferiscono strutture dati basate su alberi di ricerca. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 14/34 Sulle funzioni hash Uno dei problemi piú importanti é come scegliere la funzione di hash. Una buona funzione di hash dovrebbe poter soddisfare l’assunzione di hash semplice ed uniforme. In altre parole, sotto l’assunzione che le chiavi vengano estratte da U in maniera indipendente tra di loro ed in accordo ad una distribuzione di probabilitá P , occorre che X k h(k)=j P (k) = 1 m j = 0, . . . , m − 1 dove P (k) é la probabilitá di estrarre la chiave k e m la taglia della tabella hash usata. Sfortunatamente, P non é in generale nota... In pratica, vengono usate delle euristiche per scegliere funzioni hash che funzionino “bene”. Assumeremo da ora in poi che l’universo delle chiavi U sia l’insieme dei numeri naturali, (o che le chiavi possano essere codificate in tal modo). Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 15/34 Esempi di scelta di funzioni hash Metodo della divisione: h(k) = k (mod m) Esempio: Se m = 52 e k = 35 allora h(k) = 27. Regole di “buon senso”: • In generale, vorremmo che la funzione h “disperda” le chiavi k nella maniera piú uniforme possibile all’interno della tabella T . • Spesso le sono simili o hanno valori piú o meno uguali. Non vogliamo che queste regolaritá vengano preservate dalla funzione di hash h. Quindi, in generale, h(k) e h(k + ǫ) devono differire di “molto” tra di loro, anche se ǫ é piccolo. • Evitare quindi potenze di 2 per valori di m. Altrimenti non tutti i bit di k verranno usati da h (ad es., se m = 2p allora h(k) dipende solo dai p bits meno significativi di k, e quindi chiavi che avessero tali p bits uguali verrebbero indirizzate alla stessa locazione di memoria). Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 16/34 Esempi di scelta di funzioni hash • Per le stesse ragioni enunciate prima, occorre evitare per i valori di m le potenze di 10, se usiamo numeri decimali per i valori chiave • Se m = 2p − 1 e k é una stringa di caratteri interpretata in base 2p , allora chiavi che sono permutazione delle stesse cifre verrebbero indirizzate alla stessa locazione di memoria (ad es., se m = 15, le chiavi 51 e 15, interpretate in base 24 , colliderebbero sotto la funzione hash k (mod m)). Quindi, evitare anche questi valori di m. • Buoni valori di m sono numeri primi non troppo vicini a potenze di 2. Ad es., se n = 2000, una buona scelta é h(k) = k (mod 491) (491 é un numero primo e 512 é una potenza di 2). Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 17/34 Esempi di scelta di funzioni hash Metodo della moltiplicazione: Dati 0 < A < 1, h(k) = ⌊m(kA (mod 1))⌋ • La scelta di m non é critica per questo metodo. Si puó quindi scegliere m potenza di 2, il che rende la funzione facile da calcolare. • Un buon valore di A é considerato essere A = 0.61803... Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 18/34 C’é un problema... Osservazione: Qualunque sia la scelta della funzione di hash, un “avversario” potrebbe sempre scegliere un insieme di chiavi che verrebbero memorizzate nella stessa locazione della tabella T . Ció renderebbe il metodo basato sull’hash completamente inefficiente, in quanto avremmo una unica lista lunga quanti sono gli elementi memorizzati. Se potessimo scegliere “casualmente” una funzione di hash da un insieme di funzioni opportunamente progettato, indipendentemente dalle chiavi da memorizzare, potremmo ottenere buone performances, qualunque siano le chiavi che in seguito si presenteranno per essere memorizzate (perché l’ “avversario” non potrebbe conoscere a priori la funzione di hash scelta a caso da noi, un pó come si procedeva nella scelta del pivot nel QuickSort...) . Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 19/34 Hash Universale Definizione: Sia U l’universo delle chiavi e sia H una collezione finita di funzioni di hash, dove ciascuna funzione h ∈ H, h : U → {0, . . . , m − 1}, mappa le chiavi di U in {0, . . . , m − 1}. L’insieme H é detto universale se ∀x, y ∈ U (x 6= y), vale che |{h ∈ H : h(x) = h(y)}| = |H| m • In altri termini, la probabilitá di avere una collisione per due valori delle chiavi x e y, data una funzione di hash scelta a caso nell’insieme H, é pari a 1/m. • Se scegliessemo la funzione di hash in un insieme universale H, allora il problema dell’avversario descritto nella slide precedente scomparirebbe. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 20/34 Costruzione di insiemi universali di funzioni di hash • Scegli la taglia m della tabella come un numero primo • Decomponi la chiake k in r + 1 bytes, in modo tale che k = k0 k1 . . . kr , ed inoltre il valore di ogni ki é strettamente inferiore a m. • Sia a = a0 a1 . . . ar una sequenza di numero scelti a caso, dove ai ∈ {0, . . . , m − 1}. Ci sono mr+1 possibili tali sequenze a. Pr • Definiamo una funzione di hash ha come ha = i=0 ai ki (mod m), e l’insieme H = ∪a {ha } Teorema: L’insieme H é un insieme universale di funzioni di hash. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 21/34 Risoluzione di conflitti mediante Indirizzamento Aperto Indirizzamento Aperto: Tutti gli elementi sono memorizzati direttamente nella tabella hash (non vi sono puntatori ad elementi esterni, come nell’hash per concatenazione). Pertanto, in ogni locazione della tabella o vi é un elemento, oppure vi é NULL (denotante locazione vuota). La tabella hash é quindi statica (la memoria assegnata agli elementi non cresce al loro numero). Pertanto, il fattore di carico α = n/m ≤ 1. La questione delicata é come trovare una locazione in cui inserire nuovi elementi (nell’hashing per concatenazione si creava semplicemente una nuova locazione di memoria e dalla tabella hash c’era un puntatore a tale nuova locazione, che a sua volta puntava ad un (eventuale) vecchio elemento giá inserito). Nella tecnica dell’inidirizzamento aperto, per trovare una locazione per un nuovo elemento da inserire, si esaminerá la tabella hash stessa, in un qualche modo sistematico, fin quando non si troverá una locazione libera. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 22/34 Piú precisamente... La funzione di hash é ora definita come h : U × {0, 1, . . . , m − 1} → {0, 1, . . . , m − 1} Per ogni valore chiave k, si calcolano uno dopo l’altro i valori h(k, 0), h(k, 1), . . . , fin quando non si trova un valore h(k, i) della tabella non ancora occupato da un elemento, e la chiave k viene memorizzata in T tale locazione libera. H ASH -I NSERT(T, k) i=0 repeat j = h(k, i) if T [j] = N U LL then T [j] = k return j i=i+1 until i = m error “hash table overflow” U x k y z h(k, 0) h(k, 1) h(k, 2) h(k, j) % (La tabella é piena) Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 23/34 Ricerca in una tabella hash con indirizzamento aperto Nella ricerca di una chiave k, si procede come se si volesse inserire k, usando la funzione hash per testare le locazioni in cui potrebbe essere stata inserita precedentemente k, fin quando o la si trova o si termina la ricerca senza successo (restituendo il valore N IL). H ASH -S EARCH(T, k) i=0 repeat j = h(k, i) if T [j] = k then return j else i = i + 1 until T [j] = N U LL oppure i = m return N U LL T x k y z j k h(k, 0) h(k, 1) h(k, 2) h(k, i) return j Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 24/34 Ricerca in una tabella hash con indirizzamento aperto Nella ricerca di una chiave k, si procede come se si volesse inserire k, usando la funzione hash per testare le locazioni in cui potrebbe essere stata inserita precedentemente k, fin quando o la si trova o si termina la ricerca senza successo (restituendo il valore N IL). H ASH -S EARCH(T, k) i←0 repeat j = h(k, i) if T [j] = k then return j else i = i + 1 until T [j] = N U LL oppure i = m return N IL T x k y z j h(k, 0) h(k, 1) h(k, 2) h(k, i) return NIL Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 25/34 Cancellazione in una tabella hash con indirizzamento aperto Non possiamo semplicemente cancellare un elemento in una tabella T sostituendo la sua presenza con un N IL, ció potrebbe disturbare successive ricerche. Supponiamo infatti di voler cercare la chiave k, che si trova nella locazione j = h(k, i). T T x k y z j k h(k, 0) h(k, 1) x k y h(k, 1) h(k, 2) h(k, 2) h(k, i) h(k, 0) j k h(k, i) In un tempo successivo, la chiave memorizzata nella locazione h(k, 2) (che al tempo dell’inserimento di k era occupata, perció k era stata inserita in h(k, i)) é stata cancellata. Quando poi andiamo a cercare k, nella sequenza delle h(k, 0), h(k, 1), . . . , troviamo N IL in h(k, 2) e concluderemmo che k non c’é nella tabella, commettendo un errore! Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 26/34 Come rimediare al problema? • Potremmo inserire dei puntatori, ma la tecnica dell’indirizzamento aperto vuole proprio fare a meno dei puntatori... • Potremmo effettuare le cancellazioni non inserendo N IL al posto dell’elemento cancellato, ma bensí inserendo uno speciale simbolo DEL. • In questo modo, la funzione H ASH -S EARCH(T, k) continuerebbe ad esaminare la tabella T ogni qualvolta incontra o una locazione occupata da una chiave diversa da k, oppure se incontra una locazione con il simbolo DEL, fin quando o non trova k (e restituisce l’indice di tale locazione), oppure trova un N IL (e correttamente segnala la non presenza di k nella tabella) • Ció risolve il problema, ma fa degradare le performances della tabella, in quanto le sue locazioni sarebbero ora occupate sia da elementi che da vecchie cancellazioni. In generale, quando si richiede di effettuare anche operazioni di cancellazioni, si preferisce usare la tecnica della concatenazione vista prima. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 27/34 Come calcolare in pratica i valori h(k, 0), h(k, 1), . . .? Ci sono tre tecniche che vengono generalmente usate per calcolare le sequenze di valori h(k, 0), h(k, 1), . . . usate nell’hashing mediante indirizzamento aperto: 1. Probing lineare 2. Probing quadratico 3. Doppio hashing Queste tecniche garantiscono che la sequenza dei valori h(k, 0), h(k, 1), . . . , h(k, m − 1) sia una permutazione degli interi 0, 1, . . . , m − 1. Ció é necessario, affinché tutte le locazioni della tabella T vengano prima o poi prese in considerazione nella fase di inserzione o rdi ricerca di un elemento. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 28/34 Probing Lineare Data una funzione h′ : U → {0, 1, . . . , m − 1}, il probing lineare usa la funzione di hash h(k, i) = (h′ (k) + i) (mod m) per i = 0, 1, . . . , m − 1. Quindi, data la chiave k, la prima locazione della tabella T che vieme considerata é T [h′ (k)], poi viene considerata T [h′ (k) + 1], poi viene considerata T [h′ (k) + 2], e cosí via..., fin quando non viene trovata una locazione libera per inserire la chiave k Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 29/34 Esempio: Indirizzamento aperto e probing lineare Assumiamo di avere una tabella di 16 elementi, e vogliamo memorizzarvi le chiavi k ∈ U , k = 1, 2, 17, 35, 3, 10, 27, 28, utilizzando la funzione hash h(k, i) = (h′ (k) + i) mod 16, dove h′ (k) = k mod 16, i = 0, 1, . . . , e risolvendo le collisioni mediante la tecnica dell’indirizzamento aperto. h(1, 0) = (h′ (1) + 0) (mod 16) = 1 (mod 16) = 1 0 1 1 2 2 17 3 35 4 3 5 h(2, 0) = (h′ (2) + 0) (mod 16) = 2 (mod 16) = 2 6 h(17, 0) = (h′ (17) + 0) (mod 16) = 1 (mod 16) = 1, collisione! 7 h(17, 1) = (h′ (17) + 1) (mod 16) = 2 (mod 16) = 2, collisione! 8 h(17, 2) = (h′ (17) + 2) (mod 16) = 3 (mod 16) = 3 h(35, 0) = (h′ (35) + 0) (mod 16) = 3 (mod 16) = 3, collisione! h(35, 1) = (h′ (35) + 1) (mod 16) = 4 (mod 16) = 4 9 10 10 11 h(3, 0) = (h′ (3) + 0) (mod 16) = 3 (mod 16) = 3, collisione! 12 h(3, 1) = (h′ (3) + 1) (mod 16) = 4 (mod 16) = 4, collisione! 13 h(3, 2) = (h′ (3) + 2) (mod 16) = 5 (mod 16) = 5 14 h(10, 0) = (h′ (10) + 0) (mod 16) = 10 (mod 16) = 10 15 Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 30/34 Vantaggi e svantaggi del probing lineare • É facile da implementare • Tuttavia, due chiavi k, k ′ che collidono per i = 0, ovvero per cui h(k, 0) = h(k ′ , 0) tenderanno a collidere anche per successivi valori di i, allungando i tempi di ricerca della locazione libera in cui memorizzarli. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 31/34 Probing quadratico Il probing quadratico usa funzioni hash della forma h(k, i) = (h′ (k) + c1 i + c2 i2 ) (mod m) dove h′ é una funzione di hash ausiliaria, c1 , c2 6= 0 sono costanti ausiliarie, e i = 0, 1, . . . , m − 1, m é la taglia della tabella hash. Quindi, data la chiave k, la prima locazione della tabella T che vieme considerata é T [h′ (k)], poi viene considerata T [h′ (k) + c1 + c2 ], poi viene considerata T [h′ (k) + 2c1 + 4c2 ], poi viene considerata T [h′ (k) + 3c1 + 9c2 ], e cosí via..., fin quando non viene trovata una locazione libera per inserire la chiave k. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 32/34 Doppio hashing Il metodo del doppio hashing viene considerato uno dei migliori metodi di hash disponibili, in quanto le permutazioni generate da h(k, 0), h(k, 1), . . . , h(k, m − 1) “somigliano” maggiormente alle permutazioni “casuali”, che distribuiscono unformemente le chiavi nella tabella hash. Tale metodo usa una funzione di hash della forma h(k, i) = (h1 (k) + i · h2 (k)) (mod m) dove h1 e h2 sono due funzioni di hash ausiliarie. Quindi, la prima locazione in cui si tenta di inserire la chiave k é T [h1 (k)], poi si tenta di inserire in T [h1 (k) + h2 (k)], poi in T [h1 (k) + 2 · h2 (k)], e cosí via ..., fin quando o non si trova una locazione libera, oppure non si realizza che la tabella T é completamente piena, nel qual caso si ritorna un messaggio di errore. Nota: Affinché si possano esaminare tutte le locazioni della tabella T , occorre che h2 (k) debba essere relativamente primo con m. Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 33/34 Esercizio: Usando il metodo del doppio hashing, memorizzare le chiavi 10, 18, 34 in una tabella hash di taglia m = 8, usando le funzioni h1 (k) = k (mod 8), h2 (k) = 1 + (k (mod 6)) Universitá degli Studi di Salerno – Corso di Introduzione agli Algoritmi e Strutture Dati – Prof. Ugo Vaccaro – Anno Acc. 2014/15 – p. 34/34