Strutture dati e algoritmi

Esercitazione sulla struttura dei dati della tabella hash

Esercitazione sulla struttura dei dati della tabella hash
In informatica, la parola "mappa" significa collegare un elemento in un set a un altro elemento in un altro set. Immagina che su una pagina ci siano parole in un cerchio a sinistra, e sul lato destro della stessa pagina, ci sia un altro cerchio all'interno del quale ci sono altre parole. Supponiamo che in ogni cerchio le parole siano scritte a caso, sparse all'interno del cerchio. Inoltre, supponi che le parole nel cerchio di sinistra siano chiamate chiavi e le parole nel cerchio di destra siano chiamate valori. Se viene disegnata una freccia da ogni parola a sinistra a ogni parola a destra, allora si direbbe che le chiavi sono state mappate ai valori.

Supponi di essere il proprietario di un grande negozio di forniture nella contea in cui vivi. Supponiamo che tu viva in una vasta area, che non è una zona commerciale. Non sei l'unico ad avere un negozio di forniture nella zona; hai pochi concorrenti. E poi ti viene in mente che dovresti registrare i numeri di telefono dei tuoi clienti in un quaderno. Certo, il quaderno è piccolo e non puoi registrare tutti i numeri di telefono di tutti i tuoi clienti.

Quindi decidi di registrare solo i numeri di telefono dei tuoi clienti abituali. E così hai una tabella con due colonne. La colonna a sinistra ha i nomi dei clienti e la colonna a destra ha i numeri di telefono corrispondenti. In questo modo c'è una mappatura tra i nomi dei clienti e i numeri di telefono. La colonna di destra della tabella può essere considerata come la tabella hash principale. I nomi dei clienti ora sono chiamati chiavi e i numeri di telefono sono chiamati valori. Nota che quando un cliente va in trasferimento, dovrai cancellare la sua riga, lasciando vuota la riga o sostituita con quella di un nuovo cliente abituale. Si noti inoltre che con il tempo, il numero di clienti abituali può aumentare o diminuire, e quindi il tavolo può crescere o ridursi.

Come altro esempio di mappatura, supponiamo che ci sia un club di agricoltori in una contea. Certo, non tutti gli allevatori saranno membri del club. Alcuni membri del club non saranno soci regolari (presenza e contributo). Il barman può decidere di registrare i nomi dei membri e la loro scelta di drink. Sviluppa una tabella di due colonne. Nella colonna di sinistra scrive i nomi dei soci del club. Nella colonna di destra, scrive la scelta della bevanda corrispondente.

C'è un problema qui: ci sono duplicati nella colonna di destra. Cioè, lo stesso nome di una bevanda si trova più di una volta. In altre parole, membri diversi bevono la stessa bevanda dolce o la stessa bevanda alcolica, mentre altri membri bevono una bevanda dolce o alcolica diversa. Il barista decide di risolvere questo problema inserendo una colonna stretta tra le due colonne. In questa colonna centrale, partendo dall'alto, numera le righe partendo da zero (i.e. 0, 1, 2, 3, 4, ecc.), scendendo, un indice per riga. Con questo, il suo problema è risolto, poiché il nome di un membro ora viene mappato su un indice e non sul nome di una bevanda. Quindi, poiché una bevanda è identificata da un indice, il nome di un cliente viene mappato all'indice corrispondente.

La sola colonna dei valori (bevande) costituisce la tabella hash di base. Nella tabella modificata, la colonna degli indici e i loro valori associati (con o senza duplicati) formano una normale tabella hash - di seguito viene fornita la definizione completa di una tabella hash. Le chiavi (prima colonna) non fanno necessariamente parte della tabella hash.

Come altro esempio ancora, considera un server di rete in cui un utente dal suo computer client può aggiungere alcune informazioni, eliminare alcune informazioni o modificare alcune informazioni. Ci sono molti utenti per il server.  Ogni nome utente corrisponde a una password memorizzata nel server. Chi mantiene il server può vedere i nomi utente e la password corrispondente, e quindi essere in grado di corrompere il lavoro degli utenti.

Quindi il proprietario del server decide di produrre una funzione che crittografa una password prima che venga memorizzata. Un utente accede al server, con la sua normale password compresa. Tuttavia, ora, ogni password è archiviata in forma crittografata. Se qualcuno vede una password crittografata e tenta di accedere utilizzandola, non funzionerà, perché l'accesso riceve una password compresa dal server e non una password crittografata.

