Чому Python використовує хеш-таблицю для реалізації dict, але не Red-Black Tree?
Що є ключовим? Продуктивність?
Чому Python використовує хеш-таблицю для реалізації dict, але не Red-Black Tree?
Що є ключовим? Продуктивність?
Відповіді:
Це загальна відповідь, що не стосується Python.
| Hash Table | Red-Black Tree |
-------+-------------+---------------------+
Space | O(n) : O(n) | O(n) : O(n) |
Insert | O(1) : O(n) | O(log n) : O(log n) |
Fetch | O(1) : O(n) | O(log n) : O(log n) |
Delete | O(1) : O(n) | O(log n) : O(log n) |
| avg :worst | average : worst |
Проблема хеш-таблиць полягає в тому, що хеші можуть стикатися. Існують різні механізми вирішення зіткнень, наприклад, відкрита адресація або окреме ланцюжок. Абсолютно найгірший випадок - це те, що всі ключі мають однаковий хеш-код, і в цьому випадку хеш-таблиця перетвориться на пов'язаний список.
У всіх інших випадках хеш-таблиця - це чудова структура даних, яку легко реалізувати та забезпечує високу ефективність. Недоліком є те, що реалізації, які можуть швидко наростити таблицю і перерозподілити свої записи, швидше за все, витратять майже стільки ж пам’яті, скільки фактично використовується.
RB-Дерева самоврівноважуються і не змінюють своєї алгоритмічної складності в гіршому випадку. Однак їх важче здійснити. Їх середні складності також гірші, ніж у хеш-таблиці.
Усі ключі в хеш-таблиці повинні бути хешированными і порівнянними для рівності між собою. Це особливо легко для рядків або цілих чисел, але також досить просто для поширення на визначені користувачем типи. У деяких мовах, як Java, ці властивості гарантуються визначенням.
Ключі в дереві RB повинні мати загальний порядок: кожен ключ повинен бути порівнянний з будь-яким іншим ключем, а обидві клавіші повинні або порівняти менший, більший або рівний. Це впорядкованість рівності повинно бути рівнозначним смисловій рівності. Це зрозуміло для цілих чисел та інших чисел, також досить просто для рядків (порядок повинен бути лише послідовним і не спостерігатись зовні, тому замовлення не потрібно враховувати локалі [1] ), але важко для інших типів, які не мають притаманного порядку . Абсолютно неможливо мати ключі різних типів, якщо не можливе деяке порівняння між ними.
[1]: Насправді я тут помиляюся. Два рядки можуть бути не рівними байтам, але все ж є еквівалентними за правилами якоїсь мови. Дивіться, наприклад, нормалізацію Unicode для одного прикладу, де два рівні рядки кодуються по-різному. Чи має значення композиція символів Unicode для вашого хеш-ключа - це те, чого реалізація хеш-таблиці не може знати.
Можна подумати, що дешевим рішенням ключів RB-Tree було б спершу перевірити рівність, а потім порівняти ідентичність (тобто порівняти покажчики). Однак це впорядкування не було б перехідним: якщо a == b
і id(a) > id(c)
, тоді воно повинно також слідувати тому id(b) > id(c)
, що тут не гарантується. Таким чином, ми можемо використовувати хеш-код ключів як ключі пошуку. Тут впорядкування працює правильно, але ми можемо в кінцевому підсумку отримати декілька чітких ключів з тим самим хеш-кодом, який буде призначений тому самому вузлу в дереві RB. Для вирішення цих хеш-колізій ми можемо використовувати окремі ланцюжки, як і хеш-таблиці, але це також успадковує найгірший випадок поведінки для хеш-таблиць - найгірший з обох світів.
Я очікую, що хеш-таблиця матиме кращу локальність пам’яті, ніж дерево, тому що хеш-таблиця - це лише масив.
Записи в обох структурах даних мають досить високі накладні витрати:
Вставки та вилучення в дереві RB включають обертання дерев. Це насправді не дорого, але вони вимагають накладних витрат. У хеші вставлення та видалення не є дорожчим, ніж простий доступ (хоча змінити розмір таблиці хеша при введенні - це O(n)
зусилля).
Таблиці хешу по своїй суті є змінними, тоді як дерево RB також може бути реалізовано незмінним способом. Однак це рідко корисно.
Існує цілий спектр причин, які можуть бути правдивими, але ключовими з них, ймовірно, є:
Простіше писати / підтримувати та перемагати в типових випадках? Підпишіться, будь ласка!