Почему std::map
реализовано как красно-черное дерево ?
Существует несколько сбалансированных бинарных поисковых деревьев (BST). Каковы были дизайнерские компромиссы при выборе красно-черного дерева?
c++
dictionary
data-structures
stl
binary-search-tree
Денис Городецкий
источник
источник
O(logn)
и нетO(1)
, но значения всегда будут отсортированы. Начиная с C ++ 11 (я думаю), естьunordered_map
иunordered_set
, которые реализованы с использованием хеш-функций, и, хотя они не отсортированы, большинство запросов и операций возможноO(1)
(в среднем)Ответы:
Вероятно, два наиболее распространенных алгоритма самобалансирующегося дерева - это красно-черные деревья и деревья AVL . Чтобы сбалансировать дерево после вставки / обновления, оба алгоритма используют понятие поворотов, где узлы дерева вращаются для выполнения повторной балансировки.
В то время как в обоих алгоритмах операции вставки / удаления - это O (log n), в случае ротационной балансировки красно-черного дерева - это операция O (1), в то время как в AVL это операция O (log n) , что делает Красно-черное дерево более эффективно в этом аспекте стадии перебалансировки и одной из возможных причин того, что оно чаще используется.
Красно-черные деревья используются в большинстве библиотек коллекций, включая предложения Java и Microsoft .NET Framework.
источник
std::map
реализаций, отследить разработчиков и спросить их, какие критерии они использовали для принятия решения, так что это остается спекуляцией.Это действительно зависит от использования. У дерева AVL обычно есть больше вращений перебалансировки. Так что, если ваше приложение не имеет слишком много операций вставки и удаления, но имеет большой вес при поиске, то, вероятно, хорошим выбором будет дерево AVL.
std::map
использует красно-черное дерево, поскольку получает разумный компромисс между скоростью вставки / удаления узла и поиском.источник
Деревья AVL имеют максимальную высоту 1,44 лога, а деревья RB - максимум 2 лога. Вставка элемента в AVL может привести к перебалансированию в одной точке дерева. Перебалансировка завершает вставку. После вставки нового листа, обновление предков этого листа должно быть сделано до корня или до точки, где два поддерева имеют равную глубину. Вероятность обновления k узлов составляет 1/3 ^ k. Перебалансировка - это O (1). Удаление элемента может подразумевать более одного перебалансирования (до половины глубины дерева).
RB-деревья - это B-деревья порядка 4, представленные в виде двоичных деревьев поиска. 4-узел в B-дереве приводит к двум уровням в эквивалентном BST. В худшем случае все узлы дерева являются 2-узлами, и только одна цепочка из 3-х узлов до листа. Этот лист будет на расстоянии 2 лога от корня.
Спускаясь от корня к точке вставки, нужно изменить 4-узлы на 2-узлы, чтобы любая вставка не насытила лист. Возвращаясь из вставки, все эти узлы должны быть проанализированы, чтобы убедиться, что они правильно представляют 4-узлы. Это также можно сделать, спустившись на дерево. Глобальная стоимость будет такой же. Там нет бесплатного обеда! Удаление элемента из дерева того же порядка.
Все эти деревья требуют, чтобы узлы содержали информацию о росте, весе, цвете и т. Д. Только деревья Splay свободны от такой дополнительной информации. Но большинство людей боятся Splay-деревьев из-за неуклюжести их структуры!
Наконец, деревья могут также нести информацию о весе в узлах, что позволяет балансировать вес. Различные схемы могут быть применены. Необходимо восстановить баланс, когда поддерево содержит более чем в 3 раза больше элементов другого поддерева. Повторная балансировка выполняется либо через одно, либо через двойное вращение. Это означает худший случай 2,4 лога. Можно уйти с 2 раза вместо 3, что намного лучше, но это может означать, что чуть менее 1% поддеревьев будет неуравновешенным. Tricky!
Какой тип дерева лучше? AVL точно. Их проще всего кодировать, и их худшая высота ближе всего к logn. Для дерева из 1000000 элементов AVL будет иметь максимальную высоту 29, RB 40 и весовой баланс 36 или 50 в зависимости от соотношения.
Есть много других переменных: случайность, соотношение добавлений, удалений, поисков и т. Д.
источник
Предыдущие ответы касаются только альтернатив дерева, и красный черный цвет, вероятно, остается только по историческим причинам.
Почему не хеш-таблица?
Тип требует, чтобы
<
оператор (сравнение) использовался как ключ в дереве. Однако для хеш-таблиц требуется, чтобы для каждого типа ключа была определенаhash
функция. Соблюдение минимальных требований к типам очень важно для общего программирования, поэтому вы можете использовать его с самыми разными типами и алгоритмами.Создание хорошей хеш-таблицы требует глубокого знания контекста, в котором она будет использоваться. Должен ли он использовать открытую адресацию или связанную цепочку? Какие уровни нагрузки следует принять перед изменением размера? Следует ли использовать дорогой хеш, который избегает столкновений, или грубый и быстрый?
Так как STL не может предвидеть, какой вариант является лучшим для вашего приложения, по умолчанию он должен быть более гибким. Деревья "просто работают" и хорошо масштабируются.
(C ++ 11 действительно добавил хеш-таблицы с помощью
unordered_map
. Как видно из документации, для настройки многих из этих параметров требуется настройка политик.)А как насчет других деревьев?
Красные Черные деревья предлагают быстрый поиск и являются самобалансирующимися, в отличие от BST. Другой пользователь указал на свои преимущества перед самобалансирующимся деревом AVL.
Александр Степанов (создатель STL) сказал, что он будет использовать дерево B * вместо красно-черного дерева, если он
std::map
снова напишет , потому что оно более дружественно для современных кэшей памяти.Должны ли карты всегда использовать деревья?
Другой возможной реализацией карт будет сортированный вектор (сортировка вставкой) и бинарный поиск. Это будет хорошо работать для контейнеров, которые не часто изменяются, но часто запрашиваются. Я часто делаю это в C, как
qsort
иbsearch
встроены.Мне даже нужно использовать карту?
Соображения о кеше означают, что редко имеет смысл использовать
std::list
илиstd::deque
перезаписыватьstd:vector
даже те ситуации, которым нас учили в школе (например, удаление элемента из середины списка). Применяя те же аргументы, использование цикла for для линейного поиска по списку часто более эффективно и чище, чем построение карты для нескольких поисков.Конечно, выбор читаемого контейнера обычно важнее производительности.
источник
Обновление 2017-06-14: webbertiger измените свой ответ после того, как я прокомментировал. Я должен отметить, что его ответ теперь намного лучше для моих глаз. Но я сохранил свой ответ как дополнительную информацию ...
Из-за того, что я думаю, что первый ответ неправильный (исправление: больше не то и другое), а третий имеет неправильное подтверждение. Я чувствую, что должен был уточнить вещи ...
2 самых популярных дерева - AVL и Red Black (RB). Основное отличие заключается в использовании:
Основное отличие заключается в окраске. У вас меньше действий по перебалансировке в дереве RB, чем у AVL, потому что раскраска позволяет вам иногда пропустить или сократить действия по перебалансировке, которые имеют относительную высокую стоимость. Из-за раскраски дерево RB также имеет более высокий уровень узлов, потому что оно может принимать красные узлы между черными (имея возможности в ~ 2 раза больше уровней), делая поиск (чтение) немного менее эффективным ... но потому что это постоянная (2x), она остается в O (log n).
Если вы считаете, что снижение производительности для модификации дерева (значимое) против повышения производительности консультации с деревом (почти незначительное), становится естественным предпочесть RB над AVL для общего случая.
источник
Это просто выбор вашей реализации - они могут быть реализованы как любое сбалансированное дерево. Различные варианты сравнимы с небольшими различиями. Поэтому любой так же хорош, как и любой.
источник