Чому доступ до елемента словника здійснюється за допомогою клавіші 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.