Hash table
























Hash table

HASHTB08.svg
Una piccola rubrica telefonica come esempio di hash table.
Classe Struttura dati
Struttura dati Tabella Hash
Caso peggiore spazialmente O(n)
Ottimale Spesso

.mw-parser-output .avviso .mbox-text-div>div,.mw-parser-output .avviso .mbox-text-full-div>div{font-size:90%}.mw-parser-output .avviso .mbox-image div{width:52px}.mw-parser-output .avviso .mbox-text-full-div .hide-when-compact{display:block}





In informatica un'hash table, in italiano tabella hash, è una struttura dati usata per mettere in corrispondenza una data chiave con un dato valore. Viene usata per l'implementazione di strutture dati astratte associative come Map o Set.


L'hash table è molto utilizzata nei metodi di ricerca nominati Hashing. L'hashing è un'estensione della ricerca indicizzata da chiavi che gestisce problemi di ricerca nei quali le chiavi di ricerca non presentano queste proprietà. Una ricerca basata su hashing è completamente diversa da una basata su confronti: invece di muoversi nella struttura data in funzione dell'esito dei confronti tra chiavi, si cerca di accedere agli elementi nella tabella in modo diretto tramite operazioni aritmetiche che trasformano le chiavi in indirizzi della tabella.


Esistono vari tipi di algoritmi di hashing. Per quanto affermato, in una tabella di hashing ben dimensionata il costo medio di ricerca di ogni elemento è indipendente dal numero di elementi. L'hashing è un problema classico dell'informatica; molti algoritmi sono stati proposti, studiati a fondo e impiegati in pratica. Due metodi molto diffusi sono l'hashing statico e l'hashing estendibile e lineare, metodi utilizzati anche dai programmi DBMS.




Indice






  • 1 Descrizione


    • 1.1 Funzionamento e implementazione


    • 1.2 Gestione delle collisioni




  • 2 Open Hash


    • 2.1 Tecniche comunemente utilizzate di ispezione


      • 2.1.1 Ispezione lineare


        • 2.1.1.1 Ispezione quadratica


        • 2.1.1.2 Doppio Hashing








  • 3 Hashing statico


    • 3.1 Esempio di funzione hash




  • 4 Hashing estendibile


  • 5 Hashing lineare


    • 5.1 Esempio




  • 6 Bilanciamento spazio/tempo


  • 7 Analisi del costo di scansione


  • 8 Bibliografia


  • 9 Voci correlate


  • 10 Altri progetti


  • 11 Collegamenti esterni





Descrizione |



Funzionamento e implementazione |


Il primo passo per realizzare algoritmi di ricerca tramite hashing è quello di determinare la funzione di hash: il dato da indicizzare viene trasformato da un'apposita funzione di hash in un intero compreso tra 0{displaystyle 0}{displaystyle 0} ed m−1{displaystyle m-1}m-1 che viene utilizzato come indice in un array di lunghezza m. Supponendo che U{displaystyle U}U sia l'universo delle chiavi e T[0...m−1]{displaystyle T[0...m-1]}T[0...m-1] una tabella hash, una funzione hash h, stabilisce una corrispondenza tra U{displaystyle U}U e le posizioni nella tabella hash, quindi:


h:U→{0,1,...,m−1}{displaystyle h:Urightarrow {0,1,...,m-1}}h:Urightarrow {0,1,...,m-1}


Idealmente, chiavi diverse dovrebbero essere trasformate in indirizzi differenti, ma poiché non esiste la funzione di hash perfetta, ovvero totalmente iniettiva, è possibile che due o più chiavi diverse siano convertite nello stesso indirizzo.
Il caso in cui la funzione hash applicata a due chiavi diverse genera un medesimo indirizzo viene chiamato collisione e può essere gestito in vari modi. La scelta di una buona funzione di hash è indispensabile per ridurre al minimo le collisioni e garantire prestazioni sempre ottimali. Il risultato migliore si ha con funzioni pseudo-casuali che distribuiscono i dati in input in modo uniforme.


