In che modo le tabelle hash O (1) tengono conto della velocità di hashing?


8

Si dice che le tabelle hash siano ammortizzate Θ(1) usando dire semplice concatenamento e raddoppio a una certa capacità.

Tuttavia, ciò presuppone che le lunghezze degli elementi siano costanti. Calcolare l'hash di un elemento richiede passare attraverso l'elemento, prendendoΘ(l) tempo in cui l è la lunghezza.

Ma discriminare tra n elementi, abbiamo bisogno che gli elementi abbiano lunghezza almeno lgnbit; altrimenti per principio del buco di piccione non saranno distinti. La funzione hash sta passandolgn ci vorranno pezzi di elementi Θ(lgn) tempo.

Quindi possiamo invece dire che la velocità di una tabella hash, tenendo conto di una ragionevole funzione hash che utilizza tutte le parti dell'input, è in realtà Θ(lgn)? Perché, quindi, le tabelle di hash sono in pratica efficienti per la memorizzazione di elementi a lunghezza variabile, come stringhe e numeri interi di grandi dimensioni?



4
La risposta è che non lo sono . Questo tipo di analisi dell'hash non tiene conto della dimensione (o del numero di bit) degli elementi, ma solo della loro moltitudine.
Nikos M.,

Ma se si cercasse una mappa hash sarebbe Θ(1) non considerare la lettura e la scrittura dei bit come descritto, è Θ(lg n), quindi con gli stessi criteri, una ricerca binaria o qualsiasi altro processo che normalmente consideriamo Θlg n sarebbe effettivamente Θ(lg2 n)no?


@tAllan Sarebbe una normale ricerca binaria Θ(log2n) ma se mantieni gli oggetti ordinati in base alle sequenze di bit delle loro chiavi e fai una ricerca binaria confrontando "un bit alla volta" (dettagli difficili omessi), potresti essere in grado di raggiungere Θ(logn).
Ripristina Monica il

Risposte:


3

Il racconto che le tabelle di hash vengono ammortizzate Θ(1)è una bugia una semplificazione eccessiva.

Questo è vero solo se:
- La quantità di dati di hash per articolo è banale rispetto al numero di K eys e la velocità di hash di un K ey è alta -K.
- Il numero di C ollisions è piccolo -c.
- Noi non prendiamo in considerazione il tempo necessario per R esize tabella hash -r.

Stringhe di grandi dimensioni per l'hash
Se il primo presupposto è falso, il tempo di esecuzione aumenteràΘ(K).
Questo è sicuramente vero per le stringhe di grandi dimensioni, ma per le stringhe di grandi dimensioni un semplice confronto avrebbe anche un tempo di esecuzione diΘ(K). Quindi un hash non è asintoticamente più lento, sebbene l'hash sia sempre più lento di un semplice confronto, perché il confronto ha una rinuncia anticipata ergoO(1), Ω(K) e l'hash deve sempre eseguire l'hashing della stringa completa O(K), Ω(K).

Si noti che i numeri interi crescono molto lentamente. 8 byte possono memorizzare valori fino a1018; 8 byte è una quantità banale di hash.
Se vuoi conservare le origini, pensa a loro come a stringhe.

Algoritmo di hash lento
Se l'hashing di spesa di importo non è banale rispetto alla memorizzazione dei dati, ovviamente ilΘ(1)l'ipotesi diventa insostenibile.
A meno che non venga utilizzato un hash crittografico, questo non dovrebbe essere un problema.

Ciò che conta è quello n >> K. Finché ciò valeΘ(1) è una buona dichiarazione.

Molte collisioni
Se la funzione di hashing è scarsa, o la tabella hash è piccola, o la dimensione della tabella hash è scomoda, le collisioni saranno frequenti e il tempo di esecuzione andrà aO(log(n)).
La funzione di hashing dovrebbe essere scelta in modo che le collisioni siano rare pur essendo il più veloci possibile, in caso di dubbio optare per un minor numero di collisioni a spese di hashing più lento.
Una regola empirica è che la tabella di hashing deve essere sempre piena per meno del 75%.
E la dimensione della tabella di hashing non dovrebbe avere alcuna correlazione con la funzione di hashing.
Spesso le dimensioni della tabella di hashing sono (relativamente) prime.

Ridimensionamento della tabella hash
Poiché una tabella hash quasi piena genererà troppe collisioni e una tabella hash grande (vuota) è uno spreco di spazio, molte implementazioni consentono alla tabella hash di crescere (e ridursi!) Secondo necessità.
La crescita di una tabella può comportare una copia completa di tutti gli elementi (e possibilmente un rimpasto), poiché l'archiviazione deve essere continua per motivi di prestazioni.
Solo in casi patologici il ridimensionamento della tabella hash sarà un problema, quindi i ridimensionamenti (costosi ma rari) vengono ammortizzati in molte chiamate.

