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