In questo caso, la password compresa è la chiave e la password crittografata è il valore. Se la password crittografata si trova in una colonna di password crittografate, quella colonna è una tabella hash di base. Se tale colonna è preceduta da un'altra colonna con indici che iniziano da zero, in modo che ogni password crittografata sia associata a un indice, allora sia la colonna degli indici che la colonna della password crittografata formano una normale tabella hash. Le chiavi non fanno necessariamente parte della tabella hash.

Notare, in questo caso, che ogni chiave, che è una password comprensibile, corrisponde a un nome utente. Quindi, c'è un nome utente che corrisponde a una chiave che è mappata su un indice, che è associato a un valore che è una chiave crittografata.

Di seguito sono riportati la definizione di una funzione hash, la definizione completa di una tabella hash, il significato di un array e altri dettagli. Devi avere conoscenza di puntatori (riferimenti) e liste collegate, per apprezzare il resto di questo tutorial.

Significato della funzione hash e della tabella hash

Vettore

Un array è un insieme di locazioni di memoria consecutive. Tutte le posizioni sono della stessa dimensione. Si accede al valore nella prima posizione con l'indice, 0; si accede al valore nella seconda posizione con l'indice, 1; si accede al terzo valore con l'indice 2; quarto con indice 3; e così via. Un array normalmente non può aumentare o rimpicciolirsi. Per modificare la dimensione (lunghezza) di un array, è necessario creare un nuovo array e i valori corrispondenti copiati nel nuovo array. I valori di un array sono sempre dello stesso tipo.

Funzione hash

Nel software, una funzione hash è una funzione che accetta una chiave e produce un indice corrispondente per una cella array. L'array ha una dimensione fissa (lunghezza fissa). Il numero di chiavi è di dimensione arbitraria, solitamente maggiore della dimensione dell'array. L'indice risultante dalla funzione hash è chiamato valore hash o digest o codice hash o semplicemente hash.

Tabella hash

Una tabella hash è un array con valori, ai cui indici sono mappate le chiavi. Le chiavi sono indirettamente mappate ai valori. Si dice infatti che le chiavi siano mappate ai valori, poiché ad ogni indice è associato un valore (con o senza duplicati). Tuttavia, la funzione che esegue la mappatura (i.e. hashing) mette in relazione le chiavi con gli indici dell'array e non realmente con i valori, poiché potrebbero esserci duplicati nei valori. Il diagramma seguente illustra una tabella hash per i nomi delle persone e i loro numeri di telefono. Le celle dell'array (slot) sono chiamate bucket.

Nota che alcuni secchi sono vuoti. Una tabella hash non deve necessariamente avere valori in tutti i suoi bucket. I valori nei bucket non devono essere necessariamente in ordine crescente. Tuttavia, gli indici a cui sono associati sono in ordine crescente. Le frecce indicano la mappatura. Nota che le chiavi non sono in un array. Non devono essere in nessuna struttura. Una funzione hash accetta qualsiasi chiave e genera un indice per un array. Se non c'è alcun valore nel bucket associato all'hashing dell'indice, è possibile inserire un nuovo valore in quel bucket. La relazione logica è tra la chiave e l'indice, e non tra la chiave e il valore associato all'indice.

I valori di un array, come quelli di questa tabella hash, sono sempre dello stesso tipo di dati. Una tabella hash (bucket) può connettere le chiavi ai valori di diversi tipi di dati. In questo caso, i valori dell'array sono tutti puntatori, che puntano a diversi tipi di valore.

Una tabella hash è un array con una funzione hash. La funzione prende una chiave e esegue l'hashing di un indice corrispondente, quindi collega le chiavi ai valori, nell'array. Le chiavi non devono far parte della tabella hash.

Perché l'array e non l'elenco collegato per la tabella hash?

L'array per una tabella hash può essere sostituito da una struttura dati di un elenco collegato, ma ci sarebbe un problema. Il primo elemento di una lista concatenata è naturalmente all'indice, 0; il secondo elemento è naturalmente all'indice 1; il terzo è naturalmente all'indice 2; e così via. Il problema con l'elenco collegato è che per recuperare un valore, l'elenco deve essere iterato e questo richiede tempo. L'accesso a un valore in un array avviene per accesso casuale. Una volta conosciuto l'indice, il valore si ottiene senza iterazioni; questo accesso è più veloce.

Collisione

