Структури даних та алгоритми

Підручник зі структури даних хеш-таблиць

Підручник зі структури даних хеш-таблиць
В інформатиці слово «карта» означає пов’язування об’єкта в одному наборі з іншим об’єктом в іншому наборі. Уявіть, що на сторінці ліворуч є слова по колу, а праворуч на тій самій сторінці є інше коло, всередині якого є інші слова. Припустимо, що в кожному колі слова пишуться навмання, розкидані по колу. Також припустимо, що слова в лівому колі називаються ключами, а слова в правому колі - значеннями. Якщо від кожного слова ліворуч до кожного слова праворуч намальована стрілка, тоді було б сказано, що ключі зіставлені зі значеннями.

Припустимо, що ви є власником великого продовольчого магазину в окрузі, де ви живете. Припустимо, що ви живете на великій території, яка не є комерційною. Ви не єдиний, хто має продовольчий магазин у цьому районі; у вас є кілька конкурентів. І тоді вам спадає на думку, що ви повинні записати номери телефонів своїх клієнтів у зошит. Звичайно, зошит невеликий, і ви не можете записати всі телефонні номери для всіх своїх клієнтів.

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

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

Тут є проблема: у правому стовпці є дублікати. Тобто одна і та ж назва напою зустрічається не раз. Іншими словами, різні члени п'ють один і той же солодкий напій або той самий алкогольний напій, тоді як інші члени п'ють інший солодкий або алкогольний напій. Адвокат вирішує вирішити цю проблему, вставивши вузьку колонку між двома колонами. У цьому середньому стовпці, починаючи зверху, він нумерує рядки, що починаються з нуля (i.e. 0, 1, 2, 3, 4 тощо.), знижуючись, по одному індексу на рядок. Завдяки цьому його проблема вирішується, оскільки ім’я члена тепер відображається в індексі, а не в назві напою. Отже, оскільки напій ідентифікується за допомогою індексу, ім’я клієнта відображається у відповідному індексі.

Лише стовпець цінностей (напоїв) утворює основну хеш-таблицю. У зміненій таблиці стовпець індексів та пов'язані з ними значення (з дублікатами або без них) утворюють звичайну хеш-таблицю - повне визначення хеш-таблиці наведено нижче. Ключі (перший стовпець) не обов'язково складають частину хеш-таблиці.

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

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

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

Зверніть увагу, що в цьому випадку кожен ключ, який є зрозумілим паролем, відповідає імені користувача. Отже, існує ім’я користувача, яке відповідає ключу, який зіставляється з індексом, який пов’язаний зі значенням, яке є зашифрованим ключем.

Визначення хеш-функції, повне визначення хеш-таблиці, значення масиву та інші деталі наведені нижче. Вам потрібно мати знання вказівників (посилань) та пов’язаних списків, щоб оцінити решту цього підручника.

Значення хеш-функції та хеш-таблиці

Масив

Масив - це набір послідовних розташувань у пам'яті. Усі місця однакового розміру. Значення в першому розташуванні доступне з індексом 0; значення у другому розташуванні здійснюється за допомогою індексу 1; до третього значення звертаються за допомогою індексу 2; четвертий з індексом, 3; і так далі. Зазвичай масив не може збільшуватися або зменшуватися. Для того, щоб змінити розмір (довжину) масиву, потрібно створити новий масив і скопіювати відповідні значення в новий масив. Значення масиву завжди одного типу.

Хеш-функція

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

Хеш-таблиця

Хеш-таблиця - це масив зі значеннями, до індексів яких відображаються ключі. Ключі опосередковано зіставляються зі значеннями. Насправді, ключі називаються відображеними у значеннях, оскільки кожен індекс пов'язаний зі значенням (з дублікатами або без них). Однак функція, яка виконує відображення (i.e. hashing) стосується ключів до індексів масиву, а не насправді до значень, оскільки у значеннях можуть бути дублікати. Наступна схема ілюструє хеш-таблицю для імен людей та їх телефонних номерів. Клітинки масиву (слоти) називаються сегментами.

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

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

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

Чому масив, а не пов’язаний список для хеш-таблиці

