(Quando) è la ricerca della tabella hash O (1)?


71

Si dice spesso che la ricerca della tabella hash funzioni in tempo costante: si calcola il valore hash, che fornisce un indice per una ricerca di array. Eppure questo ignora le collisioni; nel peggiore dei casi, ogni oggetto atterra nello stesso bucket e il tempo di ricerca diventa lineare ( ).Θ(n)

Esistono condizioni sui dati che possono fare davvero la ricerca della tabella hash ? È solo in media o una tabella hash può avere una ricerca nel caso peggiore di O ( 1 ) ?O(1)O(1)

Nota: sto venendo dal punto di vista di un programmatore qui; quando conservo i dati in una tabella hash, sono quasi sempre stringhe o alcune strutture di dati compositi e i dati cambiano durante la vita della tabella hash. Quindi, mentre apprezzo le risposte sugli hash perfetti, sono carini ma aneddotici e non pratici dal mio punto di vista.

Follow-up PS: per quale tipo di dati sono le operazioni della tabella hash O (1)?


3
Riesci a vivere con tempo di accesso ammortizzato? In generale, le prestazioni della tabella hash dipenderanno fortemente da quanto sovraccarico per gli hashtabili sparsi che si è disposti a tollerare e da come vengono distribuiti i valori di hash effettivi. O(1)
Raffaello

5
Oh, btw: puoi evitare il comportamento lineare nel caso peggiore usando alberi di ricerca (bilanciati) anziché elenchi.
Raffaello

1
@Raphael Sarei molto interessato a una risposta che spieghi (in linea di massima) quando posso contare su ammortizzato e quando non posso. Per quanto riguarda come sono distribuiti i valori di hash, questo fa davvero parte della mia domanda: come posso sapere? So che le funzioni hash dovrebbero distribuire bene i valori; ma se lo facessero sempre, il caso peggiore non verrebbe mai raggiunto, il che non ha senso. O(1)
Gilles 'SO- smetti di essere malvagio' il

1
Fai anche attenzione all'ottimizzazione prematura; per dati piccoli (diverse migliaia di elementi) ho spesso visto alberi binari bilanciati sovraperformare gli hashtable a causa di un sovraccarico più basso (i confronti delle stringhe sono molto più economici degli hash delle stringhe). O(logn)
è

Risposte:


41

Esistono due impostazioni in base alle quali è possibile ottenere casi peggiori.O(1)

  1. Se la tua configurazione è statica, l'hash di FKS ti darà garanzie caso peggiore . Ma come hai indicato, l'impostazione non è statica.O(1)

  2. Se si utilizza l'hash del cuculo, le query e le eliminazioni sono caso peggiore, ma è previsto solo l'inserimento di O ( 1 ) . L'hashing del cuculo funziona abbastanza bene se si ha un limite superiore sul numero totale di inserti e si imposta la dimensione della tabella in modo che sia più grande di circa il 25%.O(1)O(1)

Ci sono maggiori informazioni qui .


3
Potresti espandere su FKS e Cuckoo? Entrambi i termini sono nuovi per me.
Gilles 'SO-smetti di essere malvagio' il

1
Che dire dell'hashing dinamico perfetto? Ha ricerche nel caso peggiore e O ( 1 ) ammortizzato inserimento e cancellazione. ( citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.30.8165 )O(1)O(1)
Joe

2
Le FKS sono le iniziali di (Fredman, Komlós, Szemerédi) e il cuculo è il nome di una specie di brid. È utilizzato per questo tipo di hashing, perché i pulcini a cucù spingono le uova di sibilings fuori dal nido. Questo assomiglia in qualche modo al funzionamento di questo metodo hasing.
uli

1
@Suresh: Davvero? Ho pensato che avessi bisogno di funzioni indipendenti da , che ho sempre associato alla necessità di espansori. Sono corretto. Eliminerò il mio commento tra poco. logn
Louis,

1
Per fare un commento più utile su questa risposta, come sottolinea @Suresh, l'hash del cuculo funzionerà bene senza le fantasiose (e grandi) funzioni di hash utilizzate per analizzarlo teoricamente.
Louis,

21

Questa risposta riassume parti di TAoCP Vol 3, Cap 6.4.

