Vantaggi degli alberi di ricerca binari rispetto alle tabelle hash


101

Quali sono i vantaggi degli alberi di ricerca binari rispetto alle tabelle hash?

Le tabelle hash possono cercare qualsiasi elemento nel tempo Theta (1) ed è altrettanto facile aggiungere un elemento ... ma non sono sicuro che i vantaggi vadano al contrario.


per le tabelle hash quali sono i tempi di esecuzione di find (), insert () e remove ()? theta (1) theta (1) e theta (1) giusto?
Dedicato l'

8
Quasi sempre, sì. Se incontri molte collisioni, quei tempi potrebbero crescere fino a O (n).
Christian Mann

1
Questi tempi dipendono anche dalla tua funzione di hashing. Se per qualche strana ragione non è O (1), ovviamente le tue operazioni avranno un limite minimo dell'efficienza in cui viene eseguita la tua funzione hash.
Christian Mann

Direi che i maggiori vantaggi di BST è che è in una struttura dati ordinata. Caso d'uso dettagliato già elencato qui .
Yuantao

Risposte:


93

Ricorda che gli alberi di ricerca binari (basati sui riferimenti) sono efficienti in termini di memoria. Non riservano più memoria del necessario.

Ad esempio, se una funzione hash ha un intervallo R(h) = 0...100, è necessario allocare un array di 100 elementi (puntatori a), anche se si stanno solo eseguendo l'hashing di 20 elementi. Se dovessi utilizzare un albero di ricerca binario per memorizzare le stesse informazioni, allocherai solo lo spazio di cui hai bisogno, oltre ad alcuni metadati sui collegamenti.


33
Non è vero che l'intera gamma di output della funzione hash deve esistere nell'array. I valori hash possono essere semplicemente modificati dalla lunghezza dell'array per consentire un array più piccolo. Ovviamente, il numero finale di elementi aggiunti potrebbe non essere noto, quindi la tabella hash potrebbe comunque allocare più spazio del necessario. Tuttavia, gli alberi di ricerca binari possono sprecare altrettanto o più memoria. Le implementazioni collegate richiedono spazio per almeno due puntatori aggiuntivi per elemento (tre se si utilizza un puntatore genitore) e i BST basati su array possono sprecare molta memoria per parti dell'albero non riempite.
Solaraeus

4
@ Solaraeus: i BST basati su array sono i migliori da confrontare con le tabelle hash e non sono più dispendiosi delle tabelle hash. È inoltre possibile espandere un BST con poco più di una copia in memoria, rispetto al ricalcolo dell'intera tabella.
Guvante

125

Un vantaggio che nessun altro ha sottolineato è che l'albero di ricerca binario consente di eseguire ricerche per intervallo in modo efficiente.

Per illustrare la mia idea, voglio fare un caso estremo. Supponiamo di voler ottenere tutti gli elementi le cui chiavi sono comprese tra 0 e 5000. E in realtà c'è solo un elemento di questo tipo e 10000 altri elementi le cui chiavi non sono nell'intervallo. BST può eseguire ricerche per intervallo in modo abbastanza efficiente poiché non cerca una sottostruttura che è impossibile avere la risposta.

Mentre, come puoi eseguire ricerche di intervallo in una tabella hash? O devi iterare ogni spazio del bucket, che è O (n), oppure devi cercare se ciascuno di 1,2,3,4 ... fino a 5000 esiste. (che dire delle chiavi comprese tra 0 e 5000 sono un insieme infinito? Ad esempio le chiavi possono essere decimali)


11
I BST eseguono ricerche di distanza in modo efficiente! Per me questa è la migliore risposta in termini di approccio pratico e algoritmico.
ady

4
wow, questo spiega davvero perché gli alberi sono così associati ai database; i loro vantaggi sono più visibili quando è necessario eseguire filtri basati su chiavi. con le mappe hash, è necessario eseguire il ciclo su tutte le chiavi per risolvere "trova tutti gli elementi con chiave tra 1000 e 3290"
Dmitry

77

Un "vantaggio" di un albero binario è che può essere attraversato per elencare tutti gli elementi in ordine. Questo non è impossibile con una tabella hash, ma non è un'operazione normale un design in una struttura hash.


3
attraversare in qualsiasi ordine probabilmente non avrebbe alcun senso su una tabella hash.
FrustratedWithFormsDesigner