Molto spesso però, una buona funzione di hash può non bastare: infatti le prestazioni di una hash table sono fortemente legate anche al cosiddetto fattore di carico (load factor) calcolato come Celle libere/Elementi presenti e che dice quanta probabilità ha un nuovo elemento di collidere con uno già presente nella tabella. Questa probabilità, in realtà, è più alta di quanto si possa pensare, come dimostra il paradosso del compleanno. È bene dunque mantenere il load factor il più basso possibile (di solito un valore di 0.75 è quello ottimale) per ridurre al minimo il numero di collisioni. Ciò può essere fatto, ad esempio, ridimensionando l'array ogni volta che si supera il load factor desiderato.



Gestione delle collisioni |


Di seguito vengono riportati i metodi più diffusi per la gestione delle collisioni.



  • Open Hash


  • Hash con concatenazione (o con lista di trabocco): per ogni cella della tabella di hash si fa corrispondere invece di un elemento, una Lista (solitamente una lista concatenata). In questo modo un elemento che collide viene aggiunto alla lista corrispondente all'indice ottenuto.



Open Hash |


Nell'indirizzamento aperto, tutti gli elementi sono memorizzati nella tavola hash stessa; ovvero ogni cella della tavola contiene un elemento dell'insieme dinamico o la costante NIL. Quando cerchiamo un elemento, esaminiamo sistematicamente le celle della tavola finché non troviamo l'elemento desiderato o finché non ci accorgiamo che l'elemento non si trova nella tavola.


Diversamente dal concatenamento, non ci sono liste né elementi memorizzati all'esterno della tavola. Quindi nell'indirizzamento aperto, la tavola hash può "riempirsi" al punto tale che non possono essere effettuati altri inserimenti.


Il vantaggio dell'indirizzamento aperto sta nel fatto che esclude completamente i puntatori, calcoliamo la sequenza delle celle da esaminare (ispezione).


Un concetto importante al riguardo è il cosiddetto hashing uniforme. Rappresenta l'hashing ideale, ovvero ogni cella della tabella ha la stessa probabilità di contenere un dato elemento.


Esistono diverse tecniche di ispezione, le tre tecniche più comunemente utilizzate sono: ispezione lineare, ispezione quadratica e doppio hashing. Tuttavia, nessuna di queste tecniche soddisfa l'ipotesi di hashing uniforme, in quanto nessuna di esse è in grado di generare più di m2{displaystyle m^{2}}{displaystyle m^{2}} sequenze di ispezione differenti (anziché m!{displaystyle m!}{displaystyle m!}, come richiede l'hashing uniforme).



Tecniche comunemente utilizzate di ispezione |



Ispezione lineare |


h(k,i)=(h1(k)+i)(modm){displaystyle h(k,i)=(h^{1}(k)+i){pmod {m}}}{displaystyle h(k,i)=(h^{1}(k)+i){pmod {m}}}


Quando si incontra una collisione non si fa altro che utilizzare l'indice successivo a quello che collide, sino a che non si trovi una casella libera.



Ispezione quadratica |

h(k,i)=(h1(k)+c1i+c2i2)(modm){displaystyle h(k,i)=(h^{1}(k)+c_{1}i+c_{2}i^{2}){pmod {m}}}{displaystyle h(k,i)=(h^{1}(k)+c_{1}i+c_{2}i^{2}){pmod {m}}}


Quando si incontra una collisione non si fa altro che utilizzare l'indice che collide elevato al quadrato con normalizzazione rispetto alla grandezza della tabella dell'indice ottenuto, sino a che non si trovi una casella libera.



Doppio Hashing |

h(k,i)=(h1(k)+ih2(k))(modm){displaystyle h(k,i)=(h_{1}(k)+ih_{2}(k)){pmod {m}}}{displaystyle h(k,i)=(h_{1}(k)+ih_{2}(k)){pmod {m}}} dove, per esempio possiamo porre h1(k)=k(modm){displaystyle h_{1}(k)=k{pmod {m}}}{displaystyle h_{1}(k)=k{pmod {m}}} e h2(k)=1+(k(modm1)){displaystyle h_{2}(k)=1+(k{pmod {m^{1}}})}{displaystyle h_{2}(k)=1+(k{pmod {m^{1}}})}.