Supponiamo di avere un insieme di valori , n di cui vogliamo memorizzare in un array A di dimensioni m . Utilizziamo una funzione hash h : V [ 0 .. M ) ; tipicamente, M | V | . Chiamiamo α = nVnAmh:V[0..M)M|V| ilfattore di caricodiA. Qui, assumeremo il naturalem=M; in scenari pratici, abbiamomM, tuttavia, e dobbiamo mappare permnoi stessi.α=nmAm=MmMm

La prima osservazione è che anche se ha caratteristiche uniformi¹, la probabilità che due valori abbiano lo stesso valore di hash è alta; questo è essenzialmente un esempio del famigerato paradosso del compleanno . Pertanto, di solito dovremo affrontare i conflitti e possiamo abbandonare la speranza del tempo di accesso nel caso peggiore O ( 1 ) .hO(1)

E il caso medio, però? Supponiamo che ogni chiave di verifichi con la stessa probabilità. Il numero medio di voci selezionate C S n (ricerca riuscita) risp. C U n (ricerca non riuscita) dipende dal metodo di risoluzione dei conflitti utilizzato.[0..M)CnSCnU

chaining

Ogni voce dell'array contiene (un puntatore alla testa di) un elenco collegato. Questa è una buona idea perché la lunghezza dell'elenco prevista è ridotta ( ) anche se la probabilità di avere collisioni è alta. Alla fine, otteniamo C S n1+αnm Questo può essere leggermente migliorato memorizzando gli elenchi (parzialmente o completamente) all'interno della tabella.

CnS1+α2 and CnU1+α22.

Analisi lineare

v

h(v),h(v)1,,0,m1,,h(v)+1
vα1
CnS12(1+11α) and CnU12(1+(11α)2).
α<0.75

Doppio hash

M

CnS1αln(11α) and CnU11α.

Si noti che la rimozione di elementi da e l'estensione di tabelle presenta vari gradi di difficoltà per i rispettivi metodi.

O(1)αh


h
Hashtable


10

S{0,1,2,...,n}O(1)O(1)lSlxxSO(|l|)SO(|S|)O(|l|+|S|)O(|l||S|)O(log(|l|)|S|)O(|l|)l

O(|l|)

lUNSUxSllh:U{true,false}hh(x)=falsexUylh(y)=trueO(|l|)O(|U|)

lO(|U|)O(|1|)O(|U|)

Uh


O(|l|)O(|S|)O(|l||S|)

hh:U{false,true}h

@Gilles Fondamentalmente viene utilizzato solo come tabella di ricerca per l'appartenenza alla lista. Quando hai una funzione hash perfetta con un inverso noto ed economico, invece di memorizzare l'oggetto stesso, devi solo memorizzare 1 bit (se è stato aggiunto l'oggetto con l'hash univoco). Se sono possibili collisioni, penso che fare questo sia indicato come un filtro Bloom, ma in ogni caso può fornire un "no" definito alla questione dell'appartenenza, che è comunque utile in molti scenari.
Patrick87

9

O(1)

O(1)O(1)O(1)O(1)


Una funzione hash perfetta sarebbe perfetta, ma come posso ottenerne una? Quanto mi costerà? E come faccio a sapere qual è il numero massimo o previsto di collisioni?
Gilles 'SO- smetti di essere malvagio' il

2
@Gilles una funzione di hash perfetta è qualsiasi funzione che produrrà un hash unico per tutti gli input possibili. Se i tuoi possibili input sono limitati (e unici), questo è facile da fare.
Rafe Kettler,

1
@RafeKettler I miei input sono in genere stringhe o strutture dati composte e di solito aggiungo e rimuovo le voci man mano che i miei dati si evolvono. Come posso creare un hash perfetto per questo?
Gilles 'SO- smetti di essere malvagio'

4
Sì, ma questo è il punto. Una funzione hash deterministica perfetta non esiste se il dominio è più grande dell'intervallo.
Suresh

@Suresh: se ti è permesso scegliere una nuova funzione hash e aumentare le dimensioni della tabella ogni volta che c'è una collisione, puoi sempre trovare una funzione hash (deterministica) che - per i dati già nella tabella più quella nuova oggetto che stai cercando di inserire - non ha collisioni (è "perfetto"). Questo è il motivo per cui l' hash dinamico dinamico sceglie periodicamente una nuova funzione di hash casuale.
David Cary,
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.