Чому std :: map реалізований як червоно-чорне дерево?


195

Чому std::mapреалізується як червоно-чорне дерево ?

Існує кілька збалансованих дерев двійкових пошуків (BST). Якими були дизайнерські компроміси у виборі червоно-чорного дерева?


26
Хоча в усіх реалізаціях, які я бачив, використовується дерево RB, зауважте, що це все ще залежить від реалізації.
Томас

3
@Thomas. Це залежить від впровадження, то чому так, що всі впровадження використовують RB-дерева?
Денис Городецький

1
Мені дуже хотілося б знати, чи думав будь-який реалізатор STL про використання списку пропусків.
Матьє М.

2
Карта та набір C ++ - це фактично впорядкована карта та впорядкований набір. Вони не реалізовані за допомогою хеш-функцій. Кожен запит брав би, O(logn)а не O(1), але значення будуть завжди відсортовані. Починаючи з C ++ 11 (я думаю), є unordered_mapі unordered_set, які реалізуються за допомогою хеш-функцій, і хоча вони не сортуються, більшість запитів і операцій можливі в O(1)(середньому)
SomethingSomething

@Тома це правда, але не так цікаво на практиці. Стандарт забезпечує гарантії складності з урахуванням конкретного алгоритму або набору алгоритмів.
Джастін Майнерс

Відповіді:


126

Ймовірно, два найпоширеніших алгоритми самоврівноваження дерев - це червоно-чорні дерева та дерева AVL . Для врівноваження дерева після вставки / оновлення обох алгоритмів використовується поняття обертання, де вузли дерева обертаються для виконання повторного врівноваження.

Хоча в обох алгоритмах операції вставки / видалення є O (log n), у випадку обертання балансування червоного-чорного дерева є операцією O (1), тоді як при AVL це операція O (log n) , що робить Червоно-чорне дерево ефективніше в цьому аспекті етапу перезбалансування і одна з можливих причин того, що воно частіше використовується.

Червоно-чорні дерева використовуються в більшості бібліотек колекцій, включаючи пропозиції від Java та Microsoft .NET Framework.


54
ви здаєтеся, що червоно-чорні дерева можуть робити зміни дерева за O (1) час, що не відповідає дійсності. Модифікації дерева є O (log n) як для червоно-чорних, так і для AVL-дерев. це робить спорним, чи є балансуюча частина модифікації дерева O (1) або O (log n), оскільки основна операція вже є O (log n). навіть після дещо додаткової роботи, яку виконують дерева AVL, призводить до більш чітко збалансованого дерева, що призводить до дещо швидшого пошуку. тому це цілком справедливий компроміс і не робить дерева AVL неповноцінними червоно-чорним деревам.
некромант

35
Щоб побачити різницю, вам доведеться дивитись за складністю до фактичного часу виконання - дерева AVL зазвичай мають менший загальний час виконання, коли існує набагато більше пошукових запитів, ніж вставки / видалення. Дерева RB мають менший загальний час виконання, коли є набагато більше вставок / видалень. Точна частка, при якій відбувається перерва, залежить, звичайно, від багатьох деталей впровадження, апаратного забезпечення та точного використання, але оскільки авторам бібліотеки доводиться підтримувати широкий спектр моделей використання, вони повинні сприймати здогадки. AVL також трохи складніше реалізувати, тому, можливо, ви хочете перевірити користь для його використання.
Стів Джессоп

6
Дерево RB не є "реалізацією за замовчуванням". Кожен реалізатор вибирає реалізацію. Наскільки ми знаємо, всі вони вибрали дерева дерев, які, напевно, є для продуктивності або для зручності впровадження / обслуговування. Як я вже сказав, точка перерви для продуктивності може не означати, що вони думають, що існує більше вставок / видалень, ніж пошуку, лише те, що співвідношення між ними вище рівня, на якому вони думають, що RB, ймовірно, перемагає AVL.
Стів Джессоп

9
@Denis: на жаль, єдиний спосіб отримати номери - це скласти список std::mapреалізацій, відстежити розробників і запитати їх, за якими критеріями вони використовувались для прийняття рішення, тому це залишається спекуляцією.
Стів Джессоп

4
Відсутнє у всьому цьому - ціна, за вузлом, для зберігання допоміжної інформації, необхідної для прийняття рішень щодо балансу. Червоно-чорним деревам потрібно 1 біт для представлення кольору. Деревам AVL потрібно щонайменше 2 біта (представляти -1, 0 або 1).
SJHowe

47

Це дійсно залежить від використання. Дерево AVL зазвичай має більше обертів відновлення. Отже, якщо у вашій програмі немає занадто багато операцій з вставки та видалення, але вона важить велику вагу для пошуку, то дерево AVL, ймовірно, є хорошим вибором.

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


