Come scegliere tra una tabella hash e un trie (albero dei prefissi)?


134

Quindi, se devo scegliere tra una tabella di hash o un albero di prefissi quali sono i fattori discriminanti che mi porterebbero a scegliere l'uno rispetto all'altro. Dal mio ingenuo punto di vista sembra che l'uso di un trie abbia un sovraccarico in più poiché non è archiviato come un array ma che in termini di tempo di esecuzione (supponendo che la chiave più lunga sia la parola inglese più lunga) può essere essenzialmente O (1) (in relazione al limite superiore). Forse la parola inglese più lunga è di 50 caratteri?

Una volta ottenuto l'indice, le tabelle hash vengono cercate immediatamente . Hashing la chiave per ottenere l'indice sembra tuttavia che potrebbe facilmente richiedere quasi 50 passaggi.

Qualcuno può fornirmi una prospettiva più esperta su questo? Grazie!


1
Vale la pena notare che un albero redix è più efficiente di un semplice trie perché non è necessario un nuovo ramo per ogni byte di stringa. Inoltre, gli alberi redix forniscono supporto per le ricerche "fuzzy" meglio delle tabelle hash perché stai osservando singoli bit quando lavori lungo il percorso. Ad esempio 00110010potrebbe essere il byte di input, ma si desidera includere la corrispondenza 00111010che viene rimossa solo per un bit.
Xeoncross,

Risposte:


116

Vantaggi dei tentativi:

Le basi:

  • Tempo di ricerca O (k) prevedibile in cui k è la dimensione della chiave
  • La ricerca può richiedere meno di k tempo se non è presente
  • Supporta l'attraversamento ordinato
  • Non c'è bisogno di una funzione hash
  • La cancellazione è semplice

Nuove operazioni:

  • Puoi cercare rapidamente prefissi di chiavi, enumerare tutte le voci con un determinato prefisso, ecc.

Vantaggi della struttura collegata:

  • Se ci sono molti prefissi comuni, lo spazio richiesto è condiviso.
  • I tentativi immutabili possono condividere la struttura. Invece di aggiornare un trie in atto, puoi crearne uno nuovo diverso solo lungo un ramo, altrove puntando al vecchio trie. Questo può essere utile per la concorrenza, più versioni simultanee di una tabella, ecc.
  • Un trie immutabile è comprimibile. Cioè, può condividere anche la struttura sui suffissi , mediante hash-consing.

Vantaggi degli hashtabili:

  • Tutti conoscono gli hashtable, giusto? Il tuo sistema avrà già un'implementazione ben ottimizzata, più veloce di quella per la maggior parte degli scopi.
  • Le tue chiavi non devono avere alcuna struttura speciale.
  • Più efficiente in termini di spazio rispetto all'ovvia struttura trie collegata ( vedi commenti sotto )

27
non può essere completamente d'accordo con "Più efficiente in termini di spazio rispetto all'ovvia struttura a trie collegata" - in un'implementazione generale della tabella hash, occupa uno spazio molto più grande per contenere le chiavi, mentre nei tentativi ogni nodo rappresenta una parola. In questo senso, i tentativi sono più efficienti in termini di spazio.
galactica,

1
che ne dici di accedere ai dati da una struttura rispetto all'altra? Sto pensando cache e posizione
Horia Toma

8
@galactica, che è in conflitto con la mia esperienza: ad esempio, in questa risposta di tutte le strutture che ho misurato per lo spazio, un trie è andato peggio. Questo ha senso poiché un puntatore è molto più grande di un byte. Sì, la condivisione dei prefissi aiuta, ma deve superare molte spese generali per raggiungere la parità. Una rappresentazione più efficiente in termini di spazio può aiutare molto, ma non stiamo più parlando dell'ovvia struttura collegata.
Darius Bacon,

1
@DariusBacon che gestisce i piani di numerazione telefonica sembra uno scenario ragionevole per i tentativi. Scenario di esempio: numero di telefono corrispondente al gestore telefonico incl. numeri trasferiti da un vettore all'altro. Per i soliti dizionari potrebbe dipendere dalla lingua (mandarino vs inglese), avresti bisogno di n-grammi e / o altri dati statistici. Per un libro di rime, un albero di suffisso sembra anche una buona opzione.
mbx,

La diversità dei dati da cercare è molto importante. Se una grande percentuale dei valori dei dati è unica, la complessità dello spazio aumenterà sull'hash a causa dell'utilizzo di puntatori null aggiuntivi.
Apprendimento delle statistiche con l'esempio del

45

Tutto dipende dal problema che stai cercando di risolvere. Se tutto ciò che devi fare sono inserimenti e ricerche, scegli una tabella hash. Se è necessario risolvere problemi più complessi come le query relative ai prefissi, un trie potrebbe essere la soluzione migliore.


8
se la tabella hash e il trie hanno la stessa complessità nella query, O (k) per la stringa di lunghezza k perché dovremmo andare per l'hash? potresti spiegare per favore?
Sazzad Hissain Khan,

29

Tutti conoscono la tabella hash e i suoi usi ma non è esattamente un tempo di ricerca costante, dipende da quanto è grande la tabella hash, dalla complessità computazionale della funzione hash.

La creazione di enormi tabelle hash per una ricerca efficiente non è una soluzione elegante nella maggior parte degli scenari industriali in cui contano anche piccole latenze / scalabilità (ad esempio: trading ad alta frequenza). Devi preoccuparti delle strutture di dati da ottimizzare per lo spazio che occupa anche in memoria per ridurre la mancanza di cache.