2
@FrustratedWithFormsDesigner. Vedi tabella hash lineare
ordinata

Grazie per il collegamento, è un'idea intersecante! Non credo di aver mai visto o usato un'implementazione di questo (almeno non consapevolmente).
FrustratedWithFormsDesigner


51

Oltre a tutti gli altri buoni commenti:

Le tabelle hash in generale hanno un comportamento della cache migliore che richiede meno letture di memoria rispetto a un albero binario. Per una tabella hash normalmente si incorre in una sola lettura prima di avere accesso a un riferimento contenente i dati. L'albero binario, se è una variante bilanciata, richiede qualcosa nell'ordine di k * lg (n) letture di memoria per qualche costante k.

D'altra parte, se un nemico conosce la tua funzione hash, il nemico può forzare la tua tabella hash per creare collisioni, ostacolando notevolmente le sue prestazioni. La soluzione è scegliere la funzione hash in modo casuale da una famiglia, ma un BST non ha questo svantaggio. Inoltre, quando la pressione della tabella hash aumenta troppo, spesso si tende ad allargare e riallocare la tabella hash, operazione che potrebbe essere costosa. Il BST ha un comportamento più semplice qui e non tende ad allocare improvvisamente molti dati e ad eseguire un'operazione di rehashing.

Gli alberi tendono ad essere la struttura dati media definitiva. Possono agire come elenchi, possono essere facilmente suddivisi per operazioni parallele, avere una rimozione, un inserimento e una ricerca rapidi nell'ordine O (lg n) . Non fanno niente di particolarmente bene, ma non hanno nemmeno un comportamento eccessivamente cattivo.

