Perché std :: map implementato come un albero rosso-nero ?
Ci sono diversi bilanciati alberi binari di ricerca (BST) là fuori. Quali sono stati progettazione compromessi nella scelta di un albero rosso-nero?
Perché std :: map implementato come un albero rosso-nero ?
Ci sono diversi bilanciati alberi binari di ricerca (BST) là fuori. Quali sono stati progettazione compromessi nella scelta di un albero rosso-nero?
Probabilmente i due algoritmi più comuni di alberi di auto bilanciamento siano alberi rosso-neri e alberi AVL . Per bilanciare l'albero dopo un inserimento / aggiornamento entrambe algoritmi utilizzano la nozione di rotazioni dove vengono ruotate i nodi dell'albero di eseguire il riequilibrio.
Mentre in entrambi gli algoritmi l'inserto / eliminare le operazioni sono O (log n), nel caso di Red-Black rotazione albero di riequilibrio è un O (1) operazione mentre con AVL questo è un O (log n) il funzionamento, rendendo la Rosso-nero albero più efficiente in questo aspetto della fase di riequilibrio e una delle possibili ragioni per cui è più comunemente usato.
alberi rosso-neri sono utilizzati nella maggior parte delle librerie di raccolta, comprese le offerte da Java e Microsoft .NET Framework.
E 'proprio la scelta della vostra implementazione - che potrebbero essere implementati come ogni albero bilanciato. Le varie scelte sono tutte paragonabili con piccole differenze. Pertanto, qualsiasi sia buono come qualsiasi.
alberi AVL avere un'altezza massima di 1.44logn, mentre gli alberi RB hanno un massimo di 2logn. Inserimento di un elemento in un'AVL può implicare un riequilibrio a un certo punto nella struttura. Il riequilibrio termina l'inserimento. Dopo l'inserimento di un nuovo foglio, aggiornando gli antenati di quella foglia deve essere fatto fino alla radice, o fino a un punto in cui i due sottoalberi sono di uguale profondità. La probabilità di dover aggiornare nodi k è di 1/3 ^ k. Riequilibrio è O (1). Rimuovere un elemento può comportare più di un riequilibrio (fino a metà della profondità dell'albero).
RB-alberi sono alberi B di ordine 4 rappresentate come alberi binari di ricerca. A 4-nodo risultati B-albero a due livelli nel BST equivalente. Nel caso peggiore, tutti i nodi dell'albero sono 2-nodi, con una sola catena di 3-nodi fino ad una foglia. Quella foglia sarà ad una distanza di 2logn dalla radice.
Scendendo dalla radice al punto di inserimento, si deve cambiare 4-nodi in 2-nodi, per assicurarsi che ogni inserzione non saturerà una foglia. Di ritorno da l'inserimento, tutti questi nodi devono essere analizzati per assicurarsi che essi rappresentano in modo corretto a 4 nodi. Questo può essere fatto anche scendendo nella struttura. Il costo globale sarà lo stesso. Non c'è pranzo gratis! Rimozione di un elemento dall'albero è dello stesso ordine.
Tutti questi alberi richiedono che i nodi trasportano le informazioni su altezza, peso, colore, ecc Solo albero splay sono esenti da tale informazioni aggiuntive. Ma la maggior parte delle persone hanno paura di alberi Splay, a causa della ramdomness della loro struttura!
Infine, gli alberi possono anche portare informazioni peso nei nodi, permettendo il bilanciamento del peso. Vari schemi possono essere applicati. Si dovrebbe riequilibrare quando una sottostruttura contiene più di 3 volte il numero degli elementi dell'altro sottostruttura. Riequilibrio è di nuovo fatto sia throuh una rotazione singola o doppia. Ciò significa che un caso peggiore di 2.4logn. Si può ottenere via con 2 volte invece di 3, un rapporto molto migliore, ma può significare lasciando poco meno thant 1% delle sottostrutture sbilanciato qua e là. Difficile!
Quale tipo di albero è il migliore? AVL di sicuro. Essi sono i più semplici da codice, e hanno il loro peggior altezza più vicina al logn. Per un albero di 1000000 elementi, un AVL sarà al massimo di altezza 29, un RB 40, e un peso equilibrato 36 o 50 a seconda del rapporto.
Ci sono un sacco di altre variabili: la casualità, rapporto aggiunge, cancella, ricerche, ecc
In realtà dipende l'uso. AVL albero di solito ha più rotazioni di riequilibrio. Quindi, se la vostra applicazione non ha troppe operazioni di inserimento e cancellazione, ma pesi pesantemente sulla ricerca, quindi AVL albero, probabilmente è una buona scelta.
std::map utilizza albero Rosso-Nero, come si arriva un ragionevole compromesso tra la velocità del nodo di inserzione / delezione e la ricerca.
Aggiornamento 2017/06/14: webbertiger modificare la sua risposta dopo ho commentato. Tengo a precisare che la sua risposta è ora molto meglio ai miei occhi. Ma ho mantenuto la mia risposta altrettanto ulteriori informazioni ...
A causa del fatto che credo che prima risposta è sbagliato (correzione: non sia più) e la terza ha un'affermazione sbagliata. Mi sento ho dovuto chiarire le cose ...
L'albero 2 più popolari sono AVL e Rosso Nero (RB). La menzogna principale differenza nell'utilizzo:
La differenza principale proviene dalla colorazione. Si ha meno azione riequilibrio in albero RB di AVL, perché la colorazione permettono a volte di saltare o accorciare azioni riequilibrio che hanno un costo hi relativo. A causa della colorazione, albero RB hanno anche più elevato livello di nodi perché potrebbe accettare nodi rossi tra quelli neri (che hanno le possibilità di ~ 2x più livelli) che fanno ricerca (leggi) un po 'meno efficace ... ma perché si tratta di una costante (2x), è rimanere in O (log n).
Se si considera il calo di prestazioni per una modifica di un albero (significativa) VS il calo di prestazioni di consultazione di un albero (quasi insignificante), diventa naturale a preferire RB sopra AVL per un caso generale.
Le risposte precedenti affrontano solo alternative albero e nero rosso probabilmente rimane solo per ragioni storiche.
Perché non una tabella hash?
In un albero tipo richiede solo ordinamento parziale (<confronto) da utilizzare come chiave nella mappa. Tuttavia, le tabelle hash richiede che ogni tipo di chiave ha una funzione di hash definita. Mantenere questi requisiti tipo al minimo è molto importante per la programmazione generica.
Progettazione di un buon tabella hash richiede la conoscenza intima del contesto, che verrà utilizzato. Dovrebbe utilizzare indirizzamento aperto, o concatenamento legato? Quali sono i livelli di carico si dovrebbe accettare prima del ridimensionamento? Dovrebbe utilizzare un hash costoso che evita le collisioni, o uno che è ruvida e veloce?
(C ++ 11 ha fatto aggiungere tabelle hash con unordered_map. Si può vedere dalla documentazione che richiede l'impostazione delle politiche per configurare molte di queste opzioni.)
Dal momento che lo STL non può anticipare che è la scelta migliore per la vostra applicazione, il default deve essere più flessibile. Alberi "solo di lavoro" e scala bene.
Che dire di altri alberi?
offerta di ricerca veloce rosso di albero nero e sono autobilanciante a differenza di BST. Un altro utente ha sottolineato i suoi vantaggi rispetto alla AVL albero di auto-bilanciamento.
Alexander Stepanov (Il creatore di STL) ha detto che avrebbe usato un B * albero al posto di un albero Rosso-Nero se ha scritto std::mapdi nuovo. Questo perché i nodi in grado di memorizzare un numero arbitrario di elementi contigui che è più adatto per le moderne cache di memoria.
Uno dei più grandi cambiamenti da allora è stata la crescita della cache. cache miss sono molto costosi, quindi frazione di riferimento è molto più importante ora. strutture di dati basato sui nodi, che hanno bassa frazione di riferimento, rendono molto meno senso. Se fossi progettando STL oggi, avrei un diverso insieme di contenitori. Ad esempio, una in memoria B * -tree è una scelta molto meglio di un albero red-black per implementare un contenitore associativo. - Alexander Stepanov
Si può leggere di più qui
È rosso albero o B nero * sempre il meglio?
In altre occasioni Alex ha dichiarato che std::vectorè quasi sempre il miglior contenitore lista per ragioni analoghe. Fa raramente senso utilizzare std::listo std::dequeanche per quelle situazioni ci hanno insegnato a scuola (come la rimozione di un elemento dalla metà della lista). std::vectorè così veloce che batte quelle strutture per tutto, ma di grandi dimensioni n.
Applicando tale stesso ragionamento se si ha solo un piccolo numero di elementi (centinaia?) Utilizzando un std::vectore la ricerca lineare può essere più efficiente l'attuazione di albero std::map. A seconda della frequenza di inserzione, una filtrate std::vectorcombinato con std::binary_searchpuò essere la scelta più veloce.