Un ottimo esempio in cui trie soddisfa meglio i requisiti è il middleware di messaggistica. Hai un milione di abbonati ed editori di messaggi in varie categorie (in termini JMS - Argomenti o scambi), in questi casi se vuoi filtrare i messaggi in base agli argomenti (che sono in realtà stringhe), non vuoi assolutamente creare una tabella hash per il milione di abbonamenti con milioni di argomenti. Un approccio migliore è archiviare gli argomenti in trie, quindi quando il filtro viene eseguito in base alla corrispondenza degli argomenti, la sua complessità è indipendente dal numero di argomenti / sottoscrizioni / editori (dipende solo dalla lunghezza della stringa). Mi piace perché puoi essere creativo con questa struttura di dati per ottimizzare i requisiti di spazio e quindi avere una mancanza di cache inferiore.


11

Usa un albero:

  1. Se hai bisogno della funzione di completamento automatico
  2. Trova tutte le parole che iniziano con "a" o "ascia" e così via.
  3. Un albero di suffisso è una forma speciale di un albero. Gli alberi dei suffissi hanno un intero elenco di vantaggi che l'hash non può coprire.

4

C'è qualcosa che non ho visto nessuno menzionare esplicitamente che penso sia importante tenere a mente. Sia le tabelle hash che i tentativi di vario tipo avranno tipicamente O(k)operazioni, dove kè la lunghezza della stringa in bit (o equivalentemente in caratteri).

Questo presuppone che tu abbia una buona funzione hash. Se non vuoi che "fattoria" e "animali da fattoria" abbiano l'hash sullo stesso valore, allora la funzione hash dovrà usare tutti i bit della chiave, e quindi l'hash "animali da fattoria" dovrebbe richiedere circa il doppio del tempo "farm" (a meno che non ci si trovi in ​​una sorta di scenario di hash rolling, ma ci sono anche scenari simili per il salvataggio delle operazioni con try). E con un trie alla vaniglia, è chiaro il motivo per cui l'inserimento di "animali da fattoria" richiederà circa il doppio rispetto a "fattoria". A lungo termine è vero anche con i tentativi compressi.


3

L'inserimento e la ricerca su un trie sono lineari con la lunghezza della stringa di input O (s).

Un hash ti darà una O (1) per la ricerca e l'inserimento, ma prima devi calcolare l'hash in base alla stringa di input che è di nuovo O (s).

Concludendo, la complessità temporale asintotica è lineare in entrambi i casi.

Il trie ha un certo sovraccarico dal punto di vista dei dati, ma puoi scegliere un trie compresso che ti metterà di nuovo, più o meno in pareggio con la tabella hash.

Per spezzare il pareggio, poniti questa domanda: devo cercare solo parole intere? O devo restituire tutte le parole corrispondenti a un prefisso? (Come in un sistema di scrittura intuitivo). Per il primo caso, scegli un hash. È un codice più semplice e più pulito. Più facile da testare e mantenere. Per un caso d'uso più elaborato in cui i prefissi o i suffissi contano, scegli un trie.

E se lo fai solo per divertimento, l'implementazione di un trie renderebbe utile una domenica pomeriggio.


"Un hash ti darà una O (1) per la ricerca e l'inserimento, ma prima devi calcolare l'hash in base alla stringa di input che è di nuovo O (s)." Grazie per aver spiegato questo!
abadawi,

Il calcolo della funzione hash non è O (s). In realtà è O (1). Non hai bisogno di tutti i bit della stringa per calcolarla, alcuni di essi (un numero costante di essi) è sufficiente.
Nicola Amadio

2

L' implementazione di HashTable è efficiente in termini di spazio rispetto alla base all'implementazione Trie . Ma con le stringhe, l'ordinamento è necessario nella maggior parte delle applicazioni pratiche. Ma HashTable disturba totalmente l'ordine lessicale. Ora, se la tua applicazione sta eseguendo operazioni basate sull'ordine lessicale (come la ricerca parziale, tutte le stringhe con prefisso specificato, tutte le parole in ordine ordinato), dovresti usare Tries. Per la sola ricerca, è necessario utilizzare HashTable (come probabilmente, fornisce un tempo di ricerca minimo).

PS: Oltre a questi, Ternary Search Trees (TST) sarebbe una scelta eccellente. Il tempo di ricerca è più che HashTable, ma è efficiente in tutte le altre operazioni. Inoltre, è più efficiente in termini di spazio rispetto ai tentativi.


-2

Alcune applicazioni (generalmente incorporate, in tempo reale) richiedono che il tempo di elaborazione sia indipendente dai dati. In tal caso, una tabella hash può garantire un tempo di esecuzione noto, mentre un trie varia in base ai dati.


6
La maggior parte delle tabelle hash non garantisce un tempo di esecuzione noto - il caso peggiore è O (n), se ogni elemento si scontra e viene incatenato
Adam Rosenfield,

2
Per qualsiasi set di dati, è possibile calcolare una funzione hash perfetta che garantirà le ricerche O (1) per tali dati. Naturalmente, calcolare l'hash perfetto non è gratuito.
George V. Reilly,

5
Inoltre, il concatenamento non è l'unico modo per gestire le collisioni; ci sono molti modi interessanti e intelligenti per gestire questo — hashing del cuculo ( en.wikipedia.org/wiki/Cuckoo_hashing ) per uno — e la scelta migliore dipende dalle esigenze del codice client.
Hank Gay,

non sapevo dell'hashing del cuculo e della sua relazione con il filtro bloom, renderà interessante la lettura, grazie!
Horia Toma,

Non dimenticare Hashing Robin Hood, che è superiore per cache e varianza. sebastiansylvan.com/2013/05/08/… codecapsule.com/2013/11/11/robin-hood-hashing
Jarred Nicholls
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.