Tabelle di hash contro alberi binari


30

Quando si implementa un dizionario ("Voglio cercare i dati dei clienti in base ai loro ID cliente"), le strutture di dati tipiche utilizzate sono tabelle hash e alberi di ricerca binari. So ad esempio che la libreria C ++ STL implementa dizionari (li chiamano mappe) usando alberi di ricerca binaria (bilanciata) e il framework .NET utilizza tabelle hash sotto il cofano.

Quali sono i vantaggi e gli svantaggi di queste strutture di dati? C'è qualche altra opzione che è ragionevole in determinate situazioni?

Nota che non sono particolarmente interessato ai casi in cui le chiavi hanno una forte struttura sottostante, diciamo, sono tutti numeri interi compresi tra 1 e n o qualcosa del genere.


1
Ti esaspererò, ma non puoi semplicemente dire "numeri interi tra 1 e n" poiché in quel caso un array supererà tutte le altre strutture di dati :-). "Stringhe" sembra giusto e copre la maggior parte delle situazioni.
jmad

@jmad ha detto che non è interessato a quel caso.
Joe

@Joe Ho pensato che fosse chiaro che l'ho preso in considerazione. Comunque non è un motivo per dare il peggior esempio possibile di chiave.
jmad

1
In realtà .NET ha implementato entrambi i dizionari usando alberi e dizionari implementati usando tabelle hash (e così fa C ++ dallo standard 2011).
sepp2k,

Risposte:


26

Un intero trattato potrebbe essere scritto su questo argomento; Tratterò solo alcuni punti salienti e terrò al minimo la discussione su altre strutture di dati (in effetti ci sono molte varianti). In tutta questa risposta, è il numero di chiavi nel dizionario.n

La risposta breve è che le tabelle hash sono più veloci nella maggior parte dei casi , ma possono essere molto peggiori. Gli alberi di ricerca hanno molti vantaggi, tra cui il comportamento nel caso peggiore , ma in alcuni casi sono più lenti.

Gli alberi di ricerca binaria bilanciata hanno una complessità abbastanza uniforme: ogni elemento prende un nodo nell'albero (in genere 4 parole di memoria) e le operazioni di base (ricerca, inserimento, cancellazione) impiegano il tempo (asintotico garantito) limite superiore). Più precisamente, un accesso nell'albero dura circa l o g 2 ( n ) confronti.O(lg(n))log2(n)

Le tabelle hash sono leggermente più variabili. Richiedono un array di circa puntatori. L'accesso a un elemento dipende dalla qualità della funzione hash. Lo scopo di una funzione hash è di disperdere gli elementi. Una tabella hash "funziona" se tutti gli elementi che si desidera archiviare hanno hash diversi. In questo caso, le operazioni di base (ricerca, inserimento, eliminazione) richiedono O ( 1 ) tempo, con una costante abbastanza piccola (un calcolo dell'hash più una ricerca del puntatore). Questo rende le tabelle hash molto veloci in molti casi tipici.2nO(1)

Un problema generale con le tabelle hash è che la complessità non è garantita.O(1)

  • Inoltre, c'è un punto in cui la tabella diventa piena; quando ciò accade (o meglio, poco prima che accada), la tabella deve essere ingrandita, il che richiede di spostare tutti i suoi elementi, per un costo . Questo può introdurre un comportamento "a scatti" quando vengono aggiunti molti elementi.O(n)
  • O(1)

Quando si inserisce la localizzazione dei dati nel mix, le tabelle di hash funzionano male. Funzionano proprio perché memorizzano elementi correlati distanti, il che significa che se l'applicazione cerca elementi che condividono un prefisso in sequenza, non trarrà beneficio dagli effetti cache. Ciò non è rilevante se l'applicazione effettua ricerche essenzialmente casuali.

Un altro fattore a favore degli alberi di ricerca è che sono una struttura di dati immutabile : se devi prendere una copia di un albero e modificarne alcuni elementi, puoi condividere la maggior parte della struttura di dati. Se si prende una copia di una tabella hash, è necessario copiare l'intero array di puntatori. Inoltre, se lavori in linguaggi puramente funzionali, le tabelle hash spesso non sono un'opzione.

K1K2h(K1)=h(K2)

In particolare, se hai bisogno dell'ordine delle chiavi, ad esempio se vuoi essere in grado di elencare le chiavi in ​​ordine alfabetico, le tabelle hash non sono di aiuto (dovrai ordinarle), mentre tu può attraversare direttamente un albero di ricerca in ordine.

È possibile combinare alberi di ricerca binari e tabelle hash sotto forma di alberi hash . Un albero hash memorizza le chiavi in ​​un albero di ricerca in base al loro hash. Ciò è utile, ad esempio, in un linguaggio di programmazione puramente funzionale in cui si desidera lavorare su dati che non hanno una relazione d'ordine facile da calcolare.

Quando le chiavi sono stringhe (o numeri interi), un trie può essere un'altra opzione. Un trie è un albero, ma indicizzato in modo diverso da un albero di ricerca: scrivi la chiave in binario e vai a sinistra per uno 0 e a destra per uno 1. Il costo di un accesso è quindi proporzionale alla lunghezza della chiave. I tentativi possono essere compressi per rimuovere nodi intermedi; questo è noto come patricia trie o radix tree . Gli alberi Radix possono sovraperformare gli alberi bilanciati, in particolare quando molte chiavi condividono un prefisso comune.


2
Anche i BST non hanno una localizzazione dei dati errata?
svick

@svick Possono o no, a seconda di come sono allocati i nodi. Aumentare l'arità dell'albero può aiutare senza compromettere il tempo di esecuzione (il costo è un codice più grande e più complesso).
Gilles 'SO- smetti di essere malvagio' il

2
Su un BST è facile mettere gli elementi "in ordine", per una tabella di hash è fuori discussione.
vonbrand

Oltre che per motivi di sicurezza, perché è importante se le tabelle hash hanno un brutto momento nel caso peggiore se il loro caso medio è migliore di quello degli alberi binari? Immagino che la convenienza utilità / utente abbia una relazione approssimativamente lineare con quanto tempo impiega l'albero a terminare, quindi il valore atteso (medio) dovrebbe essere tutto ciò che conta.
Kelmikra,

@ Kyth'Py1k Cosa intendi con "l'albero da finire"? Il punto delle tabelle hash è accedere a un valore alla volta, non all'intero albero, altrimenti un elenco o un array funzionerebbe meglio. Anche nelle situtations in cui è importante il valore medio (che non è sempre il caso, ad esempio quando si hanno vincoli in tempo reale), è la media rispetto alle richieste effettuate in una determinata situazione, che spesso non sono affatto uniformi sul tavolo - ad es. Distorto per un determinato prefisso.
Gilles 'SO- smetti di essere malvagio'
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.