Чому Python використовує хеш-таблицю для реалізації dict, але не Red-Black Tree? [зачинено]


11

Чому Python використовує хеш-таблицю для реалізації dict, але не Red-Black Tree?

Що є ключовим? Продуктивність?


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

Відповіді:


16

Це загальна відповідь, що не стосується 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-дерево: ключ, значення, колір, лівий вказівник дитини, правий вказівник дитини. Зверніть увагу, що хоча колір - це один біт, проблеми з вирівнюванням можуть означати, що ви все одно витрачаєте достатньо місця для майже цілого покажчика, або навіть майже чотирьох покажчиків, коли можна виділити лише блоки пам'яті потужністю двох розмірів. У будь-якому випадку, запис на дереві RB витрачає більше пам’яті, ніж запис хеш-таблиці.
  • Вставки та вилучення в дереві RB включають обертання дерев. Це насправді не дорого, але вони вимагають накладних витрат. У хеші вставлення та видалення не є дорожчим, ніж простий доступ (хоча змінити розмір таблиці хеша при введенні - це O(n)зусилля).

  • Таблиці хешу по своїй суті є змінними, тоді як дерево RB також може бути реалізовано незмінним способом. Однак це рідко корисно.


Чи можемо ми створити хеш-таблицю з маленькими деревами RB для зіткнення хешей?
aragaer

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

Деревам потрібні об'єкти, які підтримують "<". Таблиці хешу потрібні об'єкти, які підтримують хеш + "=". Тому дерева РБ можуть бути неможливими. Але дійсно, якщо у вашій хеш-таблиці є значна кількість зіткнень, то вам потрібна нова хеш-функція, а не альтернативний алгоритм для зіткнення ключів.
gnasher729

1

Існує цілий спектр причин, які можуть бути правдивими, але ключовими з них, ймовірно, є:

  • Таблиці хешу легше втілити, ніж дерева. Ні це не зовсім тривіально, але хеш-таблиці трохи простіше, а вплив на область законних ключів менш суворий, оскільки вам просто потрібна хеш-функція та функція рівності; дерева вимагають функції загального замовлення, і це набагато складніше написати.
  • Таблиці хешу (можуть) мають кращу продуктивність при невеликих розмірах. Це дуже важливо, оскільки значна частина роботи лише теоретично стосується великих наборів даних; на практиці багато чого насправді працює лише з десятками чи сотнями ключів, а не мільйонами. Виконання малих масштабів має велике значення, і ви не можете використовувати асимптотичний аналіз, щоб визначити, що найкраще там; ви повинні реально реалізовувати та вимірювати.

Простіше писати / підтримувати та перемагати в типових випадках? Підпишіться, будь ласка!

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