Perché una ricerca hashtable (senza collisioni) è davvero O (1)?


10

Disclaimer: so che ci sono domande simili già qui e su StackOverflow. Ma si tratta di collisioni, che non è ciò che chiedo.

La mia domanda è: perché innanzitutto la ricerca senza collisioni O(1)?

Supponiamo di avere questa tabella hash:

Hash  Content
-------------
ghdjg Data1
hgdzs Data2
eruit Data3
xcnvb Data4
mkwer Data5
rtzww Data6

Ora sto cercando il tasto in kcui la funzione hash h(k)h(k) = mkwer. Ma come fa la ricerca a "sapere" che l'hash mkwerè in posizione 5? Perché non è necessario scorrere tutti i tasti O(n)per trovarlo? Gli hash non possono essere una sorta di indirizzi hardware reali perché perderei l'abilità di spostare i dati. E per quanto ne so, l'hashtable non è ordinato sugli hash (anche se lo fosse, anche la ricerca richiederebbe O(log n))?

In che modo conoscere un hash aiuta a trovare il posto giusto nella tabella?

Risposte:


24

La funzione hash non restituisce una stringa come mkwer. Restituisce direttamente la posizione dell'elemento nell'array. Se, ad esempio, la tabella hash ha dieci voci, la funzione hash restituirà un numero intero compreso tra 0 e 9.


1
Grazie. :) Il mio errore era pensare a una funzione hash hashtable come MD5 o SHA. Ma un hash ovviamente può essere una posizione intera, a cui non avevo pensato. Ora che so cosa cercare, ho anche trovato rapidamente un buon esempio: la funzione hash di PHP: github.com/php/php-src/blob/PHP-5.6.10/Zend/zend_hash.h#L237
Foo Bar dal

13
@FooBar: MD5 e SHA calcolano anche singoli numeri dall'input, è così comune parlare degli hash in forma esadecimale. Proprio come gli indirizzi di memoria raramente sono considerati in decimale.
nperson325681

4
Inoltre, MD5 ecc. Sono troppo lunghi per essere utilizzati direttamente come indice di array. Sarebbe possibile usare una parte dell'hash, come gli n bit inferiori .
Chirlu,

6

La funzione hash calcola la posizione dell'array da una determinata stringa . Se questo è hash perfetto significa che non ci sono sicuramente collisioni, l'array più probabilmente è almeno due volte più grande del numero di elementi.

Ad esempio, darò un hash molto scarso per le lettere, solo per illustrare il meccanismo:
0) 1) per ogni carattere nella stringa prendi valore ASCII, sottrai 'a' se è minuscolo, sottrai 'A' se maiuscolo, aggiungi valore a x. 2) il numero risultante, ad es. 15, è indice di array. x = x m o d 52x=0;
x=xmod52

Questo hash molto semplice (limitato e soggetto a collisioni) differisce dagli altri hash nel meccanismo di hashing, non considera l'input dato. Nello schema più avanzato l'hash è un numero maggiore, adattato al numero di elementi. L'hash perfetto viene generato per tutti gli input per garantire l'assenza di collisioni.

Questo è perché il calcolo dell'hash dalla stringa dipende da quanto è sofisticata la funzione calcolata, ma non dipende dal numero di elementi.O(1)

In caso di hash perfetto, quando vengono aggiunti elementi viene ricalcolato, il caso più semplice con collisioni quando il carico dell'array è grande aumenta la dimensione dell'array, la funzione richiede un modulo di output più grande e gli elementi vengono spostati nei nuovi posti.h(k)

L'array è un frammento di memoria continuo, per ottenere l' elemento prendi l'indirizzo del primo elemento (inizio dell'array) e poi aggiungi a questo indirizzo modo da avere una cella di memoria esplicita.n ( s i z e o f e l e m e n t )nthn(sizeofelement)


1
E come fa la ricerca a sapere dove si trova l'hash nella tabella? Non è né ordinato né indirizzi hardware.
Foo Bar,