La funzione hash prende una chiave e esegue l'hash dell'indice corrispondente, per leggere il valore associato o per inserire un nuovo valore. Se lo scopo è leggere un valore, non c'è nessun problema (nessun problema), finora. Tuttavia, se lo scopo è inserire un valore, l'indice con hash potrebbe già avere un valore associato, e questa è una collisione; il nuovo valore non può essere messo dove c'è già un valore. Ci sono modi per risolvere la collisione - vedi sotto.

Perché si verifica la collisione?

Nell'esempio del negozio di forniture sopra, i nomi dei clienti sono le chiavi e i nomi delle bevande sono i valori. Notare che i clienti sono troppi, mentre l'array ha una dimensione limitata e non può prendere tutti i clienti. Quindi, solo le bevande dei clienti abituali sono memorizzate nell'array. La collisione si verificherebbe quando un cliente non regolare diventa regolare. I clienti per il negozio formano un set ampio, mentre il numero di secchi per i clienti nell'array è limitato.

Con le tabelle hash, sono i valori per le chiavi che sono molto probabili, che vengono registrati. Quando una chiave che non era probabile, diventa probabile, ci sarebbe probabilmente una collisione. In effetti, la collisione si verifica sempre con le tabelle hash.

Nozioni di base sulla risoluzione delle collisioni

Due approcci alla risoluzione delle collisioni sono chiamati concatenamento separato e indirizzamento aperto. In teoria, le chiavi non dovrebbero essere nella struttura dati o non dovrebbero far parte della tabella hash. Tuttavia, entrambi gli approcci richiedono che la colonna chiave preceda la tabella hash e diventi parte della struttura complessiva. Invece delle chiavi nella colonna delle chiavi, i puntatori alle chiavi potrebbero trovarsi nella colonna delle chiavi.

Una pratica tabella hash include una colonna chiavi, ma questa colonna chiave non fa ufficialmente parte della tabella hash.

Entrambi gli approcci per la risoluzione possono avere bucket vuoti, non necessariamente alla fine dell'array.

Concatenamento separato

In concatenamento separato, quando si verifica una collisione, il nuovo valore viene aggiunto a destra (non sopra o sotto) del valore in collisione. Quindi due o tre valori finiscono per avere lo stesso indice. Raramente più di tre dovrebbero avere lo stesso indice.

Può più di un valore avere davvero lo stesso indice in un array?? - No. Quindi, in molti casi, il primo valore per l'indice è un puntatore a una struttura di dati di elenchi collegati, che contiene uno, due o tre valori in collisione. Il diagramma seguente è un esempio di tabella hash per il concatenamento separato di clienti e i loro numeri di telefono:

I secchi vuoti sono contrassegnati dalla lettera x. Il resto degli slot ha puntatori a liste collegate. Ogni elemento dell'elenco collegato ha due campi dati: uno per il nome del cliente e l'altro per il numero di telefono. Si verifica un conflitto per le chiavi: Peter Jones e Suzan Lee. I valori corrispondenti sono costituiti da due elementi di un elenco concatenato.

Per le chiavi in ​​conflitto, il criterio per inserire il valore è lo stesso criterio utilizzato per individuare (e leggere) il valore.

Indirizzamento aperto

Con l'indirizzamento aperto, tutti i valori sono memorizzati nell'array di bucket. Quando si verifica un conflitto, il nuovo valore viene inserito in un bucket vuoto nuovo il valore corrispondente per il conflitto, seguendo alcuni criteri. Il criterio utilizzato per inserire un valore in conflitto è lo stesso criterio utilizzato per individuare (cercare e leggere) il valore.

Il diagramma seguente illustra la risoluzione dei conflitti con l'indirizzamento aperto:

La funzione hash prende la chiave, Peter Jones e esegue l'hash dell'indice, 152, e memorizza il suo numero di telefono nel bucket associato. Dopo qualche tempo, la funzione hash esegue l'hash dello stesso indice, 152 dalla chiave, Suzan Lee, che si scontra con l'indice per Peter Jones. Per risolvere questo problema, il valore di Suzan Lee viene memorizzato nel bucket dell'indice successivo, 153, che era vuoto. La funzione hash esegue l'hash dell'indice, 153 per la chiave, Robin Hood, ma questo indice è già stato utilizzato per risolvere il conflitto per una chiave precedente. Quindi il valore per Robin Hood viene inserito nel prossimo secchio vuoto, che è quello dell'indice 154.

Metodi di risoluzione dei conflitti per il concatenamento separato e l'indirizzamento aperto

Il concatenamento separato ha i suoi metodi per risolvere i conflitti e anche l'indirizzamento aperto ha i suoi metodi per risolvere i conflitti.

