Чому доступ до елемента словника здійснюється за допомогою клавіші O (1), хоча хеш-функція може бути не O (1)?


75

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

Якщо припустити, що у вас є гарна хеш-функція, яка є дуже ефективною, це все одно може зайняти багато операцій.

Чи можна це пояснити?


39
Позначення O - це вимірювання the growthскладності з різними входами. Справа не в тому, скільки у вас операцій. Наприклад: з 1 значенням у вас є xсекунди, зі nзначеннями вам потрібні roughly x*nсекунди => O (n). xможе бути багато операцій у поєднанні.
Хан ТО

33
Структури даних не мають складності нотацій O, операції з ними мають.
user6144226

3
То про яку операцію ми беремося?
Патрік Хофман,

@PatrickHofman Це справді пояснює деякі факти про складності O (1) у словнику, можливо, споріднене є кращим словом.
user6144226

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

Відповіді:


118

HashFuncсам має багато операцій за лаштунками

Це, безумовно, правда. Однак кількість цих операцій залежить від розміру ключа , а не від розміру хеш-таблиці, до якої вставлений ключ: кількість операцій для обчислення хеш-функції однакова для ключа в таблиці з десятьма або з десятьма тисячами записів.

Ось чому виклик хеш-функції часто вважають O (1). Це чудово працює для ключів фіксованого розміру (інтегральні значення та рядки фіксованої довжини). Він також забезпечує гідне наближення клавіш змінного розміру з практичною верхньою межею.

Однак, як правило, час доступу до хеш-таблиці становить O (k), де kє верхня межа розміру хеш-ключа.


8
Також враховуйте, що неможливо мати хеш-таблицю nрізних елементів, якщо хоча б один елемент не представлений принаймні log(n)бітами.
Оуен,

На жаль, усі операції експоненціальні, якщо ви не обмежуєте бітовий розмір входів. Але це не дуже цікавий чи корисний результат, правда?
Joker_vD

1
@Owen: Також неможливо мати більше елементів у хеш-таблиці в пам'яті, ніж унікальні призначені ключі, які вміщуються у змінну розміру вказівника.
Джошуа

the number of these operations depends on the size of the keyта щодо розміру хешованих даних.
Ерік Дж

kне повинен бути верхньою межею. Час пошуку лінійний за розміром ключа, тому справді O(k)там, де kрозмір ключа. Якщо kрозуміється як верхня межа, то це насправді O(1).
usr

136

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


1
Але це можна записати хеш - функцію, яка залежить від розміру колекції. Це було б по-дурному і надумано, але ти можеш це зробити. Твердження про те, що пошук хеш-комплексу насправді виходить з припущення, що обчислення хешу дорівнює O (1), що є практично завжди, але не обов'язково.
Серві

@Servy Навіть не обов'язково все таке дурне та надумане. Реалізація користувацького списку, яка хоче дозволити двох списків, які містять однакові елементи, порівнюватись як самі по собі, може замінити GetHashCode()комбінування хеш-кодів елементів певним чином. Якби я реалізував такий клас, для початкової реалізації я б реалізував GetHashCode()саме так. Звичайно, я б змінив це пізніше.

1
@hvd Це був би хеш O (m), де m - розмір внутрішніх колекцій. Це все одно не буде пов’язано з розміром зовнішньої колекції (фактична структура на основі хешу). Вам потрібно, щоб елементи в колекції переглядали всі елементи тієї самої колекції на основі хешу, які вони зараз знаходяться, щоб ці елементи мали O (n) (або будь-яку функцію n) для свого хеш-коду. Це було б досить дурно і надумано.
Серві

1
@Servy О, це ти мав на увазі. Так, це було б по-дурному. :) Я не можу придумати жодного правдоподібного сценарію, коли ви могли б цього захотіти.

@Servy Загальним пунктом хешування є уникнення часу пошуку O (n), тому створення хеш-функції, яка O (n), повністю перевершить мету. Ви могли б це зробити, але це було б як реалізація додавання рекурсивно з числами Пеано: можливо, але насправді не практично.
Бармар,

15

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

Отже, іншими словами, Словник з 5 учасниками скажемо, що для доступу до одного з них потрібно близько 0,002 мс, а також словник з 25 учасників повинен взяти щось подібне. Велике O означає алгоритмічну складність над розміром колекції замість фактичних операторів або виконуваних функцій


1
Але в той же час, якщо ваша хеш-функція дійсно погана, ви можете отримати багато значень у сегменті, тому O (1) більше не буде тримати
klappvisor

3
@klappvisor, необов’язково, якщо функція погана. Можливо, вхідні дані створені. Ось чому O (1) тут амортизована складність, а не "справжня" складність.
n0rd

