Dlaczego jest std::map
implementowany jako czerwono-czarne drzewo ?
Istnieje kilka zrównoważonych drzew binarnych (BST). Jakie były kompromisy w wyborze czerwono-czarnego drzewa?
c++
dictionary
data-structures
stl
binary-search-tree
Denis Gorodetskiy
źródło
źródło
O(logn)
i nieO(1)
, ale wartości będą zawsze sortowane. Począwszy od C ++ 11 (myślę), istniejąunordered_map
iunordered_set
, które są implementowane za pomocą funkcji skrótu i chociaż nie są sortowane, większość zapytań i operacji jest możliwaO(1)
(średnio)Odpowiedzi:
Prawdopodobnie dwa najczęstsze algorytmy drzew samobalansujących to drzewa czerwono-czarne i drzewa AVL . Aby zrównoważyć drzewo po wstawieniu / aktualizacji, oba algorytmy używają pojęcia obrotu, w którym obracane są węzły drzewa w celu przeprowadzenia ponownego równoważenia.
Podczas gdy w obu algorytmach operacjami wstawiania / usuwania są O (log n), w przypadku czerwono-czarnego drzewa rotacja ponownego równoważenia jest operacją O (1) , podczas gdy w AVL jest to operacja O (log n) , co powoduje, że Czerwono-czarne drzewo bardziej wydajne w tym aspekcie etapu ponownego równoważenia i jeden z możliwych powodów, że jest częściej używany.
Drzewa czerwono-czarne są używane w większości bibliotek kolekcji, w tym w ofertach Java i Microsoft .NET Framework.
źródło
std::map
wdrożeń, wyśledzenie programistów i zapytanie ich, jakie kryteria zastosowali przy podejmowaniu decyzji, więc pozostaje to spekulacja.To zależy od sposobu użytkowania. Drzewo AVL zwykle ma więcej rotacji ponownego równoważenia. Więc jeśli twoja aplikacja nie ma zbyt wielu operacji wstawiania i usuwania, ale ma duże znaczenie przy wyszukiwaniu, drzewo AVL prawdopodobnie jest dobrym wyborem.
std::map
używa drzewa czerwono-czarnego, ponieważ uzyskuje rozsądny kompromis między szybkością wstawiania / usuwania węzłów a wyszukiwaniem.źródło
Drzewa AVL mają maksymalną wysokość 1,44 logn, a drzewa RB maksymalnie 2 logn. Wstawienie elementu do AVL może oznaczać ponowne zrównoważenie w jednym punkcie drzewa. Ponowne równoważenie kończy wstawianie. Po wstawieniu nowego liścia należy wykonać aktualizację przodków tego liścia aż do korzenia lub do punktu, w którym dwa poddrzewa mają jednakową głębokość. Prawdopodobieństwo aktualizacji k węzłów wynosi 1/3 ^ k. Ponowne równoważenie to O (1). Usunięcie elementu może oznaczać więcej niż jedno ponowne zrównoważenie (do połowy głębokości drzewa).
Drzewa RB są drzewami B rzędu 4 reprezentowanymi jako drzewa wyszukiwania binarnego. 4-węzeł w drzewie B daje dwa poziomy w równoważnym BST. W najgorszym przypadku wszystkie węzły drzewa to 2-węzły, z tylko jednym łańcuchem 3-węzłów aż do liścia. Ten liść będzie w odległości 2koli od korzenia.
Przechodząc od korzenia do punktu wstawiania, należy zmienić 4-węzły na 2-węzły, aby upewnić się, że żadne wstawienie nie nasyci liścia. Wracając od wstawienia, wszystkie te węzły muszą zostać przeanalizowane, aby upewnić się, że poprawnie reprezentują 4-węzły. Można to również zrobić, schodząc w dół drzewa. Globalny koszt będzie taki sam. Nie ma darmowego lunchu! Usuwanie elementu z drzewa odbywa się w tej samej kolejności.
Wszystkie te drzewa wymagają, aby węzły niosły informacje o wysokości, wadze, kolorze itp. Tylko drzewa Splay są wolne od takich dodatkowych informacji. Ale większość ludzi boi się drzew Splay, z powodu rozpadu ich struktury!
Wreszcie, drzewa mogą również przenosić informacje o wadze w węzłach, umożliwiając równoważenie wagi. Można zastosować różne schematy. Należy ponownie zrównoważyć, gdy poddrzewo zawiera ponad 3 razy więcej elementów niż inne poddrzewo. Ponowne równoważenie jest ponownie wykonywane przez pojedynczy lub podwójny obrót. To najgorszy przypadek 2.4. Można uciec z 2 razy zamiast 3, znacznie lepszym współczynnikiem, ale może to oznaczać pozostawienie nieco mniej niż 1% subtrees niezrównoważonych tu i tam. Zdradliwy!
Który rodzaj drzewa jest najlepszy? AVL na pewno. Są najprostsze do kodowania, a ich najgorsza wysokość jest najbliższa logn. Dla drzewa zawierającego 1000000 elementów AVL będzie mieć najwyżej wysokość 29, RB 40, a waga wyważona 36 lub 50 w zależności od stosunku.
Istnieje wiele innych zmiennych: losowość, współczynnik dodawania, usuwania, wyszukiwania itp.
źródło
Poprzednie odpowiedzi dotyczyły tylko alternatyw drzewa, a czerwony czarny prawdopodobnie pozostaje tylko z powodów historycznych.
Dlaczego nie tablica skrótów?
Typ wymaga tylko
<
operatora (porównania) do użycia jako klucza w drzewie. Jednak tabele skrótów wymagają, aby każdy typ klucza miałhash
zdefiniowaną funkcję. Ograniczenie wymagań dotyczących typu do minimum jest bardzo ważne dla programowania ogólnego, dzięki czemu można go używać z szeroką gamą typów i algorytmów.Projektowanie dobrej tabeli skrótów wymaga dogłębnej znajomości kontekstu, w którym będzie używana. Czy powinien używać otwartego adresowania, czy łączenia łańcuchowego? Jakie poziomy obciążenia powinien zaakceptować przed zmianą rozmiaru? Czy powinien używać drogiego skrótu, który pozwala uniknąć kolizji, czy takiego, który jest szorstki i szybki?
Ponieważ STL nie może przewidzieć, który jest najlepszy wybór dla Twojej aplikacji, domyślna musi być bardziej elastyczna. Drzewa „po prostu działają” i dobrze się skalują.
(C ++ 11 dodał tabele skrótów
unordered_map
. Z dokumentacji wynika, że wymaga to ustawienia zasad, aby skonfigurować wiele z tych opcji).Co z innymi drzewami?
Drzewa Red Black oferują szybkie wyszukiwanie i same się równoważą, w przeciwieństwie do BST. Inny użytkownik zwrócił uwagę na jego zalety w stosunku do samowyważącego się drzewa AVL.
Alexander Stepanov (twórca STL) powiedział, że użyłby drzewa B * zamiast drzewa czerwono-czarnego, gdyby napisał
std::map
ponownie, ponieważ jest bardziej przyjazny dla nowoczesnych pamięci podręcznych.Czy mapy powinny zawsze używać drzew?
Inną możliwą implementacją map byłby posortowany wektor (sortowanie wstawiane) i wyszukiwanie binarne. Działa to dobrze w przypadku kontenerów, które nie są często modyfikowane, ale są często pytane. Często robię to w C
qsort
ibsearch
są wbudowane.Czy muszę nawet korzystać z mapy?
Rozważania cache oznacza, że rzadko ma sens użycie
std::list
lubstd::deque
nadstd:vector
nawet dla tych sytuacjach, uczono nas w szkole (takie jak usunięcie elementu ze środka listy). Stosując to samo rozumowanie, użycie pętli for do wyszukiwania liniowego listy jest często bardziej wydajne i czystsze niż budowanie mapy dla kilku wyszukiwań.Oczywiście wybór czytelnego pojemnika jest zwykle ważniejszy niż wydajność.
źródło
Aktualizacja 2017-06-14: webbertiger edytuje swoją odpowiedź po tym, jak skomentowałem. Powinienem zauważyć, że jego odpowiedź jest teraz o wiele lepsza dla mnie. Ale zachowałem swoją odpowiedź jako dodatkową informację ...
Z uwagi na to, że uważam, że pierwsza odpowiedź jest zła (poprawka: już nie obie), a trzecia ma błędną afirmację. Czuję, że musiałem wyjaśnić ...
2 najbardziej popularne drzewa to AVL i Red Black (RB). Główna różnica polega na wykorzystaniu:
Główną różnicą jest kolorystyka. W drzewku RB masz mniej akcji ponownego równoważenia niż AVL, ponieważ kolorowanie pozwala czasami pomijać lub skracać akcje ponownego równoważenia, które mają względny koszt hi. Z powodu kolorowania drzewo RB ma również wyższy poziom węzłów, ponieważ może akceptować czerwone węzły między czarnymi (mając możliwości ~ 2x więcej poziomów), co sprawia, że wyszukiwanie (odczyt) jest nieco mniej wydajne ... ale ponieważ jest to stała (2x), pozostaje w O (log n).
Jeśli weźmiesz pod uwagę wydajność w przypadku modyfikacji drzewa (istotna) w porównaniu do wydajności w wyniku konsultacji z drzewem (prawie nieistotna), naturalne jest, że w ogólnym przypadku wolisz RB niż AVL.
źródło
To tylko wybór twojej implementacji - mogą być implementowane jako dowolne zrównoważone drzewo. Różne opcje są porównywalne z niewielkimi różnicami. Dlatego każdy jest tak dobry jak każdy.
źródło