Tempo di esecuzione
Quindi il vero tempo di esecuzione di una tabella hash èΘ(Kcr).
Ciascuno diK, c, r in media si presume che sia una (piccola) costante nel tempo di esecuzione ammortizzato e quindi lo diciamo Θ(1) è una buona dichiarazione.

Per tornare alle tue domande
Per favore, scusami per parafrasare, ho cercato di estrarre diversi insiemi di significati, sentiti libero di commentare se mi sono perso alcuni

Sembra che tu sia preoccupato per la lunghezza dell'output della funzione hash. Chiamiamo questom (n è generalmente considerato il numero di elementi da sottoporre a hash). m sarà log(n)perché m deve identificare in modo univoco una voce nella tabella hash.
Ciò significa che m cresce molto lentamente. A 64 bit il numero di voci della tabella hash occuperà una parte considerevole della RAM disponibile in tutto il mondo. A 128 bit supererà di gran lunga la memoria disponibile su disco sul pianeta terra.
Produrre un hash a 128 bit non è molto più difficile di un hash a 32 bit, quindi no , il tempo di creare un hash non èO(m) (o O(log(n)) se vorrai).

La funzione hash sta passando log(n) ci vorranno pezzi di elementi Θ(log(n)) tempo.

Ma la funzione hash non passalog(n)pezzi di elementi.
Per un articolo (!!), tuttavia, vale soloO(K)dati.
Anche la lunghezza dell'input (k) non ha alcuna relazione con il numero di elementi. Questo è importante, perché alcuni algoritmi non di hashing devono esaminare molti elementi nella raccolta per trovare un elemento (non) corrispondente.
La tabella hash esegue in media solo 1 o 2 confronti per elemento considerato prima di giungere a una conclusione.

Perché le tabelle hash sono efficienti per la memorizzazione di elementi a lunghezza variabile?

Perché indipendentemente dalla lunghezza dell'input (K) la lunghezza dell'uscita (m) è sempre lo stesso, le collisioni sono rare e il tempo di ricerca è costante.
Tuttavia, quando la lunghezza della chiaveK cresce rispetto al numero di elementi nella tabella hash (n) la storia cambia ...

Perché le tabelle hash sono efficienti per l'archiviazione di stringhe di grandi dimensioni?

Le tabelle hash non sono molto efficienti per stringhe molto grandi.

Se not n>>K (ovvero la dimensione dell'input è piuttosto grande rispetto al numero di elementi nella tabella hash) quindi non possiamo più dire che l'hash ha un tempo di esecuzione costante, ma dobbiamo passare a un tempo di esecuzione di Θ(K)soprattutto perché non è possibile uscire presto. È necessario per l'hash della chiave completa. Se stai memorizzando solo un numero limitato di articoli, potresti essere molto meglio usando una memoria ordinata, perché durante il confrontoK1 K2 puoi annullare l'iscrizione non appena viene rilevata una differenza.

Tuttavia, se conosci i tuoi dati, puoi scegliere di non eseguire l'hashing della chiave completa, ma solo la parte volatile (nota o presunta), ripristinando il Θ(1) proprietà mantenendo sotto controllo le collisioni.

Costanti nascoste
Come tutti dovrebbero sapereΘ(1)significa semplicemente che il tempo per elemento elaborato è una costante. Questa costante è un po 'più grande per l'hashing che per un semplice confronto.
Per le tabelle piccole una ricerca binaria sarà più veloce di una ricerca hash, perché ad esempio 10 confronti binari potrebbero benissimo essere più veloci di un singolo hash.
Per piccoli insiemi di dati dovrebbero essere prese in considerazione alternative alle tabelle hash.
È su grandi set di dati che le tabelle hash brillano davvero.


Non capisco la tua definizione di k,c,r. Non è vero che il ridimensionamento aumenta il runtime ammortizzato. Fintanto che si esegue il ridimensionamento in modo appropriato, il costo della copia può essere ammortizzato e non aumenta il tempo di ammortamento. Non credo che la velocità dell'hash sia mai un problema (anche gli hash crittografici sono molto veloci; e in ogni caso, corrono in tempo costante, se la lunghezza dell'input è delimitata da una costante). IlO(1)le dichiarazioni di runtime dipendono sempre dall'uso di una buona funzione hash (quindi le collisioni saranno poche).
DW

1
Quindi dei problemi che hai citato, penso che solo la lunghezza dell'input sia davvero un problema serio. Inoltre, questo non risponde davvero alla domanda che è stata posta. La domanda parla della lunghezza degli output e che la lunghezza degli output dovrebbe essere considerata come miglioreΩ(lgn) bit piuttosto che O(1)bit. È corretto, ma ciò che trascura è il modello computazionale utilizzato per calcolare ilO(1)tempo di esecuzione. Questa risposta non sembra entrare in nulla di tutto ciò, quindi non sono sicuro che si tratti della questione sollevata nella domanda.
DW

Volevo essere completo con tutti gli elementi del tempo di esecuzione. Siamo d'accordo sul fatto che solo la lunghezza della chiave è davvero una preoccupazione quando si esegue l'hashing. Ho risolto il problema con il registro (n) sollevato dall'OP. Ho letto male, perché è un tale problema quando si esegue l'hashing dell'IMO.
Johan,