1
Ви впевнені в цьому ??? Я особисто вважаю, що Червоно-Чорне дерево є чи то складнішим, ніколи не простішим. Єдине, що в дереві Rd-Black, переврівноваження відбувається рідше, ніж AVL.
Ерік Оуеллет

1
@Eric Теоретично, і R / B дерево, і дерево AVL мають складність O (log n)) для вставки та видалення. Але одна велика частина операційних витрат - це обертання, яке відрізняється між цими двома деревами. Будь ласка , зверніться до discuss.fogcreek.com/joelonsoftware / ... Цитати: «балансування дерева AVL може зажадати O (журналу п) обертань, в той час як червоне чорного дерева буде приймати більше двох оборотів , щоб привести його в рівновагу (хоча , можливо , доведеться вивчіть вузли O (log n), щоб визначити, де необхідні обертання). " Відповідно відредагував мої коментарі.
webbertiger

27

Дерева AVL мають максимальну висоту 1,44logn, тоді як дерева RB мають максимум 2logn. Вставлення елемента в AVL може означати повторний баланс в одній точці дерева. Відбалансування завершує вставку. Після вставки нового аркуша оновлення предків цього листа повинно здійснюватися до кореня або до точки, коли обидві підряди мають однакову глибину. Ймовірність необхідності оновлення k вузлів дорівнює 1/3 ^ k. Відбалансування дорівнює O (1). Видалення елемента може передбачати більше одного балансування (до половини глибини дерева).

RB-дерева - це B-дерева порядку 4, представлені у вигляді двійкових дерев пошуку. 4-вузол у B-дереві призводить до двох рівнів в еквівалентній BST. У гіршому випадку всі вузли дерева - це 2-вузли, лише одна ланцюжок з 3-х вузлів вниз до листка. Цей лист буде знаходитися на відстані 2logn від кореня.

Спускаючись від кореня до точки вставки, потрібно змінити 4-вузли на 2-х вузли, щоб переконатися, що будь-яка вставка не наситить лист. Повертаючись із вставки, всі ці вузли повинні бути проаналізовані, щоб переконатися, що вони правильно представляють 4-вузли. Це також можна зробити, опустившись на дерево. Загальна вартість буде однаковою. Безкоштовного обіду немає! Видалення елемента з дерева - в тому ж порядку.

Усі ці дерева вимагають, щоб вузли містили інформацію про висоту, вагу, колір і т. Д. Тільки дерева Splay не мають такої додаткової інформації. Але більшість людей бояться Splay дерев, через бурхливість їх будови!

Нарешті, дерева також можуть нести інформацію про вагу у вузлах, дозволяючи збалансувати вагу. Можна застосувати різні схеми. Слід повторно збалансувати, коли піддерево містить більше, ніж у 3 рази більше, ніж кількість елементів іншого піддерева. Повторне врівноваження знову робиться або за допомогою одного, або подвійного обертання. Це означає найгірший випадок 2,4logn. Можна вийти з 2 рази замість 3, набагато кращим співвідношенням, але це може означати, що тут і там залишається небалансованим 1% підкреслень. Хитрий!

Який вид дерева найкращий? AVL точно. Вони найпростіші в коді та мають найгіршу висоту, найближчу до входу. Для дерева з 1000000 елементів AVL буде мати максимум висоти 29, RB 40 і вагу, збалансовану 36 або 50, залежно від співвідношення.

Існує маса інших змінних: випадковість, співвідношення кількості додавань, видалення, пошуку тощо.


2
Хороша відповідь. Але якщо AVL найкращі, чому стандартна бібліотека реалізує std :: map як дерево RB?
Денис Городецький

14
Я не погоджуюся з тим, що дерева AVL безперечно найкращі. Хоча вони мають низький зріст, вони вимагають (в цілому) більше роботи для відновлення співпраці, ніж роботи з вирівнювання червоних / чорних дерев (O (log n) порівняно з O (1) амортизованою роботою по балансуванню). Грати дерева можуть бути набагато, набагато краще, і ваше твердження, що люди їх бояться, є безпідставним. Існує не одна універсальна "найкраща" схема балансування дерев.
templatetypedef

Майже ідеальна відповідь. Чому ви сказали, що AVL - найкращий. Це просто неправильно, і саме тому в більшості загальних програм використовується червоно-чорне дерево. Щоб вибрати AVL, ви повинні мати досить високий коефіцієнт перегляду читання. Також AVL має трохи менший слід пам'яті, ніж RB.
Ерік Оуеллет