Масив для хеш-таблиці можна замінити пов'язаною структурою даних списку, але це може бути проблемою. Перший елемент пов'язаного списку, природно, має індекс 0; другий елемент, природно, має індекс 1; третій, природно, має індекс, 2; і так далі. Проблема пов’язаного списку полягає в тому, що для отримання значення список потрібно переглядати, і це вимагає часу. Доступ до значення в масиві здійснюється за допомогою довільного доступу. Як тільки індекс відомий, значення отримується без ітерації; цей доступ швидший.

Зіткнення

Хеш-функція приймає ключ і хешує відповідний індекс, зчитує відповідне значення або вставляє нове значення. Якщо метою є зчитування значення, поки що немає проблем (немає проблем). Однак, якщо метою є вставка значення, хешований індекс може вже мати пов'язане значення, і це зіткнення; нове значення не можна поставити там, де воно вже є. Є способи вирішення зіткнення - див. Нижче.

Чому виникає зіткнення

У наведеному вище прикладі магазину, імена споживачів є ключами, а назви напоїв - значеннями. Зверніть увагу, що клієнтів занадто багато, хоча масив має обмежений розмір і не може прийняти всіх клієнтів. Отже, у наборі зберігаються лише напої постійних споживачів. Зіткнення могло статися, коли нерегулярний клієнт стає регулярним. Клієнти магазину складають великий набір, тоді як кількість відер для клієнтів у масиві обмежена.

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

Основи вирішення зіткнень

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

Практична хеш-таблиця включає стовпець ключів, але цей стовпчик ключів офіційно не є частиною хеш-таблиці.

Кожен підхід для роздільної здатності може мати порожні відра, не обов’язково в кінці масиву.

Окремий ланцюжок

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

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

Порожні відра позначені літерою х. Решта слотів мають вказівники на зв’язані списки. Кожен елемент зв’язаного списку має два поля даних: одне для імені клієнта, а друге - для телефонного номера. Конфлікт виникає у ключів: Пітера Джонса та Сюзан Лі. Відповідні значення складаються з двох елементів одного пов'язаного списку.

Для конфліктуючих ключів критерієм вставки значення є той самий критерій, який використовується для пошуку (та зчитування) значення.

Відкрита адресація

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

Наступна схема ілюструє вирішення конфліктів з відкритою адресацією:

Хеш-функція бере ключ, Пітер Джонс, хешує індекс 152 і зберігає свій номер телефону у відповідному сегменті. Через деякий час хеш-функція хешує той самий індекс, 152 від ключа Сьюзан Лі, стикаючись з індексом Пітера Джонса. Щоб вирішити цю проблему, значення Сьюзан Лі зберігається у відрі наступного індексу 153, який був порожнім. Хеш-функція хешує індекс, 153 для ключа, Робін Гуд, але цей індекс вже використовувався для вирішення конфлікту для попереднього ключа. Тож значення Робін Гуда поміщається в наступне порожнє відро, тобто значення індексу 154.

Методи вирішення конфліктів для окремого ланцюжка та відкритої адресації

Окремий ланцюжок має свої методи вирішення конфліктів, а відкрита адресація також має власні методи вирішення конфліктів.

Методи вирішення конфліктів окремого ланцюга

Зараз коротко пояснюються методи окремого ланцюгового хеш-таблиць:

Окремий ланцюжок зі зв’язаними списками

Цей спосіб описаний вище. Однак кожен елемент пов'язаного списку не обов'язково повинен мати ключове поле (наприклад,.g. поле імені клієнта вгорі).

Окремий ланцюжок з клітинками головного списку

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

Окремий ланцюжок з іншими структурами

Будь-яку іншу структуру даних, таку як самозбалансоване бінарне дерево пошуку, що підтримує необхідні операції, можна використовувати замість пов’язаного списку - див. Пізніше.

Методи вирішення конфліктів відкритої адресації

Метод вирішення конфлікту при відкритій адресації називається зондовою послідовністю. Нижче коротко пояснюються три добре відомі послідовності зондів:

Лінійне зондування

При лінійному зондуванні, коли виникає конфлікт, шукається найближче порожнє відро під відром конфлікту. Крім того, при лінійному зондуванні як ключ, так і його значення зберігаються в одному сегменті.

Квадратичне зондування