Spero che la risposta sia più in sintonia con la domanda del PO ora.
Johan,

3

Cominciamo con una domanda più semplice. Considera quella che è forse la struttura di dati più semplice esistente, un array . Per concretezza, immaginiamo una matrice di numeri interi. Quanto tempo dura l'operazioneUN[io]=UN[j]prendere? La risposta dipende dal modello di calcolo. Qui sono rilevanti due modelli: il modello RAM (che è più comune) e il modello bit (che è più semplice da spiegare).

Nel modello di bit , un'operazione di base che coinvolgeN costi bit N. Quindi, se gli interi sonow bit largamente, l'operazione UN[io]=UN[j] costerà circa 2w.

Nel modello RAM , l'unità di base dei dati non è un po ', ma una parola (a volte conosciuta come una parola macchina ). Una parola è un numero intero di larghezzalogn, dove nè la dimensione degli ingressi (in bit). Un'operazione di base che coinvolgeN costi di parole N. Nella maggior parte dei casi, se si dispone di un array di numeri interi, gli interi necessari devono avere larghezzaO(logn)e così l'operazione UN[io]=UN[j] costi O(1).

Come ho detto sopra, di solito analizziamo gli algoritmi utilizzando il modello RAM. L'unica eccezione comune è l'aritmetica dei numeri interi, in particolare la moltiplicazione dei numeri interi, che viene spesso analizzata rispetto al numero di operazioni sui bit.

Perché utilizziamo il modello RAM? Dal momento che ha più potere predittivo (rispetto alla realtà). L'ipotesi che la dimensione dell'input sia al massimo esponenziale nella dimensione di una parola macchina è generalmente giustificata, specialmente per i moderni processori a 64 bit, e le operazioni sulle parole macchina richiedono tempo costante nelle CPU effettive.


Le tabelle hash sono strutture di dati più complicate e coinvolgono davvero tre tipi: il tipo di chiave, il tipo di hash e il tipo di valore. Dal punto di vista del tipo di valore , una tabella hash è solo un array glorificato, quindi ignoriamo questo aspetto. Si può sempre presumere che il tipo di hash sia costituito da un piccolo numero di parole automatiche. Il tipo di chiave soddisfa una proprietà speciale: è hash , il che significa che ha un'operazione hash che (almeno) è una funzione deterministica (una funzione che restituisce sempre lo stesso valore).

Ora possiamo rispondere alla tua domanda: quanto tempo richiede l'hash di una chiave? La risposta dipende dal modello di calcolo. Questa volta abbiamo tre modelli comuni: i due precedenti e il modello dell'oracolo.

Nel modello dell'oracolo , assumiamo che la funzione hash ci sia data da un "oracolo" che può calcolare l'hash di una chiave arbitraria in tempo costante.

Nel modello RAM e nel modello di bit , la funzione hash è una funzione effettiva e la complessità temporale della tabella hash dipende dalla complessità temporale della funzione hash. Le funzioni di hash utilizzate per la tabella hash (anziché per scopi crittografici) sono in genere molto veloci e richiedono un tempo lineare nell'input. Ciò significa che se il tipo di chiave ha lunghezzaN bit (nel modello di bit) o N parole (nel modello RAM), la funzione hash richiede tempo O(N). quandoN è una costante, la funzione hash richiede tempo costante.

Quando analizziamo il tempo di esecuzione degli algoritmi della tabella hash, di solito utilizziamo implicitamente il modello Oracle. Questo è spesso espresso in una lingua diversa: diciamo semplicemente che contiamo il numero di invocazioni della funzione hash. Questo ha senso, dal momento che di solito le applicazioni della funzione hash sono il termine dominante nel tempo di esecuzione degli algoritmi della tabella di hash, e quindi per analizzare la complessità temporale effettiva, tutto ciò che devi fare è moltiplicare il numero di invocazioni di hash per il tempo di esecuzione della funzione hash.

Quando analizziamo il tempo di esecuzione di un algoritmo utilizzando una tabella hash come struttura di dati, siamo spesso interessati al tempo di esecuzione effettivo, di solito nel modello RAM. Un'opzione qui è fare ciò che è stato suggerito nel paragrafo precedente, vale a dire moltiplicare il tempo di esecuzione delle operazioni della tabella hash (dato in termini di numero di invocazioni della funzione hash) per il tempo di esecuzione della funzione hash.

Tuttavia, questo non è abbastanza buono se i tasti hanno lunghezze variabili. Ad esempio, immagina di avere chiavi di dimensioni1,2,4,...,2me calcoliamo l'hash di ciascuno di essi una volta. La complessità temporale effettiva èO(2m), ma il calcolo sopra dà solo O(m2m). Se questo è il caso in alcune applicazioni, possiamo tenerne conto su base ad hoc, utilizzando un'analisi raffinata della complessità della tabella hash sottostante.

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.