Se facendo l'hash di una chiave si incontra una collisione, allora si somma all'indice ottenuto il risultato di una nuova funzione hash (generalmente diversa dalla prima e che ha come parametro l'indice ottenuto precedentemente), e si tenta l'inserimento nel nuovo indice così ottenuto, riapplicando la seconda funzione sino a che non si trovi una casella libera.



Hashing statico |


Nell'hashing statico si utilizza il concetto di bucket, che è l'insieme di pagine contenenti le etichette dei record di dati.
Se una pagina bucket primaria è piena, viene creata una pagina di overflow. Per cercare l'etichetta corrispondente alla chiave k{displaystyle k}k (M={displaystyle M=}M= numero di bucket) si utilizza la seguente formula di hash
h(k)=bucket{displaystyle h(k)={mbox{bucket}}}h(k)={mbox{bucket}} a cui appartiene l'etichetta.
La funzione di hash h{displaystyle h}h agisce sul campo della chiave di ricerca del record r{displaystyle r}r e deve distribuire i valori su 0,...,M−1{displaystyle 0,...,M-1}0,...,M-1 (M{displaystyle M}M bucket). Le prestazioni della ricerca dipendono molto dalla funzione h{displaystyle h}h.


Le pagine di bucket primarie, nell'hashing statico, sono allocate consecutivamente. Questo può portare ad avere il problema di lunghe catene di overflow che degradano le prestazioni dato che non abbiamo pagine contigue.



Esempio di funzione hash |


h(k)=(a∗k+b)modM{displaystyle h(k)=(a*k+b){bmod {M}}}h(k)=(a*k+b){bmod  {M}} (con a{displaystyle a}a e b{displaystyle b}b costanti)



Hashing estendibile |


Sostanzialmente è un miglioramento dell'hashing statico, perché oltre ad inserire bucket di overflow permette di raddoppiare il numero di bucket e di riorganizzare le etichette.
L'unico problema sta nella riorganizzazione delle etichette, dato che questa operazione è costosa in termini di tempo. Esistono due soluzioni. La prima (hashing estendibile) gestisce una directory di puntatori ai bucket primari, la seconda (hashing lineare) permette di risolvere il problema senza l'utilizzo delle directory ma con una famiglia di funzioni hash.
Nell'hashing estendibile si parla di profondità di directory e si intende il numero minimo di bit che permette di rappresentare il numero di elementi contenuti nella directory.



Hashing lineare |


L'hashing lineare, come accennato nel paragrafo precedente, permette di risolvere il problema delle lunghe catene di overflow senza l'utilizzo delle directory.
L'idea di base è quella di utilizzare una famiglia di funzioni hash h0,h1,...hn{displaystyle h_{0},h_{1},...h_{n}}h_{0},h_{1},...h_{n} dove hi{displaystyle h_{i}}h_{i} ha un range che è la metà di quello di hi+1{displaystyle h_{i}+1}h_{i}+1. Questo vuol dire che il range di h1{displaystyle h_{1}}h_{1} è 21N{displaystyle 2^{1}N}2^{1}N



Esempio |


Se N=32{displaystyle N=32}N=32 allora il numero minimo di bit per la rappresentazione del numero è 5. Quindi h0=hmod20N{displaystyle h_{0}=h{bmod {2}}^{0}N}h_{0}=h{bmod  2}^{0}N cioè h0=hmod32{displaystyle h_{0}=h{bmod {3}}2}h_{0}=h{bmod  3}2
La prossima funzione h2{displaystyle h_{2}}h_{2} avrà range 5+1{displaystyle 5+1}5+1 e potrà rappresentare i bucket da 0 a 63. La funzione di hash sarà come segue h1=hmod21∗32{displaystyle h_{1}=h{bmod {2}}^{1}*32}h_{1}=h{bmod  2}^{1}*32



Bilanciamento spazio/tempo |


In base alla grandezza dell'array in cui avviene la ricerca e la memorizzazione delle informazioni, si ha una tabella della complessità computazionale del tempo necessario alla ricerca stessa. A maggiore spazio disponibile, corrisponderà un minor tempo necessario nel caso peggiore.



















Spazio
Tempo
m=1{displaystyle m=1}m=1
O(n){displaystyle O(n)}O(n)
m<n{displaystyle m<n}m<n
O(1+α){displaystyle O(1+alpha )}O(1+alpha )
m≫n{displaystyle mgg n}mgg n
O(1){displaystyle O(1)}O(1)

Ad n{displaystyle n}n corrisponde il numero di elementi presenti nell'array, ad m{displaystyle m}m il numero di posti disponibili mentre α{displaystyle alpha }alpha è il fattore di carico.



Analisi del costo di scansione |


Il numero di passi da effettuare per una scansione completa della tabella è data nel caso medio da:


















Esito ricerca Scansione lineare Hashing doppio / Scansione quadratica
Chiave trovata 12+12−{displaystyle {{1} over {2}}+{{1} over {2-2alpha }}}{{1} over {2}}+{{1} over {2-2alpha }}
loge⁡(1−α{displaystyle {{-log _{e}(1-alpha ),} over {alpha }}}{{-log _{e}(1-alpha ),} over {alpha }}
Chiave non trovata 12+1(2−)2{displaystyle {{1} over {2}}+{{1} over {(2-2alpha )^{2}}}}{{1} over {2}}+{{1} over {(2-2alpha )^{{2}}}}
11−α{displaystyle {{1} over {1-alpha }}}{{1} over {1-alpha }}

dove α è il fattore di carico.



Bibliografia |


  • Thomas H. Cormen, Charles E. Leiserson, Ronald L. Rivest, Introduzione agli algoritmi. Jackson Libri, 2003, ISBN 88-256-1421-7.


Voci correlate |


  • Tabella di hash distribuita


Altri progetti |



Altri progetti


  • Wikimedia Commons



  • Collabora a Wikimedia CommonsWikimedia Commons contiene immagini o altri file su hash table


Collegamenti esterni |


  • (IT) Fabrizio Luccio, Note su tabelle hash (PDF), su didawiki.di.unipi.it. URL consultato il 20 giugno 2017.

.mw-parser-output .navbox{border:1px solid #aaa;clear:both;margin:auto;padding:2px;width:100%}.mw-parser-output .navbox th{padding-left:1em;padding-right:1em;text-align:center}.mw-parser-output .navbox>tbody>tr:first-child>th{background:#ccf;font-size:90%;width:100%}.mw-parser-output .navbox_navbar{float:left;margin:0;padding:0 10px 0 0;text-align:left;width:6em}.mw-parser-output .navbox_title{font-size:110%}.mw-parser-output .navbox_abovebelow{background:#ddf;font-size:90%;font-weight:normal}.mw-parser-output .navbox_group{background:#ddf;font-size:90%;padding:0 10px;white-space:nowrap}.mw-parser-output .navbox_list{font-size:90%;width:100%}.mw-parser-output .navbox_odd{background:#fdfdfd}.mw-parser-output .navbox_even{background:#f7f7f7}.mw-parser-output .navbox_center{text-align:center}.mw-parser-output .navbox .navbox_image{padding-left:7px;vertical-align:middle;width:0}.mw-parser-output .navbox+.navbox{margin-top:-1px}.mw-parser-output .navbox .mw-collapsible-toggle{font-weight:normal;text-align:right;width:7em}.mw-parser-output .subnavbox{margin:-3px;width:100%}.mw-parser-output .subnavbox_group{background:#ddf;padding:0 10px}




























.mw-parser-output .CdA{border:1px solid #aaa;width:100%;margin:auto;font-size:90%;padding:2px}.mw-parser-output .CdA th{background-color:#ddddff;font-weight:bold;width:20%}



Controllo di autorità
GND (DE) 1046573225


InformaticaPortale Informatica: accedi alle voci di Wikipedia che trattano di informatica



Popular posts from this blog

浄心駅

カンタス航空