Припустимо, що конфлікт відбувається за індексом Н. Наступний порожній слот (сегмент) з індексом H + 12 використовується; якщо це вже зайнято, то наступне порожнє при H + 22 використовується, якщо це вже зайнято, то наступне порожнє при H + 32 використовується тощо. Є варіанти цього.

Подвійне хешування

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

Ідеальна хеш-функція

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

У наборі символів ASCII великі регістри можуть бути зіставлені з відповідними малими літерами за допомогою хеш-функції. Букви представлені в пам'яті комп'ютера у вигляді цифр. У наборі символів ASCII A дорівнює 65, B дорівнює 66, C дорівнює 67 тощо. і a дорівнює 97, b дорівнює 98, c дорівнює 99 тощо. Для відображення від А до А додайте 32 до 65; для відображення від B до b додайте 32 до 66; для відображення від C до c додати 32 до 67; і так далі. Тут великі літери - це клавіші, а малі - значення. Хеш-таблицею для цього може бути масив, значення якого є пов'язаними індексами. Пам’ятайте, відра масиву можуть бути порожніми. Тож відра в масиві від 64 до 0 можуть бути порожніми. Хеш-функція просто додає 32 до великого коду, щоб отримати індекс, а отже, і малу літеру. Така функція є досконалою хеш-функцією.

Хешування від цілих до цілих індексів

Існують різні методи хешування цілого числа. Один з них називається методом поділу за модулем (функція).

Функція хешування модуля підрозділу

Функція в комп'ютерному програмному забезпеченні не є математичною функцією. У обчисленні (програмному забезпеченні) функція складається з набору тверджень, яким передують аргументи. Для функції поділу за модулем ключі є цілими числами та відображаються в індексах масиву сегментів. Набір ключів великий, тому буде відображено лише ті ключі, які з великою часткою ймовірності мають місце в діяльності. Отже, зіткнення трапляються, коли малоймовірні ключі мають бути зіставлені.

У заяві,

20/6 = 3R2

20 - дивіденд, 6 - дільник, 3 - залишок 2 - частка. Залишок 2 також називається за модулем. Примітка: можна мати модуль 0.

Для цього хешування розмір таблиці зазвичай дорівнює 2, e.g. 64 = 26 або 256 = 28, тощо.  Дільник для цієї функції хешування є простим числом, близьким до розміру масиву. Ця функція ділить ключ на дільник і повертає модуль. Модуль - це індекс масиву сегментів. Пов’язане значення в сегменті - це значення на ваш вибір (значення для ключа).

Хешування ключів змінної довжини

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

Хешування перетворення Radix

У рядку кожен символ у комп’ютері є числом. У цьому методі,

Хеш-код (індекс) = x0ak − 11ak − 2+… + Xk − 2a1k − 1a0

Де (x0, x1,…, xk − 1) - символи вхідного рядка, а a - радіус, e.g. 29 (див. Далі). k - кількість символів у рядку. Це ще щось - дивіться пізніше.

Ключі та цінності

У парі ключ / значення значення не обов'язково може бути числом або текстом. Це також може бути запис. Запис - це список, написаний горизонтально. У парі ключ / значення кожен ключ може насправді посилатися на якийсь інший текст, номер або запис.

Асоціативний масив

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

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

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

Оскільки асоціативний масив є структурою даних, то має щонайменше такі операції:

Операції з асоціативним масивом

вставити або додати

Це вставляє нову пару ключ / значення в колекцію, відображаючи ключ до її значення.

перепризначити

Ця операція замінює значення певного ключа на нове значення.

видалити або видалити

Це видаляє ключ плюс відповідне йому значення.

пошук

Ця операція шукає значення певного ключа і повертає значення (не видаляючи його).

Висновок

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

HD Remastered Games для Linux, які раніше ніколи не випускали Linux
Багато розробників і видавців ігор випускають HD-ремастер старих ігор, щоб продовжити життя франшизи. Будь ласка, шанувальники просять сумісність із с...
Як використовувати AutoKey для автоматизації ігор Linux
AutoKey - це утиліта автоматизації робочого столу для Linux та X11, запрограмована на Python 3, GTK та Qt. Використовуючи його сценарії та функціональ...
How to Show FPS Counter in Linux Games
Linux gaming got a major push when Valve announced Linux support for Steam client and their games in 2012. Since then, many AAA and indie games have m...