Metodi per risolvere i conflitti di concatenamento separato Separate

I metodi per concatenare tabelle hash separate sono ora brevemente spiegati:

Concatenamento separato con elenchi collegati

Questo metodo è come spiegato sopra. Tuttavia, ogni elemento della lista collegata non deve necessariamente avere il campo chiave (e.g. campo nome cliente sopra).

Concatenamento separato con celle di testa dell'elenco

In questo metodo, il primo elemento della lista collegata è memorizzato in un bucket dell'array. Questo è possibile, se il tipo di dati per l'array è l'elemento della lista collegata.

Concatenamento separato con altre strutture

Qualsiasi altra struttura di dati, come l'albero di ricerca binaria autobilanciato che supporta le operazioni richieste, può essere utilizzata al posto dell'elenco collegato - vedere più avanti.

Metodi per risolvere i conflitti di indirizzamento aperto

Un metodo per risolvere i conflitti nell'indirizzamento aperto è chiamato sequenza sonda. Vengono ora brevemente spiegate tre sequenze di sonde ben note:

Sondaggio lineare

Con il sondaggio lineare, quando si verifica un conflitto, viene cercato il secchio vuoto più vicino al di sotto del secchio in conflitto. Inoltre, con la tastatura lineare, sia la chiave che il suo valore sono memorizzati nello stesso bucket.

Sondaggio quadratico

Supponiamo che il conflitto si verifichi all'indice H. Il prossimo slot vuoto (bucket) all'indice H + 12 si usa; se questo è già occupato, allora il prossimo vuoto a H + 22 viene utilizzato, se è già occupato, quindi il prossimo vuoto a H + 32 viene utilizzato, e così via. Ci sono varianti a questo.

Doppio Hashing

Con il doppio hashing, ci sono due funzioni di hash. Il primo calcola (hash) l'indice. Se si verifica un conflitto, il secondo utilizza la stessa chiave per determinare fino a che punto deve essere inserito il valore. C'è di più in questo - vedi dopo.

Funzione hash perfetta

Una funzione di hash perfetta è una funzione di hash che non può causare alcuna collisione. Questo può accadere quando il set di chiavi è relativamente piccolo e ogni chiave è mappata su un particolare numero intero nella tabella hash.

Nel set di caratteri ASCII, i caratteri maiuscoli possono essere mappati alle lettere minuscole corrispondenti utilizzando una funzione hash. Le lettere sono rappresentate nella memoria del computer come numeri. Nel set di caratteri ASCII, A è 65, B è 66, C è 67, ecc. e a è 97, b è 98, c è 99, ecc. Per mappare da A ad a, aggiungi 32 a 65; per mappare da B a b, aggiungi 32 a 66; per mappare da C a c, aggiungi 32 a 67; e così via. Qui, le lettere maiuscole sono le chiavi e le lettere minuscole sono i valori. La tabella hash per questo può essere un array i cui valori sono gli indici associati. Ricorda, i bucket dell'array possono essere vuoti. Quindi i bucket nell'array da 64 a 0 possono essere vuoti. La funzione hash aggiunge semplicemente 32 al numero di codice maiuscolo per ottenere l'indice, e quindi la lettera minuscola. Tale funzione è una perfetta funzione di hash.

Hashing da numeri interi a indici interi

Esistono diversi metodi per l'hashing di interi. Uno di questi è chiamato Metodo di divisione Modulo (Funzione).

La funzione di hashing della divisione Modulo

Una funzione in un software per computer non è una funzione matematica. In informatica (software), una funzione consiste in un insieme di istruzioni precedute da argomenti. Per la funzione Modulo Divisione, le chiavi sono numeri interi e sono mappate agli indici dell'array di bucket. Il set di chiavi è grande, quindi verranno mappate solo le chiavi che molto probabilmente si verificano nell'attività. Quindi si verificano collisioni quando le chiavi improbabili devono essere mappate.

Nella dichiarazione,

20 / 6 = 3R2

20 è il dividendo, 6 è il divisore e 3 resto 2 è il quoziente. Il resto 2 è anche chiamato modulo. Nota: è possibile avere un modulo di 0.

Per questo hashing, la dimensione della tabella è solitamente una potenza di 2, e.g. 64 = 26 o 256 = 28, eccetera.  Il divisore per questa funzione di hashing è un numero primo vicino alla dimensione dell'array. Questa funzione divide la chiave per il divisore e restituisce il modulo. Il modulo è l'indice dell'array di bucket. Il valore associato nel bucket è un valore di tua scelta (valore per la chiave).