Я погоджуюся, що AVL у більшості випадків прагне бути кращим, оскільки зазвичай дерева шукають частіше, ніж їх вставляють. Чому дерево RB настільки широко вважається кращим, коли воно має незначну перевагу в основному випадку запису, і що ще важливіше, незначний недолік у випадку зчитаного здебільшого? Чи справді віриться, що ви вставите більше, ніж знайдете?
doug65536

25

Попередні відповіді стосуються лише альтернативних варіантів дерева, а червоний чорний, ймовірно, залишається лише з історичних причин.

Чому б не хеш-таблицю?

Тип вимагає використання лише <оператора (порівняння) як ключа в дереві. Однак хеш-таблиці вимагають, щоб у кожного типу ключів була hashвизначена функція. Зведення до мінімуму вимог до типу є дуже важливим для загального програмування, тому ви можете використовувати його з найрізноманітнішими типами та алгоритмами.

Розробка хорошої хеш-таблиці вимагає глибокого знання контексту, в якому вона буде використовуватися. Чи слід використовувати відкриту адресацію чи пов'язане ланцюг? Які рівні навантаження слід прийняти перед зміною розміру? Чи слід використовувати дорогий хеш, що дозволяє уникнути зіткнень, або грубу та швидку?

Оскільки STL не може передбачити, який найкращий вибір для вашої програми, за замовчуванням потрібно бути більш гнучким. Дерева «просто працюють» і гарно масштабуються.

(C ++ 11 зробив додавання хеш-таблиць unordered_map. Ви можете бачити з документації, що вимагає налаштування політики для налаштування багатьох із цих параметрів.)

Що з іншими деревами?

Червоні чорні дерева пропонують швидкий пошук і самоврівноважуються, на відміну від BST. Інший користувач вказав на свої переваги перед самоврівноваженим деревом AVL.

Олександр Степанов (творець STL) сказав, що він буде використовувати B * Дерево замість Червоно-Чорного дерева, якщо він напише std::mapще раз, оскільки це більш дружнє для сучасних кешів пам'яті.

Однією з найбільших змін відтоді було зростання кеш-пам'яті. Пропуски кешу коштують дуже дорого, тому локальність довіри зараз набагато важливіша. Структури даних на основі вузлів, які мають низьку локальність відліку, мають набагато менший сенс. Якби я сьогодні проектував STL, у мене був би інший набір контейнерів. Наприклад, дерево B * -пам'яті є набагато кращим вибором, ніж червоно-чорне дерево для реалізації асоціативного контейнера. - Олександр Степанов

Чи слід на картах завжди використовувати дерева?

Іншою можливою реалізацією карт буде відсортований вектор (сортування вставки) та двійковий пошук. Це буде добре для контейнерів, які не змінюються часто, але часто запитуються. Я часто роблю це в C як qsortі bsearchє вбудованим.

Чи потрібно мені навіть використовувати карту?

Міркування кешу означає , що вона рідко має сенс використовувати std::listабо std::dequeнад std:vectorнавіть в тих ситуаціях , ми вчили в школі (наприклад, видалення елемента з середини списку). Застосовуючи ті ж міркування, використання циклу для лінійного пошуку списку часто є більш ефективним та чіткішим, ніж створення карти для кількох пошукових запитів.

Звичайно, вибір читабельного контейнера зазвичай важливіший, ніж продуктивність.


3

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

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

2 найпопулярніших дерева - AVL та Red Black (RB). Основна відмінність полягає у використанні:

  • AVL: Краще, якщо співвідношення консультацій (читання) більше, ніж маніпуляція (модифікація). Друк на нозі пам'яті трохи менше, ніж RB (через біт, необхідний для фарбування).
  • РБ: Краще в загальних випадках, коли існує баланс між консультацією (читання) та маніпуляцією (модифікацією) або більшою кількістю модифікацій над консультацією. Трохи більший слід пам’яті через зберігання червоно-чорного прапора.

Основна відмінність виходить від забарвлення. У дереві RB у вас менше дій по відновленню балансу, ніж у AVL, оскільки забарвлення дає змогу іноді пропускати або скорочувати дії на повторне врівноваження, які мають відносну високу вартість. Через забарвлення дерево RB також має більш високий рівень вузлів, тому що воно може приймати червоні вузли між чорними (маючи можливості ~ 2 рази більше рівнів), роблячи пошук (читання) трохи менш ефективним ... але тому, що це константа (2x), вона залишається в O (log n).

Якщо ви вважаєте хіт продуктивності для модифікації дерева (значущий) VS, то показник ефективності консультації з деревом (майже незначний), стає природним перевагу RB над AVL для загального випадку.


2

Це лише вибір вашої реалізації - вони можуть бути реалізовані як будь-яке збалансоване дерево. Різні варіанти можна порівняти з незначними відмінностями. Тому будь-яке добре, як і будь-яке.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.