Menu
Coddy logo textTech

Hash table (tabella hash)

Ultimo aggiornamento

Una tabella hash memorizza gli elementi in un array di bucket. Per collocare o trovare una chiave, la passa a una funzione di hash e prende il risultato modulo il numero di bucket: così ottiene l'indice del bucket in O(1). Premi play qui sopra per vedere ogni chiave trasformata con l'hash in un bucket e memorizzata, poi una ricerca che salta dritta al bucket giusto.

Quando due chiavi finiscono nello stesso bucket, cioè in caso di collisione, questa visualizzazione usa il concatenamento separato: ogni bucket contiene una piccola lista e le chiavi in collisione vengono aggiunte in fondo. Finché la tabella non è troppo piena (fattore di carico basso) e l'hash distribuisce bene le chiavi, le catene restano corte e inserimento, ricerca ed eliminazione sono O(1) in media.

Complessità temporale

OperazioneMediaCaso peggiore
InserimentoO(1)O(n) (tutte le chiavi collidono)
RicercaO(1)O(n)
EliminazioneO(1)O(n)
SpazioO(n)O(n)

Concetti chiave

TermineSignificato
Funzione di hashAssocia una chiave a un indice di bucket
CollisioneDue chiavi finiscono nello stesso bucket
Concatenamento separatoOgni bucket contiene una lista delle voci in collisione
Indirizzamento apertoAlternativa: cerca la prossima posizione libera
Fattore di caricovoci / bucket: determina il ridimensionamento

Esempio svolto

Inserimento delle chiavi 20, 34, 9, 13 in una tabella con 7 bucket, usando hash(k) = k % 7:

PassoStrutturaAzione
Inserisci 20bucket 6: [20]20 % 7 = 6, bucket 6 vuoto, memorizza 20
Inserisci 34bucket 6: [20, 34]34 % 7 = 6, collisione con 20, aggiungi 34 alla catena
Inserisci 9bucket 2: [9]9 % 7 = 2, bucket 2 vuoto, memorizza 9
Inserisci 13bucket 6: [20, 34, 13]13 % 7 = 6, collisione, aggiungi 13 alla catena del bucket 6
Cerca 34bucket 6: [20, 34, 13]34 % 7 = 6, scorri la catena: 20 no, 34 corrisponde: trovato

Quando usare una tabella hash

Usala quandoEvitala quando
Ti servono inserimento, ricerca ed eliminazione per chiave O(1) in mediaTi servono le chiavi in ordine: usa un BST bilanciato
Le chiavi non hanno un ordine utile e verifichi solo l'appartenenza esattaTi servono query su intervalli o ricerche della chiave più vicina
Puoi permetterti un po' di memoria extra per i bucket e un fattore di carico bassoLa memoria è estremamente limitata e ogni byte conta
Esiste una buona funzione di hash per il tuo tipo di chiaveLa latenza nel caso peggiore deve essere limitata: le collisioni la portano a O(n)

Codice Hash Table

Un'implementazione di Hash Table pulita ed eseguibile in Python, JavaScript, Java, C++, C. Scegli un linguaggio, copia il codice o aprilo già caricato nel Playground di Coddy.

Codice Hash Table in Python

Python
1class HashTable:2    def __init__(self, size=8):3        self.size = size4        self.buckets = [[] for _ in range(size)]5
6    def _index(self, key):7        # Hash the key to a bucket; different keys can collide8        return sum(ord(ch) for ch in key) % self.size9
10    def set(self, key, value):11        bucket = self.buckets[self._index(key)]12        for i, (k, _) in enumerate(bucket):13            if k == key:14                bucket[i] = (key, value)  # update existing key15                return16        bucket.append((key, value))  # chain on collision17
18    def get(self, key):19        for k, v in self.buckets[self._index(key)]:20            if k == key:21                return v22        raise KeyError(key)23
24    def delete(self, key):25        bucket = self.buckets[self._index(key)]26        for i, (k, _) in enumerate(bucket):27            if k == key:28                del bucket[i]29                return30        raise KeyError(key)31
32
33table = HashTable()34table.set("apple", 3)35table.set("banana", 7)36table.set("cherry", 5)37
38print("apple  ->", table.get("apple"))39print("banana ->", table.get("banana"))40table.set("apple", 10)41print("apple  ->", table.get("apple"))42table.delete("banana")43print("bucket sizes:", [len(b) for b in table.buckets])
Esegui questo codice nel playground Python

Domande frequenti sulla tabella hash

Cos'è una collisione di hash e come si risolve?
Una collisione avviene quando due chiavi diverse finiscono nello stesso bucket. Le due soluzioni comuni sono il concatenamento separato (ogni bucket contiene una lista e le chiavi in collisione vengono aggiunte, come mostrato qui) e l'indirizzamento aperto (cerca la prossima posizione vuota nell'array). Entrambe mantengono corrette le ricerche; cambiano l'organizzazione in memoria e le prestazioni sotto carico elevato.
Qual è la complessità temporale di una tabella hash?
Inserimento, ricerca ed eliminazione sono O(1) in media quando la funzione di hash distribuisce le chiavi in modo uniforme e il fattore di carico resta basso. Nel caso peggiore, con tutte le chiavi in collisione in un unico bucket, degradano a O(n), ed è per questo che una buona funzione di hash e il ridimensionamento contano.
Cos'è il fattore di carico?
Il fattore di carico è il numero di voci memorizzate diviso il numero di bucket. Quando cresce, le catene si allungano e le operazioni rallentano, quindi la maggior parte delle tabelle hash si ridimensiona (ricalcola l'hash in un array più grande) quando supera una soglia come 0,75.
Quando conviene usare una tabella hash invece di un albero binario di ricerca?
Usa una tabella hash quando ti servono solo ricerche per corrispondenza esatta e vuoi una velocità O(1) in media senza requisiti di ordine. Usa un albero binario di ricerca bilanciato quando ti servono chiavi in ordine, query su intervalli o ricerche di predecessore/successore, che una tabella hash non gestisce in modo efficiente. Un BST garantisce operazioni O(log n), mentre una tabella hash rinuncia a questa garanzia in cambio di prestazioni medie migliori.
Qual è la differenza tra concatenamento separato e indirizzamento aperto?
Il concatenamento separato memorizza le chiavi in collisione in una lista per ogni bucket, quindi un bucket può contenere molte voci e la tabella non si riempie mai davvero. L'indirizzamento aperto tiene tutto nell'array stesso e, in caso di collisione, cerca la prossima posizione libera: sfrutta bene la cache ma peggiora bruscamente quando il fattore di carico si avvicina a 1 e richiede una gestione attenta delle eliminazioni. Il concatenamento tollera fattori di carico più alti; l'indirizzamento aperto usa la memoria in modo più compatto.
Perché non posso contare su una tabella hash per mantenere le chiavi in ordine di inserimento o ordinate?
Una funzione di hash sparpaglia di proposito le chiavi tra i bucket per evitare raggruppamenti, quindi l'ordine di iterazione riflette la disposizione dei bucket, non l'ordine di inserimento né quello ordinato. Se ti serve un ordine, usa una mappa ordinata/un albero o una struttura come una mappa che conserva l'ordine di inserimento (ad es. il dict di Python conserva l'ordine di inserimento, ma è una garanzia del linguaggio, non una proprietà intrinseca delle tabelle hash). Non dare mai per scontato che l'ordine di iterazione corrisponda a quello in cui hai inserito le chiavi, a meno che il linguaggio non lo prometta esplicitamente.
Illustrazione dei linguaggi di programmazione di Coddy

Padroneggia gli algoritmi con Coddy

INIZIA