Hashing chiavi a lunghezza variabile

Qui, le chiavi del set di chiavi sono testi di diverse lunghezze. Diversi numeri interi possono essere memorizzati nella memoria utilizzando lo stesso numero di byte (la dimensione di un carattere inglese è un byte). Quando chiavi diverse hanno dimensioni di byte diverse, si dice che siano di lunghezza variabile. Uno dei metodi per l'hashing delle lunghezze variabili è chiamato Radix Conversion Hashing.

Hashing di conversione radicale

In una stringa, ogni carattere nel computer è un numero. In questo metodo,

Codice hash (indice) = x0unk-1+X1unk-2+… +xk-2un1+Xk-1un0

Dove (x0, x1,… , xk−1) sono i caratteri della stringa di input e a è una radice, e.g. 29 (vedi oltre). k è il numero di caratteri nella stringa. C'è di più in questo - vedi dopo.

Chiavi e valori

In una coppia chiave/valore, un valore potrebbe non essere necessariamente un numero o un testo. Può anche essere un record. Un record è un elenco scritto orizzontalmente. In una coppia chiave/valore, ogni chiave potrebbe effettivamente fare riferimento a qualche altro testo, numero o record.

Array associativo

Un elenco è una struttura dati, in cui gli elementi dell'elenco sono correlati e c'è un insieme di operazioni che operano sull'elenco. Ogni elemento dell'elenco può essere costituito da una coppia di elementi. La tabella hash generale con le sue chiavi può essere considerata come una struttura dati, ma è più un sistema che una struttura dati. Le chiavi e i loro valori corrispondenti non sono molto dipendenti l'uno dall'altro. Non sono molto imparentati tra loro.

D'altra parte, un array associativo è una cosa simile, ma le chiavi ei loro valori sono molto dipendenti l'uno dall'altro; sono molto legati tra loro. Ad esempio, puoi avere una serie associativa di frutti e i loro colori. Ogni frutto ha naturalmente il suo colore. Il nome del frutto è la chiave; il colore è il valore. Durante l'inserimento, ogni chiave viene inserita con il suo valore. Quando si elimina, ogni chiave viene eliminata con il suo valore.

Un array associativo è una struttura dati di una tabella hash composta da coppie chiave/valore, dove non ci sono duplicati per le chiavi. I valori possono avere duplicati. In questa situazione le chiavi fanno parte della struttura. Cioè, le chiavi devono essere memorizzate, mentre, con la tabella hast generale, le chiavi non devono essere memorizzate. Il problema dei valori duplicati è naturalmente risolto dagli indici dell'array di bucket. Non confondere tra valori duplicati e collisione in un indice.

Poiché un array associativo è una struttura dati, ha almeno le seguenti operazioni:

Operazioni di array associativo

inserisci o aggiungi

Questo inserisce una nuova coppia chiave/valore nella raccolta, mappando la chiave al suo valore.

riassegnare

Questa operazione sostituisce il valore di una chiave particolare con un nuovo valore.

eliminare o rimuovere

Questo rimuove una chiave più il suo valore corrispondente.

consultare

Questa operazione cerca il valore di una particolare chiave e restituisce il valore (senza rimuoverlo).

Conclusione

Una struttura dati di una tabella hash è costituita da un array e da una funzione. La funzione è chiamata funzione hash. La funzione mappa le chiavi ai valori nell'array attraverso gli indici dell'array. Le chiavi non devono necessariamente essere parte della struttura dati. Il set di chiavi è solitamente più grande dei valori memorizzati. Quando si verifica una collisione, viene risolta mediante l'approccio concatenamento separato o l'approccio con indirizzamento aperto. Un array associativo è un caso speciale della struttura dati della tabella hash.

Come mostrare il contatore FPS nei giochi Linux
I giochi Linux hanno avuto una grande spinta quando Valve ha annunciato il supporto Linux per il client Steam e i loro giochi nel 2012. Da allora, mol...
Come scaricare e giocare a Sid Meier's Civilization VI su Linux
Introduzione al gioco Civilization 6 è una versione moderna del concetto classico introdotto nella serie dei giochi Age of Empires. L'idea era abbasta...
Come installare e giocare a Doom su Linux
Introduzione a Doom La serie Doom è nata negli anni '90 dopo l'uscita dell'originale Doom. È stato un successo immediato e da quel momento in poi la s...