Fornisci una stringa, ad esempio "xcnvb", quindi l'hash calcolato fornisce l'indice dell'array, "xcnvb" è l'elemento da cercare, 8 è l'indice nella tabella. Viene ordinato, l'hash restituisce il posto per recuperare l'elemento. Questo elemento è stato inserito nella stessa funzione. L'hardware non ha nulla a che fare qui. Fornisci array, funzione hash e calcola hash per ottenere l'indice in array, lo stesso in retreival. La matrice non è ordinata, inoltre non è mai piena. h("xcnvb")=8
Male

Ma non tutti gli indici saranno riempiti. Se ho hash 1, 4, 8, 90 e 223 pieni di dati, come fa una ricerca a trovare il posto giusto? In questo caso l'indice "90" è nella posizione 4 perché la maggior parte degli altri indici non esiste. E una tabella vuota non ha dimensioni infinite e ha tutte le posizioni possibili !?
Foo Bar,

Sì, l'array supponiamo che siano lunghi 512 elementi, 9 bit utilizzati per la funzione hash e hai solo 4 elementi. L'indice 90 ha la posizione 90 nell'array, come nell'esempio: quasi tutte le celle sono vuote. Se la tua matrice è , la indicizzi = i tuoi dati per "xcnvb"HaHa(h("xcnvb"))=Ha[90]
Evil

La funzione hash non restituisce un indice nell'array. Al contrario, restituisce un numero prevedibile che può essere mappato nell'array. Di solito viene fatto utilizzando l' operatore modulo con il numero di bucket di tabella hash come l'altro operando.
Christopher Schultz,

3

Per espandere la risposta di David Richerby, il termine " funzione hash " è un po 'sovraccarico. Spesso, quando parliamo di una funzione hash, pensiamo a MD5, SHA-1 o qualcosa come il .hashCode()metodo Java , che trasforma alcuni input in un singolo numero. Tuttavia, è improbabile che il dominio di questo numero (ovvero il valore massimo) abbia le stesse dimensioni della tabella hash in cui si sta tentando di archiviare i dati. (MD5 è 16 byte, SHA-1 è 20 byte ed .hashCode()è un int- 4 byte).

Quindi la tua domanda riguarda il prossimo passo: una volta che abbiamo una funzione hash in grado di mappare input arbitrari su numeri, come li inseriamo in una struttura di dati di una dimensione particolare? Con un'altra funzione, chiamata anche "funzione hash"!

Un banale esempio di tale funzione è il modulo ; puoi facilmente mappare un numero di dimensioni arbitrarie su un indice specifico in un array con modulo. Questo è introdotto in CLRS come "metodo di divisione":

Nel metodo di divisione per la creazione di funzioni hash, mappiamo una chiave in uno degli slot prendendo il resto di diviso per . Cioè, la funzione hash èkmkm

h(k)=k mod .m

...

Quando si utilizza il metodo di divisione di solito si evitano determinati valori di . Ad esempio, non dovrebbe essere una potenza di 2, poiché se allora è solo i bit di ordine inferiore di .mmm=2ph(k)pk

~ Introduzione agli algoritmi, §11.3.1 - CLRS

Quindi modulo non è una grande funzione di hash, poiché limita le dimensioni che possiamo utilizzare in sicurezza per la nostra struttura di dati sottostante. La sezione successiva introduce un "metodo di moltiplicazione" leggermente più complesso, che utilizza anche il modulo ma è vantaggioso perché "il valore di non è critico". Funziona comunque meglio con alcune conoscenze preliminari di "caratteristiche dei dati sottoposti a hash" - qualcosa che spesso non conosciamo.m

Java HashMaputilizza una versione modificata del metodo di divisione che esegue una fase di pre-elaborazione per tenere conto delle .hashCode()implementazioni deboli in modo da poter utilizzare array di potenza di due dimensioni. Puoi vedere esattamente cosa sta succedendo nel .getEntry()metodo (i commenti sono miei):

 // hash() transforms key.hashCode() to protect against bad hash functions
 int hash = (key == null) ? 0 : hash(key.hashCode());
 // indexOf() converts the resulting hash to a value between 0 and table.length-1
 for (Entry<K,V> e = table[indexFor(hash, table.length)];
     ...

Java 8 ha portato con sé una riscrittura HashMapancora più veloce, ma un po 'più difficile da leggere. Tuttavia, utilizza lo stesso principio generale per la ricerca dell'indice.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.