Hashing dinamico perfetto - Dynamic perfect hashing
In informatica , l' hashing dinamico perfetto è una tecnica di programmazione per risolvere le collisioni in una struttura di dati di tabelle hash . Anche se richiede più memoria rispetto alle controparti della tabella hash, questa tecnica è utile per le situazioni in cui è necessario eseguire query, inserimenti ed eliminazioni veloci su un ampio set di elementi.
Dettagli
caso statico
Schema FKS
Il problema dell'hashing statico ottimale è stato risolto per la prima volta in generale da Fredman, Komlós e Szemerédi. Nel loro articolo del 1984, descrivono in dettaglio uno schema di tabelle hash a due livelli in cui ciascun bucket della tabella hash (di primo livello) corrisponde a una tabella hash separata di secondo livello. Le chiavi vengono hash due volte: il primo valore hash viene mappato a un determinato bucket nella tabella hash di primo livello; il secondo valore hash fornisce la posizione di quella voce nella tabella hash di secondo livello di quel bucket. La tabella di secondo livello è garantita per essere esente da collisioni (cioè hashing perfetto ) al momento della costruzione. Di conseguenza, il costo di ricerca è garantito essere O(1) nel caso peggiore .
Nel caso statico, ci viene fornito un insieme con un totale di x voci, ognuna con una chiave univoca, in anticipo. Fredman, Komlós e Szemerédi scelgono una tabella hash di primo livello con dimensione s = 2(x-1) bucket.
Per costruire, le voci x sono separate in s bucket dalla funzione di hashing di livello superiore, dove s = 2(x-1) . Quindi per ogni bucket con k voci, viene allocata una tabella di secondo livello con k 2 slot e la sua funzione di hash viene selezionata casualmente da un set di funzioni di hash universale in modo che sia priva di collisioni (ovvero una funzione di hash perfetta ) e memorizzata accanto alla tabella hash. Se la funzione hash selezionata casualmente crea una tabella con collisioni, viene selezionata casualmente una nuova funzione hash fino a quando non è possibile garantire una tabella priva di collisioni. Infine, con l'hash senza collisioni, le k voci vengono hash nella tabella di secondo livello.
La dimensione quadratica dello spazio k 2 assicura che la creazione casuale di una tabella con collisioni sia poco frequente e indipendente dalla dimensione di k , fornendo un tempo di costruzione ammortizzato lineare. Sebbene ogni tabella di secondo livello richieda uno spazio quadratico, se le chiavi inserite nella tabella hash di primo livello sono distribuite uniformemente , la struttura nel suo insieme occupa lo spazio O( n ) previsto , poiché le dimensioni dei bucket sono piccole con alta probabilità .
La funzione hash di primo livello è scelta in modo specifico in modo che, per l'insieme specifico di x valori di chiave univoci, lo spazio totale T utilizzato da tutte le tabelle hash di secondo livello abbia previsto uno spazio O( n ), e più specificamente T < s + 4*x. Fredman, Komlós e Szemerédi hanno mostrato che data una famiglia di hashing universale di funzioni hash, almeno la metà di queste funzioni ha quella proprietà.
Custodia dinamica
Dietzfelbinger et al. presentano un algoritmo di dizionario dinamico che, quando un insieme di n elementi viene aggiunto in modo incrementale al dizionario, le query di appartenenza vengono sempre eseguite in tempo costante e quindi O(1) tempo nel caso peggiore, lo spazio di archiviazione totale richiesto è O(n) (lineare) , e O(1) tempo di inserimento ed eliminazione ammortizzato previsto ( tempo costante ammortizzato ).
Nel caso dinamico, quando una chiave viene inserita nella tabella hash, se la sua voce nella rispettiva sottotabella è occupata, si dice che si verifica una collisione e la sottotabella viene ricostruita in base al nuovo conteggio totale delle voci e alla funzione hash selezionata casualmente. Poiché il fattore di carico della tabella di secondo livello è mantenuto basso (1/ k ), la ricostruzione non è frequente e il costo previsto ammortizzato degli inserimenti è O(1). Analogamente, il costo previsto ammortizzato delle cancellazioni è O(1).
Inoltre, le dimensioni finali della tabella di primo livello o di una qualsiasi delle sottotabelle non sono conoscibili nel caso dinamico. Un metodo per mantenere lo spazio O( n ) atteso della tabella consiste nel richiedere una ricostruzione completa quando si è verificato un numero sufficiente di inserimenti e delezioni. Per i risultati dovuti a Dietzfelbinger et al., finché il numero totale di inserimenti o eliminazioni supera il numero di elementi al momento dell'ultima costruzione, il costo previsto ammortizzato di inserimento e cancellazione rimane O (1) con il rimaneggiamento completo preso in considerazione .
L'implementazione dell'hashing dinamico perfetto di Dietzfelbinger et al. usa questi concetti, così come la cancellazione pigra , ed è mostrato in pseudocodice di seguito.
Implementazione dello pseudocodice
Individuare
function Locate(x) is
j := h(x)
if (position hj(x) of subtable Tj contains x (not deleted))
return (x is in S)
end if
else
return (x is not in S)
end else
end
Inserire
Durante l'inserimento di una nuova voce x in j , il contatore di operazioni globali, count , viene incrementato.
Se x esiste in j , ma è contrassegnato come eliminato, il contrassegno viene rimosso.
Se x esiste in j o nella sottotabella T j , e non è contrassegnato come cancellato, si dice che si verifica una collisione e la tabella di secondo livello del j- esimo bucket T j viene ricostruita con una diversa funzione hash selezionata casualmente h j .
function Insert(x) is
count = count + 1;
if (count > M)
FullRehash(x);
end if
else
j = h(x);
if (Position hj(x) of subtable Tj contains x)
if (x is marked deleted)
remove the delete marker;
end if
end if
else
bj = bj + 1;
if (bj <= mj)
if position hj(x) of Tj is empty
store x in position hj(x) of Tj;
end if
else
Put all unmarked elements of Tj in list Lj;
Append x to list Lj;
bj = length of Lj;
repeat
hj = randomly chosen function in Hsj;
until hj is injective on the elements of Lj;
for all y on list Lj
store y in position hj(y) of Tj;
end for
end else
end if
else
mj = 2 * max{1, mj};
sj = 2 * mj * (mj - 1);
if the sum total of all sj ≤ 32 * M2 / s(M) + 4 * M
Allocate sj cells for Tj;
Put all unmarked elements of Tj in list Lj;
Append x to list Lj;
bj = length of Lj;
repeat
hj = randomly chosen function in Hsj;
until hj is injective on the elements of Lj;
for all y on list Lj
store y in position hj(y) of Tj;
end for
end if
else
FullRehash(x);
end else
end else
end else
end else
end
Elimina
L'eliminazione di x contrassegna semplicemente x come eliminato senza rimozione e il conteggio degli incrementi . Sia nel caso di inserimenti che di cancellazioni, se il conteggio raggiunge una soglia M viene ricostruita l'intera tabella, dove M è un multiplo costante della dimensione di S all'inizio di una nuova fase . Qui la fase si riferisce al tempo tra le ricostruzioni complete. Nota che qui il -1 in "Delete( x )" è una rappresentazione di un elemento che non è nell'insieme di tutti i possibili elementi U .
function Delete(x) is
count = count + 1;
j = h(x);
if position hj(x) of subtable Tj contains x
mark x as deleted;
end if
else
return (x is not a member of S);
end else
if (count >= M)
FullRehash(-1);
end if
end
Ricostruzione completa
Una ricostruzione completa della tabella di S inizia rimuovendo tutti gli elementi contrassegnati come cancellati e quindi impostando il successivo valore di soglia M a un multiplo costante della dimensione di S . Una funzione hash, che partiziona S in s ( M ) sottoinsiemi, dove la dimensione del sottoinsieme j è s j , viene ripetutamente scelta a caso finché:
Infine, per ogni sottotabella T j viene ripetutamente scelta casualmente una funzione hash h j da H sj fino a quando h j è iniettiva sugli elementi di T j . Il tempo previsto per una ricostruzione completa della tabella di S con dimensione n è O( n ).
function FullRehash(x) is
Put all unmarked elements of T in list L;
if (x is in U)
append x to L;
end if
count = length of list L;
M = (1 + c) * max{count, 4};
repeat
h = randomly chosen function in Hs(M);
for all j < s(M)
form a list Lj for h(x) = j;
bj = length of Lj;
mj = 2 * bj;
sj = 2 * mj * (mj - 1);
end for
until the sum total of all sj ≤ 32 * M2 / s(M) + 4 * M
for all j < s(M)
Allocate space sj for subtable Tj;
repeat
hj = randomly chosen function in Hsj;
until hj is injective on the elements of list Lj;
end for
for all x on list Lj
store x in position hj(x) of Tj;
end for
end