Qual è la differenza tra un heap e BST?
Quando utilizzare un heap e quando utilizzare un BST?
Se vuoi ottenere gli elementi in modo ordinato, BST è meglio su heap?
Qual è la differenza tra un heap e BST?
Quando utilizzare un heap e quando utilizzare un BST?
Se vuoi ottenere gli elementi in modo ordinato, BST è meglio su heap?
Risposte:
Sommario
Type BST (*) Heap
Insert average log(n) 1
Insert worst log(n) log(n) or n (***)
Find any worst log(n) n
Find max worst 1 (**) 1
Create worst n log(n) n
Delete worst log(n) log(n)
Tutti i tempi medi su questa tabella sono gli stessi dei loro periodi peggiori, ad eccezione di Inserisci.
*
: ovunque in questa risposta, BST == BST bilanciato, poiché uno sbilanciato fa schifo asintoticamente**
: usando una banale modifica spiegata in questa risposta***
: log(n)
per heap albero puntatore, n
per heap di array dinamicoVantaggi dell'heap binario rispetto a un BST
l'inserimento del tempo medio in un heap binario è O(1)
, poiché BST lo è O(log(n))
. Questa è la caratteristica killer di heap.
Ci sono anche altri cumuli che raggiungono l' O(1)
ammortamento (più forte) come il mucchio di Fibonacci , e anche il caso peggiore, come la coda Brodal , anche se potrebbero non essere pratici a causa di prestazioni non asintotiche: i cumuli di Fibonacci o le code Brodal sono usati nella pratica ovunque?
gli heap binari possono essere implementati in modo efficiente su array dinamici o alberi basati su puntatori, BST solo alberi basati su puntatori. Quindi per l'heap possiamo scegliere l'implementazione dell'array più efficiente in termini di spazio, se possiamo permetterci latenze di ridimensionamento occasionali.
la creazione dell'heap binario è il O(n)
caso peggiore , O(n log(n))
per BST.
Vantaggio di BST rispetto all'heap binario
la ricerca di elementi arbitrari è O(log(n))
. Questa è la caratteristica killer dei BST.
Per l'heap, è O(n)
in generale, ad eccezione dell'elemento più grande che è O(1)
.
Vantaggio "falso" dell'heap rispetto a BST
heap è O(1)
trovare max, BST O(log(n))
.
Questo è un malinteso comune, perché è banale modificare un BST per tenere traccia dell'elemento più grande e aggiornarlo ogni volta che tale elemento può essere modificato: all'inserimento di uno più grande scambiare, alla rimozione trovare il secondo più grande. Possiamo usare l'albero di ricerca binario per simulare il funzionamento dell'heap? (menzionato da Yeo ).
In realtà, questa è una limitazione degli heap rispetto ai BST: l' unica ricerca efficiente è quella per l'elemento più grande.
L'inserto heap binario medio è O(1)
fonti:
Argomento intuitivo:
In un heap binario, aumentare il valore in un determinato indice è anche O(1)
per lo stesso motivo. Ma se si desidera farlo, è probabile che si desideri mantenere un indice aggiuntivo aggiornato sulle operazioni di heap Come implementare l' operazione O-logn chiave di riduzione per la coda di priorità basata su min-heap? ad es. per Dijkstra. Possibile senza costi aggiuntivi.
La libreria standard GCC C ++ inserisce benchmark su hardware reale
Ho confrontato C ++ std::set
( Red-black tree BST ) e std::priority_queue
( dynamic array heap ) per vedere se avevo ragione sui tempi di inserimento, e questo è quello che ho ottenuto:
Quindi chiaramente:
il tempo di inserimento dell'heap è sostanzialmente costante.
Possiamo vedere chiaramente i punti di ridimensionamento della matrice dinamica. Dato che calcoliamo una media di ogni 10k inserti per poter vedere qualsiasi cosa sopra il rumore del sistema , tali picchi sono in effetti circa 10k volte più grandi di quanto mostrato!
Il grafico ingrandito esclude essenzialmente solo i punti di ridimensionamento dell'array e mostra che quasi tutti gli inserti sono inferiori a 25 nanosecondi.
BST è logaritmico. Tutti gli inserti sono molto più lenti dell'inserto heap medio.
BST vs hashmap analisi dettagliata su: Qual è la struttura dei dati all'interno di std :: map in C ++?
La libreria standard GCC C ++ inserisce il benchmark su gem5
gem5 è un simulatore di sistema completo e quindi fornisce un orologio infinitamente accurato con m5 dumpstats
. Quindi ho provato a usarlo per stimare i tempi per i singoli inserti.
Interpretazione:
l'heap è ancora costante, ma ora vediamo più in dettaglio che ci sono alcune righe e ogni riga superiore è più sparsa.
Ciò deve corrispondere alle latenze di accesso alla memoria eseguite per inserti sempre più alti.
TODO Non riesco davvero a interpretare completamente il BST in quanto non sembra così logaritmico e un po 'più costante.
Con questo maggiore dettaglio, tuttavia, possiamo vedere anche alcune linee distinte, ma non sono sicuro di cosa rappresentino: mi aspetto che la linea di fondo sia più sottile, dal momento che inseriamo la parte inferiore inferiore?
Benchmark con questa configurazione Buildroot su una CPU HPI aarch64 .
BST non può essere implementato in modo efficiente su un array
Le operazioni di heap devono solo fare il bubble up o down di un singolo ramo di un albero, quindi nella O(log(n))
peggiore delle ipotesi, in O(1)
media.
Mantenere un BST bilanciato richiede rotazioni dell'albero, che possono cambiare l'elemento superiore per un altro, e richiederebbero lo spostamento dell'intero array intorno ( O(n)
).
Gli heap possono essere implementati in modo efficiente su un array
Gli indici padre e figlio possono essere calcolati dall'indice corrente come mostrato qui .
Non ci sono operazioni di bilanciamento come BST.
Elimina min è l'operazione più preoccupante in quanto deve essere dall'alto verso il basso. Ma si può sempre fare "percolando" un singolo ramo dell'heap come spiegato qui . Questo porta ad un caso peggiore O (log (n)), poiché l'heap è sempre ben bilanciato.
Se stai inserendo un singolo nodo per ognuno di quelli rimossi, perdi il vantaggio dell'inserto medio asintotico O (1) che heap fornisce in quanto l'eliminazione dominerebbe e potresti anche usare un BST. Dijkstra tuttavia aggiorna i nodi più volte per ogni rimozione, quindi stiamo bene.
Cumuli di array dinamici vs cumuli di alberi di puntatori
Gli heap possono essere implementati in modo efficiente su heap di puntatori: è possibile realizzare implementazioni heap binarie basate su puntatori efficienti?
L'implementazione di array dinamici è più efficiente in termini di spazio. Supponiamo che ogni elemento heap contenga solo un puntatore a un struct
:
l'implementazione dell'albero deve memorizzare tre puntatori per ciascun elemento: padre, figlio sinistro e figlio destro. Quindi l'utilizzo della memoria è sempre 4n
(3 puntatori ad albero + 1 struct
puntatore).
I BST degli alberi necessiterebbero inoltre di ulteriori informazioni di bilanciamento, ad esempio il rosso-nero.
l'implementazione di array dinamici può avere dimensioni 2n
subito dopo il raddoppio. Quindi in media lo sarà 1.5n
.
D'altra parte, l'heap dell'albero ha un migliore inserimento nel caso peggiore, perché copiare l'array dinamico di supporto per raddoppiarne le dimensioni prende il O(n)
caso peggiore, mentre l'heap dell'albero fa solo nuove piccole allocazioni per ciascun nodo.
Tuttavia, il raddoppio dell'array di supporto viene O(1)
ammortizzato, quindi si riduce alla massima latenza. Menzionato qui .
Filosofia
I BST mantengono una proprietà globale tra un genitore e tutti i discendenti (a sinistra più piccolo, a destra più grande).
Il nodo superiore di un BST è l'elemento intermedio, che richiede una conoscenza globale da mantenere (sapendo quanti elementi sempre più piccoli ci sono).
Questa proprietà globale è più costosa da mantenere (log n insert), ma offre ricerche più potenti (log n search).
Heap mantiene una proprietà locale tra genitore e figli diretti (genitore> figli).
Il nodo principale di un heap è il grande elemento, che richiede solo conoscenze locali per mantenere (conoscere il tuo genitore).
Confronto tra BST vs Heap vs Hashmap:
BST: può essere sia ragionevole:
heap: è solo una selezionatrice. Non può essere un set efficiente non ordinato, poiché è possibile verificare rapidamente solo l'elemento più piccolo / più grande.
mappa hash: può essere solo un set non ordinato, non una macchina di smistamento efficiente, poiché l'hash mescola qualsiasi ordine.
Elenco doppiamente collegato
Un elenco doppiamente collegato può essere visto come sottoinsieme dell'heap in cui il primo elemento ha la massima priorità, quindi confrontiamoli anche qui:
O(1)
caso peggiore dato che abbiamo puntatori agli elementi e l'aggiornamento è davvero sempliceO(1)
medio, quindi peggio dell'elenco collegato. Un compromesso per avere una posizione di inserimento più generale.O(n)
per entrambiUn caso d'uso per questo è quando la chiave dell'heap è il timestamp corrente: in tal caso, le nuove voci andranno sempre all'inizio dell'elenco. Quindi possiamo persino dimenticare del tutto il timestamp esatto e mantenere la posizione nell'elenco come priorità.
Questo può essere usato per implementare una cache LRU . Proprio come per le applicazioni heap come Dijkstra , vorrai mantenere una hashmap aggiuntiva dalla chiave al nodo corrispondente dell'elenco, per trovare quale nodo aggiornare rapidamente.
Confronto tra diversi BST bilanciati
Anche se i tempi di inserimento e di ricerca asintotici per tutte le strutture di dati che sono comunemente classificati come "BST bilanciati" che ho visto finora è lo stesso, BBST diversi hanno compromessi diversi. Non l'ho ancora studiato a fondo, ma sarebbe bene riassumere questi compromessi qui:
Guarda anche
Domanda simile su CS: /cs/27860/whats-the-difference-between-a-binary-search-tree-and-a-binary-heap
Heap garantisce solo che gli elementi ai livelli più alti sono maggiori (per heap max) o più piccoli (per heap min) rispetto agli elementi ai livelli inferiori, mentre BST garantisce l'ordine (da "sinistra" a "destra"). Se vuoi elementi ordinati, scegli BST.
[1, 5, 9, 7, 15, 10, 11]
rappresenta un min-heap valido, ma il 7
livello 3 è inferiore rispetto 9
al livello 2. Per una visualizzazione, vedere ad esempio gli elementi 25
e nell'immagine di esempio di Wikipedia per gli heap . (Si noti inoltre che le relazioni di disuguaglianza tra gli elementi non sono rigide, poiché gli elementi non sono necessariamente unici.)19
Quando utilizzare un heap e quando utilizzare un BST
Heap è meglio su findMin / findMax ( O(1)
), mentre BST è buono in tutti i find ( O(logN)
). L'inserto è O(logN)
per entrambe le strutture. Se ti interessa solo findMin / findMax (ad es. Relativo alla priorità), vai con heap. Se vuoi tutto ordinato, vai con BST.
Le prime diapositive da qui spiegano le cose molto chiaramente.
Come accennato da altri, Heap può fare findMin
o findMax
in O (1) ma non entrambi nella stessa struttura di dati. Tuttavia non sono d'accordo sul fatto che Heap sia migliore in findMin / findMax. In effetti, con una leggera modifica, il BST può fare entrambe le cose findMin
e findMax
in O (1).
In questo BST modificato, si tiene traccia del nodo min e del nodo max ogni volta che si esegue un'operazione che può potenzialmente modificare la struttura dei dati. Ad esempio, durante l'operazione di inserimento è possibile verificare se il valore minimo è maggiore del valore appena inserito, quindi assegnare il valore minimo al nodo appena aggiunto. La stessa tecnica può essere applicata sul valore massimo. Quindi, questo BST contiene queste informazioni che è possibile recuperarle in O (1). (uguale all'heap binario)
In questo BST (BST bilanciato), quando tu pop min
o pop max
, il prossimo valore minimo da assegnare è il successore del nodo minimo, mentre il prossimo valore massimo da assegnare è il predecessore del nodo massimo. Quindi si esibisce in O (1). Tuttavia, è necessario riequilibrare l'albero, quindi eseguirà comunque O (log n). (uguale all'heap binario)
Sarei interessato a sentire il tuo pensiero nel commento qui sotto. Grazie :)
Riferimenti incrociati a domande simili Possiamo usare l'albero di ricerca binario per simulare il funzionamento dell'heap? per ulteriori discussioni sulla simulazione di Heap utilizzando BST.
popMin
o popMax
non è O (1), ma è O (log n) perché deve essere un BST bilanciato che deve essere ribilanciato ad ogni operazione di cancellazione. Quindi è lo stesso dell'heap binario popMin
o popMax
che esegue O (log n)
Un albero di ricerca binario usa la definizione: che per ogni nodo, il nodo alla sua sinistra ha un valore minore (chiave) e il nodo alla sua destra ha un valore maggiore (chiave).
Dove come heap, essendo un'implementazione di un albero binario utilizza la seguente definizione:
Se A e B sono nodi, dove B è il nodo figlio di A, il valore (chiave) di A deve essere maggiore o uguale al valore (chiave) di B. Cioè, chiave (A) ≥ chiave (B ).
http://wiki.answers.com/Q/Difference_between_binary_search_tree_and_heap_tree
Ho fatto la stessa domanda oggi per il mio esame e ho capito bene. Sorridi ... :)
Un altro uso di BST su Heap; a causa di un'importante differenza:
Uso di BST su un mucchio : ora, supponiamo che utilizziamo una struttura di dati per memorizzare il tempo di atterraggio dei voli. Non possiamo programmare un volo a terra se la differenza nei tempi di atterraggio è inferiore a "d". E supponiamo che molti voli siano programmati per atterrare in una struttura di dati (BST o Heap).
Ora, vogliamo programmare un altro volo che atterrerà a t . Quindi, dobbiamo calcolare la differenza di t con il suo successore e predecessore (dovrebbe essere> d). Pertanto, avremo bisogno di un BST per questo, che lo fa velocemente, cioè in O (logn) se bilanciato.
Modificato:
L'ordinamento di BST richiede O (n) tempo per stampare gli elementi in ordine ordinato (Inorder traversal), mentre Heap può farlo in tempo O (n logn). Heap estrae l'elemento min e ri-heap l'array, il che lo rende in grado di eseguire l'ordinamento nel tempo O (n logn).
from unsorted to sorted sequence. O(n) time for inorder traversal of a BST, which gives sorted sequence.
Bene, dalla sequenza non ordinata al BST non conosco un metodo basato sul confronto chiave con tempo inferiore a O (n logn), che domina il BST alla parte della sequenza. (Considerando che esiste la costruzione di heap O (n).). Considererei giusto (se inutile) affermare che i cumuli sono vicini all'indifferenza e ai BST ordinati.
Heap garantisce solo che gli elementi ai livelli più alti sono maggiori (per heap max) o più piccoli (per heap min) rispetto agli elementi ai livelli inferiori
Adoro la risposta di cui sopra e mettere il mio commento solo più specifico per le mie necessità e utilizzo. Ho dovuto ottenere la lista n posizioni per trovare la distanza da ogni posizione a punto specifico dire (0,0) e quindi restituire le posizioni am con distanza minore. Ho usato la coda di priorità che è Heap. Per trovare le distanze e mettere in heap mi ci sono voluti n (log (n)) n-locations log (n) ogni inserimento. Quindi per ottenere m con le distanze più brevi ci sono volute m (log (n)) m-locations log (n) eliminazioni di accumulo.
Se avessi dovuto farlo con BST, mi ci sarebbe voluto n (n) inserimento nel caso peggiore (supponiamo che il primo valore sia molto più piccolo e tutti gli altri vengano sequenzialmente sempre più lunghi e l'albero si estende solo dal figlio destro o dal figlio sinistro in caso di minori e minori. Il minimo avrebbe richiesto O (1) tempo ma di nuovo dovevo bilanciarmi. Quindi dalla mia situazione e dalle risposte di cui sopra ciò che ho ottenuto è quando sei solo dopo che i valori alla base di priorità minima o massima vanno per mucchio.