Чому пошук (без зіткнення) пошуку хешбелів насправді O (1)?


10

Відмова: Я знаю, що є подібні звукові питання вже тут і на Stackoverflow. Але всі вони стосуються зіткнень, про що я не прошу.

Моє питання: чому зіткнень менше LookUp O(1)в першу чергу?

Припустимо, у мене є цей хештел:

Hash  Content
-------------
ghdjg Data1
hgdzs Data2
eruit Data3
xcnvb Data4
mkwer Data5
rtzww Data6

Тепер я шукаю ключ, kкуди h(k)дає хеш-функція h(k) = mkwer. Але як пошук "знає", що хеш mkwerзнаходиться в положенні 5? Чому не потрібно прокручувати всі клавіші, O(n)щоб знайти його? Хеші не можуть бути якимось реальним апаратним адресом, тому що я втрачаю неможливість переміщення даних. І наскільки я знаю, хештейн не сортується за хешами (навіть якби він був, пошук також би зайняв O(log n))?

Як знання хешу допомагає знайти правильне місце в таблиці?

Відповіді:


24

Хеш-функція не повертає деякі рядки, такі як mkwer. Він безпосередньо повертає позицію елемента в масиві. Якщо, наприклад, у вашій хеш-таблиці є десять записів, хеш-функція поверне ціле число в діапазоні 0–9.


1
Дякую. :) Моя помилка була думкою про хеш-хеш-функцію на зразок MD5 або SHA. Але хеш, звичайно, може бути цілою позицією, про яку я не думав. Тепер, коли я знаю, на що звернути увагу, я навіть швидко знайшов хороший приклад: хеш-функція PHP: github.com/php/php-src/blob/PHP-5.6.10/Zend/zend_hash.h#L237
Foo Бар

13
@FooBar: MD5 і SHA також обчислюють одиничні числа на вході, просто так часто говорити про хеші у шістнадцятковій формі. Так само, як адреси пам'яті рідко вважаються десятковими.
nperson325681

4
Крім того, MD5 і т.д. занадто довгі, щоб безпосередньо використовуватися як індекс масиву. Можна було б використовувати частину хеша, як-от нижні n бітів.
chirlu

6

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

Наприклад, я дам дуже бідний хеш для літер, просто щоб зобразити механізм:
0) 1) для кожного символу в рядку прийміть значення ascii, відніміть 'a', якщо це нижній регістр, відніміть 'A', якщо верхній регістр, додайте значення x. x = x m o d 52 2) отримане число, наприклад 15, є індексом масиву. x=0;
x=xmod52

Цей дуже простий хеш (обмежений і схильний до зіткнень) відрізняється від інших хешів механізмом хешування, не враховує заданий вклад. У більш досконалій схемі хеш - це більша кількість, відрегульована на кількість елементів. Ідеальний хеш генерується для всіх входів, щоб гарантувати відсутність зіткнень.

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

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

nthn(sizeofelement)


1
І як пошук знає, де в таблиці знаходиться хеш? Це ні впорядковані, ні апаратні адреси.
Foo Bar

Ви даєте рядок, наприклад, "xcnvb", тому обчислений хеш дає індекс масиву, "xcnvb" - ваш елемент для пошуку, 8 - індекс у таблиці. Це впорядковано кивком, хеш повертає місце для відтворення елемента. Цей елемент був поміщений туди саме тією ж функцією. Обладнання тут ні до чого. Ви надаєте масив, хеш-функцію та обчислюєте хеш для отримання індексу в масиві, такий самий при відтворенні. Масив не відсортований, також він ніколи не заповнений. h("xcnvb")=8
Зло

Але не кожен індекс буде заповнений. Якщо у мене хеші 1, 4, 8, 90 і 223 заповнені даними, як пошук шукає правильне місце? У цьому випадку індекс "90" знаходиться на позиції 4, оскільки більшість інших індексів не існує. І порожній хештейл не має нескінченного розміру, маючи всі можливі позиції !?
Foo Bar

Так, масив дозволить припустити 512 елементів завдовжки, 9 біт, які використовуються для хеш-функції, і у вас є лише 4 елементи. Індекс 90 має позицію 90 у масиві, як у прикладі - майже всі комірки порожні. Якщо ваш масив ви індексуєте його = ваші дані для "xcnvb"H a ( h ( " x c n v b " ) ) = H a [ 90 ]HaHa(h("xcnvb"))=Ha[90]
Зло

Хеш-функція не повертає індекс в масив. Натомість він повертає передбачуване число, яке можна відобразити в масив. Зазвичай це робиться за допомогою оператора модуля з кількістю відходів хеш-таблиць, як і інший операнд.
Крістофер Шульц

3

Щоб розширити відповідь Девіда Річербі, термін " хеш-функція " трохи перевантажений. Часто, коли ми говоримо про хеш-функцію, ми думаємо про MD5, SHA-1 або щось на зразок .hashCode()методу Java , який перетворює деякий вхід в єдине число. Однак домен цього числа (тобто це максимальне значення) дуже малоймовірно , щоб бути тим же розміром , як Hashtable ви намагаєтеся зберігати дані в MD5 (16 байт, SHA-1 становить 20 байт, а. .hashCode()Це int- 4 байт).

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

Тривіальним прикладом такої функції є модуль ; Ви можете легко зіставити кількість довільних розмірів на конкретний індекс у масиві з модулем. Це вводиться в CLRS як "метод поділу":

У способі поділу для створення хеш-функцій ми відображаємо ключ в один з слотів, беручи решту поділену на . Тобто хеш-функція єm k mkmkm

mh(k)=k mod .m

...

Використовуючи метод поділу, ми зазвичай уникаємо певних значень . Наприклад, не повинна бути потужністю 2, оскільки якщо то - це просто бітів нижнього порядку .m m = 2 p h ( k ) p kmmm=2ph(k)pk

~ Вступ до алгоритмів, §11.3.1 - CLRS

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

Java HashMapвикористовує модифіковану версію методу поділу, яка робить етап попередньої обробки для обліку слабких .hashCode()реалізацій, щоб вона могла використовувати масиви потужності двох розмірів. Ви можете точно побачити, що відбувається в .getEntry()методі (коментарі мої):

 // hash() transforms key.hashCode() to protect against bad hash functions
 int hash = (key == null) ? 0 : hash(key.hashCode());
 // indexOf() converts the resulting hash to a value between 0 and table.length-1
 for (Entry<K,V> e = table[indexFor(hash, table.length)];
     ...

Java 8 принесла переписування, HashMapяке ще швидше, але трохи важче для читання. Однак він використовує той же загальний принцип для пошуку індексу.

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