Почему std:: map реализована как красно-черное дерево? - программирование

Почему std:: map реализована как красно-черное дерево?

Почему std::map реализован в виде красно-черного дерева?

Существует несколько сбалансированных бинарных поисковых деревьев (BST). Каковы были дизайнерские компромиссы при выборе красно-черного дерева?

4b9b3361

Ответ 1

Вероятно, два наиболее общих алгоритма дерева балансировки: Красно-черные деревья и Деревья AVL. Чтобы сбалансировать дерево после вставки/обновления, оба алгоритма используют понятие поворота, где узлы дерева вращаются для выполнения повторной балансировки.

В обоих алгоритмах операции вставки/удаления являются O (log n), в случае повторного балансировки дерева Red-Black это O ( 1), в то время как с AVL это O (log n), что делает дерево Red-Black более эффективным в этом аспекте стадии повторной балансировки и одной из возможных причин, по которой он более широко используется.

Деревья Red-Black используются в большинстве библиотек коллекции, включая предложения от Java и Microsoft.NET Framework.

Ответ 2

Это действительно зависит от использования. Дерево AVL обычно имеет больше поворотов перебалансировки. Поэтому, если ваше приложение не имеет слишком большого количества операций вставки и удаления, но сильно зависит от поиска, то, вероятно, AVL-дерево является хорошим выбором.

std::map использует дерево Red-Black, поскольку оно получает разумный компромисс между скоростью node вставки/удаления и поиска.

Ответ 3

Деревья 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 в зависимости от соотношения.

Есть много других переменных: случайность, соотношение добавлений, удалений, поисков и т.д.

Ответ 4

Предыдущие ответы касаются только альтернатив дерева, и красный черный цвет, вероятно, остается только по историческим причинам.

Почему не хеш-таблица?

Для типа требуется только < сравнение для использования в качестве ключа в дереве. Однако для хеш-таблиц требуется, чтобы для каждого типа ключей была определена hash функция. Соблюдение минимальных требований к типу очень важно для общего программирования.

Создание хорошей хеш-таблицы требует глубокого знания контекста, в котором она будет использоваться. Должен ли он использовать открытую адресацию или связанную цепочку? Какие уровни нагрузки следует принять перед изменением размера? Следует ли использовать дорогой хеш, который избегает столкновений, или грубый и быстрый?

Поскольку STL не может предугадать, какой вариант является лучшим для вашего приложения, значение по умолчанию должно быть более гибким. Деревья "просто работают" и хорошо масштабируются.

(С++ 11 действительно добавил хеш-таблицы с помощью unordered_map. Из документации видно, что для настройки многих из этих параметров требуется настройка политик.)

А как насчет других деревьев?

Красное Черное дерево предлагает быстрый поиск и является самобалансирующимся, в отличие от BST. Другой пользователь указал на свои преимущества перед самобалансирующимся деревом AVL.

Александр Степанов (создатель STL) сказал, что он будет использовать дерево B * вместо красно-черного дерева, если он снова напишет std::map, потому что это более удобно для современных кэшей памяти.

Одним из самых больших изменений с тех пор стал рост кешей. Промахи в кеше очень дороги, так что местность ссылок сейчас гораздо важнее. Структуры данных на основе узлов, которые имеют низкую локальность ссылок, имеют гораздо меньше смысла. Если бы я проектировал STL сегодня, у меня был бы другой набор контейнеров. Например, B * -tree в памяти является гораздо лучшим выбором, чем красно-черное дерево для реализации ассоциативного контейнера. - Александр Степанов

Вы должны всегда использовать красное черное дерево или дерево B *?

В других случаях Алекс заявлял, что std::vector почти всегда является лучшим контейнером списка по аналогичным причинам. Редко имеет смысл использовать std::list или std::deque даже для тех ситуаций, которым нас учили в школе (таких как удаление элемента из середины списка). std::vector настолько быстр, что превосходит эти структуры для всего, кроме большого N

Применяя это рассуждение, если у вас есть только небольшое количество элементов (сотни?), Использование std::vector и линейный поиск могут быть более эффективными, чем реализация дерева std::map. В зависимости от частоты вставки сортированный std::vector сочетании с std::binary_search может быть самым быстрым выбором.

Ответ 5

Обновление 2017-06-14: webbertiger измените свой ответ после того, как я прокомментировал. Я должен отметить, что его ответ теперь намного лучше для моих глаз. Но я сохранил свой ответ как дополнительную информацию...

Из-за того, что я думаю, что первый ответ неправильный (исправление: больше не то и другое), а третий имеет неправильное подтверждение. Я чувствую, что должен был уточнить вещи...

2 самых популярных дерева - AVL и Red Black (RB). Основное отличие заключается в использовании:

  • AVL: Лучше, если соотношение консультации (чтения) больше, чем манипуляции (модификации). Объем отпечатка памяти чуть меньше, чем у RB (из-за бита, необходимого для окраски).
  • РБ: Лучше в общих случаях, когда существует баланс между консультацией (чтение) и манипулированием (модификацией) или большим количеством модификации по сравнению с консультацией. Немного больший след памяти из-за хранения красно-черного флага.

Основное отличие заключается в окраске. У вас меньше действий по перебалансировке в дереве RB, чем у AVL, потому что раскраска позволяет вам иногда пропустить или сократить действия по перебалансировке, которые имеют относительную высокую стоимость. Из-за раскраски дерево RB также имеет более высокий уровень узлов, потому что оно может принимать красные узлы между черными (имея возможности в ~ 2 раза больше уровней), что делает поиск (чтение) немного менее эффективным... но потому что это постоянная (2x), она остается в O (log n).

Если вы считаете, что снижение производительности для модификации дерева (значимое) против повышения производительности консультации с деревом (почти незначительное), становится естественным предпочесть RB над AVL для общего случая.

Ответ 6

Это просто выбор вашей реализации - они могут быть реализованы как любое сбалансированное дерево. Различные варианты сопоставимы с незначительными различиями. Поэтому любой такой же хороший, как любой.