Це не означає, що кожен учасник займе однакову кількість часу, це просто (приблизно) означає, що верхня межа часу доступу не зростає із розміром колекції. Поміркуйте, як хеш-таблиця обробляє неоднозначні зіткнення. Подібним чином пошук елемента для бінарного дерева пошуку - це O (log2 n), оскільки найгірший випадок - log2 із розміром N, але елемент біля кореня займе менше часу, ніж елемент листя, наприклад.
пухнастий

@ n0rd Це насправді не означає "амортизоване" роз'яснення O (1). Той факт, що це амортизований O (1), пояснює той факт, що приблизно 1 / N доповнень (якщо ви додаєте до набору) вимагатиме перерозподілу нового масиву підкладки, що є операцією O (N) , отже, ви можете виконати N додавань за час O (N) для амортизованого додавання O (1), тоді як одне додавання насправді також є O (N) (коли не амортизується). Це окреме роз’яснення асимптотичної складності, яке передбачає, що хеші досить добре розподілені.
Серві

12

Якщо словник / карта реалізується як HashMapвона має кращий випадок складності з O(1), так як я кращому випадку це вимагає саме обчислення хеш-коду ключового елемента для пошуку, якщо немає ключових зіткнень.

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

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

Ось чому словники / карти реалізуються по-різному для різних сценаріїв. Для Java існує безліч різних реалізацій, C ++ використовує червоні / чорні дерева тощо. Ви вибрали їх на основі кількості даних та на основі їх найкращої / середньої / найгіршої ефективності виконання.


1
Це не повинно бути так, наприклад, Java 8 HashMapвдається до збалансованого дерева на випадок виявлення кількох зіткнень.
acecent

@acelent може бути правдою, але тоді це вже класична хеш-карта. Існує багато різних реалізацій для карт / словників, саме в цьому випадку. Я змінив відповідь, щоб вказати на це.
Мартін К.

6

Теоретично це все ще O (n), тому що в гіршому випадку всі ваші дані можуть мати однаковий хеш і бути об’єднаними, і в цьому випадку вам доведеться лінійно пройти все це.


3

Будь ласка, перегляньте допис Що означає "час доступу O (1)"?

Кількість операцій у хеш-функції не має значення, якщо для КОЖНОГО елемента в колекції потрібно однаковий (постійний) проміжок часу. Наприклад, доступ до одного елемента в колекції з 2 елементів займає 0,001 мс, але також доступ до одного елемента в колекції з 2 000 000 000 елементів займає 0,001 мс. Хоча хеш-функція може містити сотні операторів if і кілька обчислень.


6
Постійна кількість часу, не лінійна.
Кусалананда,

Хіба не потрібно, щоб хеш-функція містила більше "if операторів та декількох обчислень", щоб отримати досить довге хеш-значення, щоб однозначно ідентифікувати 2 мільярди елементів, ніж це було б для 200?
Damian Yerrick

1

з документів:

Отримати значення за допомогою його ключа дуже швидко, близько до O (1), оскільки клас T: System.Collections.Generic.Dictionary`2 реалізований як хеш-таблиця.

Отже, це може бути O (1), але може бути повільнішим. Тут ви можете знайти ще одну тему щодо продуктивності хеш-таблиці : Хеш-таблиця - чому це швидше, ніж масиви?


1

Як тільки ви допускаєте той факт, що більші та більші словники займають більше пам'яті, рухаючись далі по ієрархії кеш-пам’яті і, врешті-решт, сповільнюючи обмін місцями на диску, важко стверджувати, що це справді O (1). Продуктивність словника буде повільнішою, оскільки вона збільшується, ймовірно, надаючи O (log N) часову складність. Не вірите мені? Спробуйте самі, використовуючи 1, 100, 1000, 10000 і так далі елементи словника, до 100 мільярдів, і виміряйте, скільки часу потрібно на практиці для пошуку елемента.

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


У вас є щось, але коли ми говоримо про алгоритмічну складність, має сенс припустити ідеальне обладнання. Суть полягає у визначенні характеристик алгоритму, а не різних реалізаціях апаратного забезпечення в реальному житті. Крім того, якщо у вас є достатньо великі дані, складність алгоритму насправді є найбільш важливою: чи це, наприклад, O (1), (logN), O (n) або O (n ^ 2).
Теро Лахтінен

1
Існує також проблема зіткнення хеш-ключа зі словниками більших розмірів. Як тільки ви отримаєте достатньо великий розмір, більшість нових записів будуть стикатися з існуючим записом, викликаючи лінійний пошук по кожному сегменту хешу і закінчуючи як O (n). Якщо ви не зробите, щоб ключі хешу зростали довше із збільшенням розміру ... але тоді у вас також немає O (1). Я погоджуюсь, що на практиці ви можете розглядати це як постійний час, але я волів би триматися подалі від офіційного O-позначення для чогось, що є лише приблизним наближенням для досить малих розмірів, а не офіційним доказом для будь-якого розміру.
Ед Авіс,
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.