Infine, i BST sono molto più facili da implementare in linguaggi funzionali (puri) rispetto alle tabelle hash e non richiedono aggiornamenti distruttivi per essere implementati (l' argomento di persistenza di Pascal sopra).


3
BSTs are much easier to implement in (pure) functional languages compared to hash-tables- veramente? Voglio imparare un linguaggio funzionale adesso!
nawfal

1
La tabella hash deve essere persistente in un linguaggio funzionale. Questo spesso complica le implementazioni.
HO DATO CRAP RISPOSTE

per elaborare, se crei strutture dati del presidente in linguaggi funzionali, tutto ciò che finisci per fare è scrivere lo stesso codice che faresti in assembly, tranne in ogni operazione che trasformi esplicitamente il tuo array di memoria / registri, o parli con un server per fingere fare quello. Im tutto per essere consapevole del tuo stato ma è isomorfo all'approccio imperativo se fatto correttamente (non puoi copiare realisticamente una grande quantità di dati su ogni trasformazione nella vita reale, devi imbrogliare).
Dmitry

27

I principali vantaggi di un albero binario rispetto a una tabella hash è che l'albero binario ti offre due operazioni aggiuntive che non puoi fare (facilmente, rapidamente) con una tabella hash

  • trova l'elemento più vicino a (non necessariamente uguale a) un valore chiave arbitrario (o il più vicino sopra / sotto)

  • scorrere i contenuti dell'albero in ordine ordinato

I due sono collegati: l'albero binario mantiene i suoi contenuti in un ordine ordinato, quindi le cose che richiedono quell'ordine ordinato sono facili da fare.


BST trova la corrispondenza più vicina, solo se la corrispondenza esatta non esiste, giusto? Cosa succede se trovi una corrispondenza esatta nella radice stessa?
developer747

2
@ developer747: Quindi il successivo più vicino in basso e in alto sono la foglia più a destra della sottostruttura sinistra e la foglia più a sinistra della sottostruttura destra.
Chris Dodd

16

Un albero di ricerca binario (bilanciato) ha anche il vantaggio che la sua complessità asintotica è in realtà un limite superiore, mentre i tempi "costanti" per le tabelle hash sono tempi ammortizzati: se hai una funzione hash non adatta, potresti finire per degradare al tempo lineare , piuttosto che costante.


3
Per portare a casa questo punto, un caso degenere è quando la raccolta contiene molte copie di una sola chiave. nel BST, l'inserimento è O (log n), in una tabella hash, l'inserimento è O (n)
SingleNegationElimination

2
Quando una tabella hash contiene molte copie di una sola chiave, insert è (ancora) O (1), non O (n). Il problema per le tabelle hash è quando ci sono molte chiavi diverse con lo stesso hash. Ciò può essere evitato con uno schema hash dinamico che passa a una funzione hash diversa quando ci sono molte collisioni.
Chris Dodd

Nota che un albero sbilanciato può degenerare in un elenco e avere anche una ricerca O (n).
awiebe

9

Una tabella hash occuperebbe più spazio quando viene creata per la prima volta: avrà slot disponibili per gli elementi che devono ancora essere inseriti (indipendentemente dal fatto che vengano inseriti o meno), un albero di ricerca binario sarà grande quanto necessario essere. Inoltre, quando una tabella hash necessita di più spazio, espandersi a un'altra struttura potrebbe richiedere molto tempo, ma ciò potrebbe dipendere dall'implementazione.


8

Un albero di ricerca binario può essere implementato con un'interfaccia persistente , in cui viene restituito un nuovo albero ma il vecchio albero continua ad esistere. Implementati con attenzione, il vecchio e il nuovo albero condividono la maggior parte dei loro nodi. Non puoi farlo con una tabella hash standard.


6

Un albero binario è più lento da cercare e inserire, ma ha la caratteristica molto interessante dell'attraversamento dell'infisso che essenzialmente significa che puoi iterare attraverso i nodi dell'albero in un ordine ordinato.

Iterare le voci di una tabella hash non ha molto senso perché sono tutte sparse nella memoria.


6

Da Cracking the Coding Interview, 6a edizione

Possiamo implementare la tabella hash con un albero di ricerca binario bilanciato (BST). Questo ci dà un tempo di ricerca O (log n). Il vantaggio di ciò è potenzialmente utilizzare meno spazio, poiché non allochiamo più un array di grandi dimensioni. Possiamo anche scorrere i tasti in ordine, il che a volte può essere utile.


5

I BST forniscono anche le operazioni "findPredecessor" e "findSuccessor" (per trovare gli elementi successivi più piccoli e successivi più grandi) in tempo O (logn), che potrebbero anche essere operazioni molto utili. Hash Table non può fornire efficienza in quel tempo.


Se stai cercando operazioni "findPredecessor" e "findSuccessor", HashTable è una cattiva scelta per la struttura dei dati in primo luogo.
AKDesai

1

Se si desidera accedere ai dati in modo ordinato, è necessario mantenere un elenco ordinato in parallelo alla tabella hash. Un buon esempio è Dictionary in .Net. (vedi http://msdn.microsoft.com/en-us/library/3fcwy8h6.aspx ).

Questo ha l'effetto collaterale non solo di rallentare gli inserimenti, ma consuma una maggiore quantità di memoria rispetto a un b-tree.

Inoltre, poiché un b-tree è ordinato, è semplice trovare intervalli di risultati o eseguire unioni o unioni.


1

Dipende anche dall'uso, Hash permette di individuare la corrispondenza esatta. Se si desidera eseguire una query per un intervallo, BST è la scelta. Supponiamo di avere molti dati e1, e2, e3 ..... en.

Con la tabella hash puoi localizzare qualsiasi elemento in tempo costante.

Se desideri trovare valori di intervallo maggiori di e41 e minori di e8, BST può trovarli rapidamente.

La cosa fondamentale è la funzione hash utilizzata per evitare una collisione. Ovviamente non possiamo evitare totalmente una collisione, nel qual caso ricorriamo al concatenamento o ad altri metodi. Ciò rende il recupero non più tempo costante nei casi peggiori.

Una volta piena, la tabella hash deve aumentare la dimensione del bucket e copiare nuovamente tutti gli elementi. Questo è un costo aggiuntivo non presente rispetto a BST.


1

Le tabelle hash non sono adatte per l'indicizzazione. Quando cerchi un intervallo, i BST sono migliori. Questo è il motivo per cui la maggior parte degli indici di database utilizza alberi B + invece di tabelle hash


gli indici dei database sono di entrambi i tipi hash e B + tree. Quando vuoi fare un confronto come maggiore o minore di, allora l'indice degli alberi B + è utile altrimenti l'indice hash è utile per la ricerca. Pensa anche a quando i dati non sono confrontabili e se vuoi creare un indice, allora db creerà un indice hash e non un indice albero B +. @ssD
Sukhmeet Singh,

1

Gli alberi di ricerca binari sono una buona scelta per implementare il dizionario se le chiavi hanno un ordine totale (le chiavi sono comparabili) definito su di esse e si desidera preservare le informazioni sull'ordine.

Poiché BST conserva le informazioni sull'ordine, fornisce quattro ulteriori operazioni di impostazione dinamica che non possono essere eseguite (in modo efficiente) utilizzando le tabelle hash. Queste operazioni sono:

  1. Massimo
  2. Minimo
  3. Successore
  4. Predecessore

Tutte queste operazioni, come ogni operazione BST, hanno una complessità temporale di O (H). Inoltre, tutte le chiavi memorizzate rimangono ordinate nel BST, consentendo così di ottenere la sequenza ordinata di chiavi semplicemente attraversando l'albero in ordine.

In sintesi, se tutto ciò che desideri sono le operazioni di inserimento, eliminazione e rimozione, la tabella hash è imbattibile (la maggior parte delle volte) in termini di prestazioni. Ma se vuoi una o tutte le operazioni sopra elencate dovresti usare un BST, preferibilmente un BST autobilanciato.


0

Il vantaggio principale della tabella hash è che esegue quasi tutte le operazioni in ~ = O (1). Ed è molto facile da capire e implementare. Risolve molti "problemi di intervista" in modo efficiente. Quindi, se vuoi decifrare un'intervista di programmazione, fai amicizia con la tabella hash ;-)


Penso che l'OP abbia chiesto vantaggi del BST rispetto all'hashing.
Sniper

0

Una hashmap è un array associativo impostato. Quindi, la tua matrice di valori di input viene raggruppata in bucket. In uno schema di indirizzamento aperto, hai un puntatore a un bucket e ogni volta che aggiungi un nuovo valore in un bucket, scopri dove ci sono spazi liberi nel bucket. Ci sono alcuni modi per farlo: si inizia dall'inizio del bucket e si incrementa il puntatore ogni volta e si verifica se è occupato. Questo è chiamato rilevamento lineare. Quindi, puoi fare una ricerca binaria come add, dove raddoppi la differenza tra l'inizio del bucket e dove raddoppi o riduci ogni volta che cerchi uno spazio libero. Questo è chiamato sondaggio quadratico. OK. Ora il problema in entrambi questi metodi è che se il bucket trabocca nell'indirizzo del bucket successivo, allora devi-

  1. Raddoppia ogni dimensione dei bucket - malloc (N bucket) / cambia la funzione hash - Tempo richiesto: dipende dall'implementazione di malloc
  2. Trasferisci / Copia ciascuno dei dati dei bucket precedenti nei dati dei nuovi bucket. Questa è un'operazione O (N) in cui N rappresenta l'intero dato

OK. ma se usi una lista collegata non dovrebbe esserci un problema del genere, giusto? Sì, negli elenchi collegati non hai questo problema. Considerando che ogni bucket inizia con un elenco collegato, e se hai 100 elementi in un bucket, ti richiede di attraversare quei 100 elementi per raggiungere la fine dell'elenco collegato, quindi List.add (Elemento E) richiederà tempo per-

  1. Hash l'elemento su un bucket: normale come in tutte le implementazioni
  2. Prenditi del tempo per trovare l'ultimo elemento in detta operazione bucket-O (N).

Il vantaggio dell'implementazione della lista collegata è che non è necessaria l'operazione di allocazione della memoria e il trasferimento / copia O (N) di tutti i bucket come nel caso dell'implementazione di indirizzamento aperto.

Quindi, il modo per ridurre al minimo l'operazione O (N) è convertire l'implementazione in quella di un albero di ricerca binario in cui le operazioni di ricerca sono O (log (N)) e aggiungere l'elemento nella sua posizione in base al suo valore. La caratteristica aggiunta di un BST è che viene ordinato!


0

Gli alberi di ricerca binari possono essere più veloci se usati con chiavi stringa. Soprattutto quando le stringhe sono lunghe.

Alberi di ricerca binari che utilizzano confronti per minore / maggiore che sono veloci per le stringhe (quando non sono uguali). Quindi un BST può rispondere rapidamente quando una stringa non viene trovata. Quando viene trovato, sarà necessario eseguire un solo confronto completo.

In una tabella hash. Devi calcolare l'hash della stringa e questo significa che devi passare attraverso tutti i byte almeno una volta per calcolare l'hash. Poi di nuovo, quando viene trovata una voce corrispondente.

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.