330 likes | 579 Views
Hashing. argomenti. Hashing Tabelle hash Funzioni hash e metodi per generarle Inserimento e risoluzione delle collisioni Eliminazione Funzioni hash per file Hashing estendibile Hashing lineare. Richiamo sul concetto di dizionario. Insieme di coppie del tipo <elemento, chiave>
E N D
argomenti • Hashing • Tabelle hash • Funzioni hash e metodi per generarle • Inserimento e risoluzione delle collisioni • Eliminazione • Funzioni hash per file • Hashing estendibile • Hashing lineare Hashing
Richiamo sul concetto di dizionario • Insieme di coppie del tipo <elemento, chiave> • Le chiavi appartengono a un insieme totalmente ordinato • Operazioni: • insert(el, key) • delete(key) • search(key) Hashing
Tabelle hash • Adatte per realizzare dizionari • Generalizzazione del concetto di array • Importanti nell’accesso a dati su memoria secondaria • Gli accessi avvengono a memoria secondaria • Costo degli accessi predominante • Indirizzamento diretto: si associa ad ogni valore della chiave un indice di un array – ricerca in tempo O(1) • Problemi? Hashing
Indirizzamento diretto • Ogni possibile chiave corrisponde a el. diverso dell’array • Può portare a spreco di memoria • Es.: 10000 studenti e matr.= No. decimale a 5 cifre 0 1 No. chiavi N-1 Hashing
Obiettivi • N ~ No. Chiavi effettivamente usate • Tempo di ricerca O(1) • D.: possibile? • Nota: No. Chiavi possibili può essere >> N 0 1 No. chiavi N-1 Hashing
Tabella hash Dato l’insieme base di un dizionario: • <T, h> • T è una tabella con N celle • h: K {0,...,N-1} • K insieme delle possibili chiavi • {0,...,N-1} insieme delle posizioni nella tabella, dette pseudochiavi Hashing
Funzioni hash perfette e collisioni • Funzione hash perfetta: • k1!=k2 h(k1) != h(k2) • Richiede N >= |K| • Raramente ragionevole in pratica • In generale N < |K| (spesso N << |K|) • Conseguenza: k1!=k2mah(k1) == h(k2) è possibile Collisione • Paradosso del Compleanno • Es.: proporre una funzione hash perfetta nel caso in cui le chiavi siano stringhe di lunghezza 3 sull’alfabeto {a, b, c} Hashing
Requisiti di una funzione hash • Uniformità semplice: Pr[h(k)=j] ~ 1/|K| • La probabilità è calcolata rispetto alla distribuzione delle chiavi • Intuitivamente, si desidera che gli elementi si distribuiscano nell’array in modo uniforme • Difficile costruire funzioni che soddisfino la proprietà • D.: perché? Hashing
Requisiti di una funzione hash/2 • Esempio: sia |T|=5 e h(k)=k mod 5 {1, 7, 10, 14} {1, 6, 11, 16} 0 1 2 3 4 0 1 2 3 4 • Non è nota la distribuzione delle chiavi • Può aversi agglomerazione degli elementi In pratica: si cerca di avere indipendenza dai dati Hashing
Paradosso del compleanno • scelti 23 individui a caso, la probabilità che due di essi abbiano lo stesso compleanno è > ½ (50.7%) • dimostrare! • in termini di hashing, anche ipotizzando di avere le pseudochiavi a distribuzione uniforme, dopo 22 inserimenti in una tabella di 365 celle, ogni ulteriore inserimento avrà probabilità superiore a ½ di generare una collisione Hashing
Interpretazione delle chiavi • Tra gli elementi di K è definito un ordinamento totale ma: • Le chiavi non sono necessariamente numeri naturali • Es.: stringhe • Soluzione: associare a ciascuna chiave un intero • Modalità dipendono da insieme delle chiavi e applicazione Hashing
Esempio: stringhe • Possibile metodo: associare a ciascun carattere il valore ASCII e alla stringa il numero intero ottenuto in una base scelta • Esempio: base 2, posizioni meno significative a destra Stringa = “pt” pseudochiave = 112*21+116*20=340 Ascii(‘p’)=112 Ascii(‘t’)=116 Hashing
Derivazione di funzioni hash • Molti metodi • Divisione • Ripiegamento • Mid-square • Estrazione • ........... • Obiettivo: distribuzione possibilmente uniforme • Differenze: • Complessità • Fenomeni di agglomerazione Hashing
Divisione • h(k)=k mod |T| - Bassa complessità • Attenzione ai fenomeni di agglomerazione • No potenze di 2: se m=2p allora tutte le chiavi con i p bit meno significativi uguali collidono • No potenze di 10 se le chiavi sono numeri decimali (motivo simile) • In generale, la funzione dovrebbe dipendere da tutte le cifre della chiave (comunque rappresentata) • Scelta buona in pratica: numero primo non troppo vicino a una potenza di 2 (esempio: h(k)=k mod 701 per |K|=2048 valori possibili) Hashing
Ripiegamento • Chiave k suddivisa in parti k1,k2,....,kn • h(k)=f(k1,k2,....,kn) • Esempio: la chiave è un No. di carta di credito. Possibile funzione hash: 1. 4772 6453 7348 {477, 264, 537, 348} 2. f(477,264,537,348) = (477+264+537+348)mod 701 = 224 Hashing
Estrazione • Si usa soltanto una parte della chiave per calcolare l’indirizzo • Esempio: 6 cifre centrali del numero di carta di credito 4772 6453 7348 264537 • Il numero ottenuto può essere ulteriormente manipolato • L’indirizzo può dipendere da una porzione della chiave Hashing
Risoluzione delle collisioni • I metodi si distinguono per la collocazione degli elementi che danno luogo alla collisione • Concatenazione: alla i-esima posizione della tabella è associata la lista degli elementi tali che h(k)=i • Indirizzamento aperto: tutti gli elementi sono contenuti nella tabella Hashing
Concatenazione • h(k1)= h(k4)=0 • h(k5)= h(k7)=h(k2)=4 0 1 2 3 4 k1 k4 k2 k7 k2 k5 • Es.: h(k)=k mod 5 • k1=0, k4=10 • k5=9, k7=14, k2=4 Hashing
Concatenazione/2 • insert(el, k): inserimento in testa alla lista associata alla posizione h(k) – costo O(1) • search(k): ricerca lineare nella lista associata alla posizione h(k) – costo O(lungh. lista associata a h(k)) • delete(k): ricerca nella lista associata a h(k), quindi cancellazione – costo O(lungh. lista associata a h(k)) Hashing
Indirizzamento aperto • Tutti gli elementi sono memorizzati nella tabella • Le collisioni vanno risolte all’interno della tabella • Se la posizione calcolata è già occupata occorre cercarne una libera • I diversi metodi ad indirizzamento diretto si distinguono per il metodo di scansione adottato • La funzione hash può dipendere anche dal numero di tentativi effettuati • Indirizzo=h(k, i) per l’i-esimo tentativo Hashing
Inserimento insert (el, k) { /* T denota la tabella */ i=0; while (h(k, i) <occupata> && (i<|T|)) i++; if (i < |T|) <inserisci el in pos. i> else <overflow> } Hashing
Ricerca search (k) { /* T denota la tabella */ i=0; while ((k!=key(T[h(k, i)])) && (i<|T|)) i++; if (i < |T|) <restituisci T[h(k, i)]> else <elemento assente> } Hashing
Cancellazione delete (k) { /* T denota la tabella */ search(k); if (<trovato>) <elimina elemento con chiave k> } Hashing
Scansione • La funzione h(k, i) deve essere tale che tutte le posizioni della tabella siano esaminate • Sono possibili diverse forme per la funzione h(k,i) • Scansione lineare • Scansione quadratica • Hashing doppio • Si differenziano per complessità e comportamento rispetto a fenomeni di agglomerazione Hashing
Scansione lineare • h(k, i) = (h’(k)+i) mod |T|, dove h’(k) è una funzione di hashing • Si scandiscono tutte le posizioni nella sequenza T[h’(k)], T[h’(k)]+1, .... T[|T|], 0, 1, ...., T[h’(k)]-1 • Possibilità di agglomerazione primaria: gli elementi si agglomerano per lunghi tratti • agglomerazione primaria = due sequenze di scansione che hanno una collisione rimangono collidenti Hashing
Agglomerazione primaria • h(k, i) = (h’(k)+i) mod 101, h’(k)=k mod 101 0 1 2 {2, 103, 104, 105,....} Caso estremo, ma il problema esiste 100 Hashing
Scansione quadratica • h(k, i) = (h’(k)+c1i+c2i2) mod |T|, dove h’(k) è una funzione di hashing, c1 e c2 sono costanti • es. indirizzi scanditi: h’(k), h’(k)+1, h’(k)+2,h’(k)+3… • Es.: h(k, i) = h’(k)+i2, h(k, i+1) = h’(k)+(i+1)2, i=1,..., (|T|-1)/2 • Possibilità di agglomerazione secondaria: se h’(k1)= h’(k2) h’(k1,i)= h’(k2,i) • Descrivere h(k, i) quando h’(k)=k mod |5| Hashing
Hashing doppio • h(k, i) = (h1(k)+ih2(k)) mod |T|, dove h1(k) e h2(k) sono funzioni di hashing • Anche la modalità di scansione dipende dalla chiave • L’hashing doppio riduce i fenomeni di agglomerazione secondaria • esente da quelli di agglomerazione primaria Hashing
Hashing estendibile • E’ usato nella gestione di file, la cui dimensione può variare. • Es.: file ad accesso diretto • Il file è organizzato in bucket, ciascuno dei quali ha una dimensione fissa • Gli indirizzi sono associati ai bucket • La funzione hash restituisce in questo caso un puntatore al bucket Hashing
Hashing estendibile/2 File • h(k) restituisce una stringa binaria b • I bit più significativi di b sono usati per determinare l’indirizzo del bucket 2 Lungh. locale Bucket 00 Lungh. globale 2 h(k)=11001 2 Bucket 01 00 01 10 1 Bucket 1 11 Indice Hashing
Esempio File File 2 1 Bucket 00 Bucket 0 01100 01100 00001 01100 00001 00101 h(k)=00101 2 1 2 00 0 Bucket 01 01100 01100 01100 01 1 1 10 Indice Bucket 1 10010 11 Indice 1 Bucket 1 10010 Dim. Bucket = 4 Hashing
H. estendibile - Inserimento extHashInsert (el, k) { /* L=lunghezza globale */ str=h(k); /* LS= lunghezza str */ p=indice[pattern[LS-1...LS-L]]; if (<Bucket puntato da p non pieno) <inserisci el> else { <suddividi bucket> /* l=lunghezza locale bucket*/ l++; <inserisci el> } if (l > L) <Raddoppia indice> L++; } Hashing